免责声明
本文的爬虫知识仅用于合法和合理的数据收集,使用者需遵守相关法律法规及目标网站的爬取规则,尊重数据隐私,合理设置访问频率,不得用于非法目的或侵犯他人权益。因使用网络爬虫产生的任何法律纠纷或损失,由使用者自行承担风险和责任。
1、Scrapy保存数据到数据库
概述:下面通过一个完整的案例让大家了解Scrapy如何将数据保存到不同的数据库。
1.1、获取数据
1.1.1.创建项目和爬虫文件
1.1.2.编写爬虫文件
import scrapy
import json
class SsqSpider(scrapy.Spider):
name = "ssq"
allowed_domains = ["gov.cn"]
start_urls = ["https://www.cwl.gov.cn/cwl_admin/front/cwlkj/search/kjxx/findDrawNotice?name=ssq&pageNo=1&pageSize=30&systemType=PC"]
def parse(self, response):
datas = json.loads(response.text)
# 获取所有的数据列表
for data in datas.get("result"):
# 期号 红球 蓝球
print(data.get("code"),'===',data.get("red"),'===',data.get("blue"))
1.1.3、修改配置文件
# 添加请求头
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,
'scrapy_fake_useragent.middleware.RandomUserAgentMiddleware': 400,
'scrapy_fake_useragent.middleware.RetryUserAgentMiddleware': 401,
}
# 配置 UserAgent 类型
FAKEUSERAGENT_PROVIDERS = [
'scrapy_fake_useragent.providers.FakeUserAgentProvider', # 第一选择
'scrapy_fake_useragent.providers.FakerProvider', # 第二选择
'scrapy_fake_useragent.providers.FixedUserAgentProvider', # 第三选择
]
# 关闭爬虫规则
# ROBOTSTXT_OBEY = True
1.1.3、创建并编辑启动脚本
在scrapy09项目下创建启动脚本begin:
from scrapy.cmdline import execute
execute(["scrapy","crawl","ssq"])
运行启动脚本,结果如下:
1.2、将数据保存到mongo
概述:将前面获取到的数据返回给引擎,引擎再交给pipeline,pipeline再将获取的数据保存到指定数据库中。
1.2.1、修改爬虫文件
import scrapy
import json
class SsqSpider(scrapy.Spider):
name = "ssq"
allowed_domains = ["gov.cn"]
start_urls = ["https://www.cwl.gov.cn/cwl_admin/front/cwlkj/search/kjxx/findDrawNotice?name=ssq&pageNo=1&pageSize=30&systemType=PC"]
def parse(self, response):
datas = json.loads(response.text)
# 获取所有的数据列表
for data in datas.get("result"):
# 将获取的数据传递给引擎
yield {
"code":data.get("code"), # 期号
"red":data.get("red"), # 红球
"blue":data.get("blue") # 蓝球
}
1.2.2、修改Pipeline文件
import pymongo
class MongoPipeline:
def open_spider(self):
# 获取客户端链接
self.client = pymongo.MongoClient()
# 通过客户端获取数据库实例,再通过数据库实例获取指定的集合
self.ssq = self.client.myssq_data.ssq
def process_item(self, item, spider):
# 将引擎传过来的数据写入mongo数据库
self.ssq.insert_one(item)
return item
def close_spider(self):
self.client.close()
1.2.3、编辑配置文件
# 主要是开启ITEM_PIPELINES,注意将pipeline修改成自己的pipeline名称
ITEM_PIPELINES = {
"scrapy09.pipelines.MongoPipeline": 300,
}
1.2.4、运行爬虫文件
注意:运行前一定要提前开启mongo,否则Pycharm将无法连接到mongo。
运行启动脚本后,通过图形管理工具Robo 3T查看mongo数据库中的数据如下,由此可知已将获取的数据正确的保存到了mongo数据库中:
1.3、将数据保存到MySql
注意:以下操作都是在1.2的基础上进行,如果跳过了上面的内容无法实现的可以查看是否少了上面的某些操作。
1.3.1、操作前准备
首先下载pymysql(在终端中运行如下代码):
pip install pymysql
然后去自己的mysql数据库创建一个名为ssq的数据库,并进入ssq数据库创建一个名为ssq_table的表,以下是ssq_table的表结构(以上名字都是可以改的,只是修改后代码也许做出对应的调整):
1.3.2、编写Pipeline文件
概述:在1.1小节中就以及将数据获取了,本节只需编写Pipeline文件将数据存储到mysql即可。
import pymysql
class MysqlPipeline:
# 创建链接
def open_spider(self,spider):
# 获取客户端连接(要提前在mysql中创建一个名为ssq的数据库)
self.client = pymysql.connect(host="localhost",port=3306,user="root",password="123456",db="ssq",charset="utf8")
# 获取游标对象
self.cursor = self.client.cursor()
def process_item(self, item, spider):
# 向表中插入数据的sql语句(ssq_table这个表也要提前创建)
sql = "insert into ssq_table (id,code,red,blue) values (0,%s,%s,%s)"
# 对应sql中要传入的参数
args = (item['code'],item['red'],item['blue'])
# 执行sql语句
self.cursor.execute(sql,args)
# 由于修改了数据,需要提交数据后才能生效
self.client.commit()
return item
# 关闭链接
def close_spider(self):
# 关闭游标对象
self.cursor.close()
# 关闭客户端连接
self.client.close()
1.3.3、编辑配置文件
ITEM_PIPELINES = {
# "scrapy09.pipelines.MongoPipeline": 300,
"scrapy09.pipelines.MysqlPipeline": 300,
}
1.3.4、运行脚本文件
运行启动脚本后,我是通过Navicat查看数据库内容的,后面我扩大了获取的范围(用我之前给的爬虫文件照样能拿到数据,不用担心这里),所以我的数据会多一点,具体数据如下:
1.4、将数据保存到多个数据库
1.4.1、所有数据同时保存到不同的数据库
概述:在前面的章节中,我们都是将数据保存到单一的数据库中,那么能否将所有的数据保存到不同的数据库中呢?当然是可以实现的,下面的代码是对上面章节的修改:
在上面的代码中,我们已经准备好了两个Pipeline(这里没做修改):
import pymongo
class MongoPipeline:
def open_spider(self,spider):
# 获取客户端链接
self.client = pymongo.MongoClient()
# 通过客户端获取数据库实例,再通过数据库实例获取指定的集合
self.ssq = self.client.myssq_data.ssq
def process_item(self, item, spider):
# 将引擎传过来的数据写入mongo数据库
self.ssq.insert_one(item)
return item
def close_spider(self):
self.client.close()
import pymysql
class MysqlPipeline:
# 创建链接
def open_spider(self,spider):
# 获取客户端连接(要提前在mysql中创建一个名为ssq的数据库)
self.client = pymysql.connect(host="localhost",port=3306,user="root",password="123456",db="ssq",charset="utf8")
# 获取游标对象
self.cursor = self.client.cursor()
def process_item(self, item, spider):
# 向表中插入数据的sql语句(ssq_table这个表也要提前创建)
sql = "insert into ssq_table (id,code,red,blue) values (0,%s,%s,%s)"
# 对应sql中要传入的参数
args = (item['code'],item['red'],item['blue'])
# 执行sql语句
self.cursor.execute(sql,args)
# 由于修改了数据,需要提交数据后才能生效
self.client.commit()
return item
# 关闭链接
def close_spider(self):
# 关闭游标对象
self.cursor.close()
# 关闭客户端连接
self.client.close()
并且在1.1小节中已经获取了所需数据,所以只需在前面的基础上对配置文件做出修改即可:
ITEM_PIPELINES = {
# 这里演示优先将数据保存到Mongo数据库中
"scrapy09.pipelines.MongoPipeline": 300,
"scrapy09.pipelines.MysqlPipeline": 301,
}
1.4.2、保存指定爬虫文件的数据到不同数据库
概述:在实际生产环境中,大概率同时有多个爬虫文件在运行,难免就会出现不同的爬虫文件要将数据保存到不同的数据库中,下面模拟如果我们的scrapy项目中有多个爬虫文件,如何实现ssq.py这个爬虫文件的数据保存到mongo数据库中(主要是修改Mongo的Pipeline):
import pymongo
class MongoPipeline:
def open_spider(self,spider):
# 获取客户端链接
self.client = pymongo.MongoClient()
# 通过客户端获取数据库实例,再通过数据库实例获取指定的集合
self.ssq = self.client.myssq_data.ssq
def process_item(self, item, spider):
if spider.name == "ssq":
# 将引擎传过来的数据写入mongo数据库
self.ssq.insert_one(item)
return item
def close_spider(self):
self.client.close()
提示:在学习过程中可能会有同学说既然这里对文件做了判断,如果不是指定名称的文件能不能在后面加上 elif 或 else 使其保存到指定的数据库中呢?这种做法当然是能够实现的,但是不推荐这样操作,因为这样操作后会增加程序的耦合性,建议不同的数据库有不同的Pipeline,就像我们前面学习的那样,Mysql和Mongo都有属于自己的Pipeline。
总结:在Scrapy项目中,同时开启多个爬虫和Pipeline时,可以通过spider.name来区分使用哪个pipeline。
知识点补充:部分同学可能会疑问return item有什么作用,其实return item的作用就是让数据(item)进入到下一个Pipeline。
1.4.3、保存指定值的数据到指定数据库
下面模拟code为2024091的数据保存到mongo数据库中,其它数据保存到Mysql中(首先需要修改mongo的Pipeline):
import pymongo
class MongoPipeline:
def open_spider(self,spider):
# 获取客户端链接
self.client = pymongo.MongoClient()
# 通过客户端获取数据库实例,再通过数据库实例获取指定的集合
self.ssq = self.client.myssq_data.ssq
def process_item(self, item, spider):
if item.get("code") == "2024091":
# 将引擎传过来的数据写入mongo数据库
self.ssq.insert_one(item)
return item
def close_spider(self):
self.client.close()
但是在上一小节1.4.2的知识点补充中就有提到,return item会将数据传递到下一个Pipeline(即使数据已经保存到指定数据库中,仍然会传递到下一个Pipeline中),因此还需要对其它Pipeline做出判断,如果发现code为2024091的值就不要保存了:
import pymysql
from scrapy.exceptions import DropItem
class MysqlPipeline:
# 创建链接
def open_spider(self,spider):
# 获取客户端连接(要提前在mysql中创建一个名为ssq的数据库)
self.client = pymysql.connect(host="localhost",port=3306,user="root",password="123456",db="ssq",charset="utf8")
# 获取游标对象
self.cursor = self.client.cursor()
def process_item(self, item, spider):
if item.get("code") == "2024091":
raise DropItem("2024091只能保存到mongo数据库中")
# 向表中插入数据的sql语句(ssq_table这个表也要提前创建)
sql = "insert into ssq_table (id,code,red,blue) values (0,%s,%s,%s)"
# 对应sql中要传入的参数
args = (item['code'],item['red'],item['blue'])
# 执行sql语句
self.cursor.execute(sql,args)
# 由于修改了数据,需要提交数据后才能生效
self.client.commit()
return item
# 关闭链接
def close_spider(self):
# 关闭游标对象
self.cursor.close()
# 关闭客户端连接
self.client.close()
运行结果如下(运行前最好情况之前获取的数据,这样结果更加直观):
mongo数据如下:
Mysql数据如下:
总结:DropItem()表示该数据不再保存,某个数据被DropItem()后,该数据将不会进入下一个Pipeline。
2、Scrapy实战
2.1、数据提取
需求: 获取某家的二手房数据,要求包含房屋基本信息与详情。
2.1.1、创建项目和爬虫文件
2.1.2、编写爬虫文件
import scrapy
import re
class LianjiaSpider(scrapy.Spider):
name = "lianjia"
allowed_domains = ["lianjia.com"]
start_urls = ["https://bj.lianjia.com/ershoufang/pa1/"]
def parse(self, response):
"""
解析页面列表
"""
# 获取页面中所有详情页的链接
all_room_link = response.xpath('//div[@class="title"]/a/@href').getall()
# 请求每一个链接并交由parse_room函数处理
for room_link in all_room_link:
yield scrapy.Request(url=room_link,callback=self.parse_room)
def parse_room(self,response):
"""
解析详细数据
"""
# 价格
price = response.xpath('//span[@class="total"]/text()').get()
# price = response.xpath('//div[@class="price-container"]/div[@class="price"]/span[@class="total"]').get() + response.xpath('//div[@class="price-container"]/div[@class="price"]/span[@class="unit"]').get()
# 小区名称
house_name = response.xpath('//div[@class="communityName"]/a[1]/text()').get()
# 区域名称
area_name = response.xpath('//div[@class="areaName"]/span[@class="info"]/a/text()').get()
# 基本信息
# 房屋户型
huxing1 = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[1]').get()).group(1)
# 楼层
louceng = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[2]').get()).group(1)
# 面积
mianji = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[3]').get()).group(1)
# 户型结构
huxing2 = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[4]').get()).group(1)
# 朝向
direction = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[7]').get()).group(1)
# 装修情况
zhuangxiu = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[9]').get()).group(1)
# 梯户
tihu = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[10]').get()).group(1)
# 供暖
nuanqi = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[11]').get()).group(1)
# 是否有电梯
dianti = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[12]').get()).group(1)
# 交易信息
# 交易权属
quanshu = response.xpath('//div[@class="transaction"]/div/ul/li[2]/span[2]/text()').get()
# 房屋用途
yongtu = response.xpath('//div[@class="transaction"]/div/ul/li[4]/span[2]/text()').get()
2.1.3、编辑配置文件
# 不遵守爬虫规则
# ROBOTSTXT_OBEY = True
# 设置UserAgent
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None,
'scrapy.downloadermiddlewares.retry.RetryMiddleware': None,
'scrapy_fake_useragent.middleware.RandomUserAgentMiddleware': 400,
'scrapy_fake_useragent.middleware.RetryUserAgentMiddleware': 401,
}
# 配置 UserAgent 类型
FAKEUSERAGENT_PROVIDERS = [
'scrapy_fake_useragent.providers.FakeUserAgentProvider', # 第一选择
'scrapy_fake_useragent.providers.FakerProvider', # 第二选择
'scrapy_fake_useragent.providers.FixedUserAgentProvider', # 第三选择
]
# 设置延时(降低服务器压力,不影响目标网站正常运行)
DOWNLOAD_DELAY = 5
# 如果设置了以上内容还是被拦截可以设置请求头
2.1.4、创建并编写启动脚本
具体内容如下:
from scrapy.cmdline import execute
execute(["scrapy","crawl","lianjia"])
2.1.5、测试程序
概述:上面的程序主要用于获取一页的所有详情页的具体内容,测试程序最高效的办法就是通过调试程序实现,避免多次请求对目标服务器造成压力,能正确的拿到数据就算成功。
2.2、优化爬虫程序
2.2.1、简化公共代码
概述:在parse_room函数中的获取基本信息部分的代码通过xpath获取数据时存在很多公共代码,下面演示简化操作:
简化前:
# 基本信息
# 房屋户型
huxing1 = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[1]').get()).group(1)
# 楼层
louceng = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[2]').get()).group(1)
# 面积
mianji = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[3]').get()).group(1)
# 户型结构
huxing2 = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[4]').get()).group(1)
# 朝向
direction = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[7]').get()).group(1)
# 装修情况
zhuangxiu = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[9]').get()).group(1)
# 梯户
tihu = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[10]').get()).group(1)
# 供暖
nuanqi = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[11]').get()).group(1)
# 是否有电梯
dianti = re.search("</span>(.*)\\n",response.xpath('//div[@class="base"]/div[@class="content"]/ul/li[12]').get()).group(1)
简化后:
# 基本信息
# 公共部分
base = response.xpath('//div[@class="base"]/div[@class="content"]/ul')
# 房屋户型 //div[@class="content"]/ul/li[1]/text()
huxing1 = re.search("</span>(.*)\\n",base.xpath('.li[1]').get()).group(1)
# 楼层
louceng = re.search("</span>(.*)\\n",base.xpath('./li[2]').get()).group(1)
# 面积
mianji = re.search("</span>(.*)\\n",base.xpath('./li[3]').get()).group(1)
# 户型结构
huxing2 = re.search("</span>(.*)\\n",base.xpath('./li[4]').get()).group(1)
# 朝向
direction = re.search("</span>(.*)\\n",base.xpath('./li[7]').get()).group(1)
# 装修情况
zhuangxiu = re.search("</span>(.*)\\n",base.xpath('./li[9]').get()).group(1)
# 梯户
tihu = re.search("</span>(.*)\\n",base.xpath('./li[10]').get()).group(1)
# 供暖
nuanqi = re.search("</span>(.*)\\n",base.xpath('./li[11]').get()).group(1)
# 是否有电梯
dianti = re.search("</span>(.*)\\n",base.xpath('./li[12]').get()).group(1)
2.2.2、获取多页数据
概述:在前面的代码中我们只获取一页中所有的详情页,下面尝试从广度优先和深度优先的角度获取多页数据。
广度优先:这种方式会先获取所有的页面链接,以获取100页数据为例,想要拿到详情数据需要等到第101次请求,所以只作为参考,本项目不使用。
# 广度优先(优先获取每一页的链接,然后再从每一页的链接获取每一页中详情页的链接)
start_urls = [f"https://bj.lianjia.com/ershoufang/pa{i}/" for i in range(1,101)]
深度优先:该方式获取到一页的链接后会直接去拿详情页链接,然后再去拿下一页链接,以此类推,除修改类属性外还需修改parse()函数。
class LianjiaSpider(scrapy.Spider):
name = "lianjia"
allowed_domains = ["lianjia.com"]
# 广度优先(优先获取每一页的链接,然后再从每一页的链接获取每一页中详情页的链接)
# start_urls = [f"https://bj.lianjia.com/ershoufang/pa{i}/" for i in range(1,101)]
# 深度优先(获取到一页的链接后,直接获取这一页的详情页链接,然后再去获取下一页链接...)
page = 1
start_urls = [f"https://bj.lianjia.com/ershoufang/pa{page}/"]
def parse(self, response):
"""
解析页面列表
"""
# 获取页面中所有详情页的链接
all_room_link = response.xpath('//div[@class="title"]/a/@href').getall()
# 请求每一个链接并交由parse_room函数处理
for room_link in all_room_link:
# 返回每一个详情页的链接交给parse_room函数处理
yield scrapy.Request(url=room_link,callback=self.parse_room)
# 当一页页面的详情页链接都解析完并交给parse_room函数后,立即请求下一页的链接并提取详情页链接
self.page += 1
yield scrapy.Request(url = f"https://bj.lianjia.com/ershoufang/pa{self.page}/")
2.3、保存数据
2.3.1、将数据传递给Pipeline
概述:首先将数据返回给ItemPipeline处理,由于数据量较大,推荐将要返回的数据粘贴到一个空文件中,然后用Ctrl+R使用正则做替换:
然后手动对无用的信息做清理,很快就获取到所有要返回的信息:
yield {
# 价格
'price': price,
# 小区名称
'house_name': house_name,
# 区域名称
'area_name': area_name,
# 基本信息
# 户型
'huxing1': huxing1,
# 楼层
'louceng': louceng,
# 面积
'mianji': mianji,
# 户型结构
'huxing2': huxing2,
# 朝向
'direction': direction,
# 装修情况
'zhuangxiu': zhuangxiu,
# 梯户
'tihu': tihu,
# 供暖
'nuanqi': nuanqi,
# 是否有电梯
'dianti': dianti,
# 交易信息
# 交易权属
'quanshu': quanshu,
# 房屋用途
'yongtu': yongtu,
}
2.3.2、编辑Pipeline
概述:接下来编辑Pipeline文件,将获取到的数据保存到mongo数据库中(为避免出错,建议提取打开mongo的启动脚本):
import pymongo
class MongoPipeline:
def open_spider(self,spider):
# 获取mongoDB链接
self.client = pymongo.MongoClient()
# 通过mongoDB链接找到(没有会自动创建)数据库实例room,然后用数据库实例找到集合lianjia,最后将该集合赋值给self.lianjia
self.lianjia = self.client.room.lianjia
def process_item(self, item, spider):
# 将爬虫文件传过来的数据存入mongoDB
self.lianjia.insert_one(item)
return item
def close_spider(self,spider):
# 关闭mongoDB
self.client.close()
2.3.3、编辑配置文件
概述:这里主要是打开ItemPipeline,并将值替换成自己的Pipeline的名称。
ITEM_PIPELINES = {
"scrapy10.pipelines.MongoPipeline": 300,
}
2.3.4、运行脚本并查看数据库
概述:运行启动脚本之前一定要开启mongo,否则Pipeline无法获取到mongoDB的链接。
运行后的mongo数据库内容如下(该网站目前有反爬措施,获取部分数据后不绕过反爬就会被重定向到首页,需要通过学习反反爬后破解):
完整爬虫文件如下:
import scrapy
import re
class LianjiaSpider(scrapy.Spider):
name = "lianjia"
allowed_domains = ["lianjia.com","ke.com"]
# 广度优先(优先获取每一页的链接,然后再从每一页的链接获取每一页中详情页的链接)
# start_urls = [f"https://bj.lianjia.com/ershoufang/pa{i}/" for i in range(1,101)]
# 深度优先(获取到一页的链接后,直接获取这一页的详情页链接,然后再去获取下一页链接...)
page = 1
start_urls = [f"https://bj.lianjia.com/ershoufang/pa{page}/"]
def parse(self, response):
"""
解析页面列表
"""
# 获取页面中所有详情页的链接
all_room_link = response.xpath('//div[@class="title"]/a/@href').getall()
# 请求每一个链接并交由parse_room函数处理
for room_link in all_room_link:
# 返回每一个详情页的链接交给parse_room函数处理
yield scrapy.Request(url=room_link,callback=self.parse_room)
if self.page<=60:
# 当一页页面的详情页链接都解析完并交给parse_room函数后,立即请求下一页的链接
self.page += 1
yield scrapy.Request(url=f"https://bj.lianjia.com/ershoufang/pa{self.page}/",callback=self.parse)
def parse_room(self,response):
"""
解析详细数据
"""
if response.url.__contains__('https://bj.lianjia.com/ershoufang/'):
# 价格
price = response.xpath('//span[@class="total"]/text()').get()
# price = response.xpath('//div[@class="price-container"]/div[@class="price"]/span[@class="total"]').get() + response.xpath('//div[@class="price-container"]/div[@class="price"]/span[@class="unit"]').get()
# 小区名称
house_name = response.xpath('//div[@class="communityName"]/a[1]/text()').get()
# 区域名称
area_name = response.xpath('//div[@class="areaName"]/span[@class="info"]/a/text()').get()
# 基本信息
# 公共部分
base = response.xpath('//div[@class="base"]/div[@class="content"]/ul')
# 房屋户型 //div[@class="content"]/ul/li[1]/text()
huxing1 = re.search("</span>(.*)\\n",base.xpath('./li[1]').get()).group(1)
# 楼层
louceng = re.search("</span>(.*)\\n",base.xpath('./li[2]').get()).group(1)
# 面积
mianji = re.search("</span>(.*)\\n",base.xpath('./li[3]').get()).group(1)
# 户型结构
huxing2 = re.search("</span>(.*)\\n",base.xpath('./li[4]').get()).group(1)
# 朝向
direction = re.search("</span>(.*)\\n",base.xpath('./li[7]').get()).group(1)
# 装修情况
zhuangxiu = re.search("</span>(.*)\\n",base.xpath('./li[9]').get()).group(1)
# 梯户
tihu = re.search("</span>(.*)\\n",base.xpath('./li[10]').get()).group(1)
# 供暖
nuanqi = re.search("</span>(.*)\\n",base.xpath('./li[11]').get()).group(1)
# 是否有电梯
dianti = re.search("</span>(.*)\\n",base.xpath('./li[12]').get()).group(1)
# 交易信息
# 交易权属
quanshu = response.xpath('//div[@class="transaction"]/div/ul/li[2]/span[2]/text()').get()
# 房屋用途
yongtu = response.xpath('//div[@class="transaction"]/div/ul/li[4]/span[2]/text()').get()
yield {
# 价格
'price': price,
# 小区名称
'house_name': house_name,
# 区域名称
'area_name': area_name,
# 基本信息
# 户型
'huxing1': huxing1,
# 楼层
'louceng': louceng,
# 面积
'mianji': mianji,
# 户型结构
'huxing2': huxing2,
# 朝向
'direction': direction,
# 装修情况
'zhuangxiu': zhuangxiu,
# 梯户
'tihu': tihu,
# 供暖
'nuanqi': nuanqi,
# 是否有电梯
'dianti': dianti,
# 交易信息
# 交易权属
'quanshu': quanshu,
# 房屋用途
'yongtu': yongtu,
}