使用 Python 爬虫库完成链家二手房(https://bj.lianjia.com/ershoufang/rs/)房源信息抓取,包括楼层、区域、总价、单价等信息。
第一页:https://bj.lianjia.com/ershoufang/pg1/
第二页:https://bj.lianjia.com/ershoufang/pg2/
第三页:https://bj.lianjia.com/ershoufang/pg3/
第n页:https://bj.lianjia.com/ershoufang/pgn/
使用 Chrome 开发者工具对页面元素进行审查,从而确定 Xpath 表达式。首先根据要抓取的数据确定“基准表达式”。通过审查一处房源的元素结构,可以得知房源信息都包含在以下代码中:
<div class="info clear">
<div class="title"><a class="" href="https://bj.lianjia.com/ershoufang/101122052862.html" target="_blank"
data-log_index="1" data-el="ershoufang" data-housecode="101122052862" data-is_focus="" data-sl="">精装修大三居双卫,南北通透,有车位满五唯一</a><!-- 拆分标签 只留一个优先级最高的标签--><span
class="goodhouse_tag tagBlock">必看好房</span></div>
<div class="flood">
<div class="positionInfo"><span class="positionIcon"></span><a href="https://bj.lianjia.com/xiaoqu/1111027377165/"
target="_blank" data-log_index="1" data-el="region">金汉绿港三区 </a> - <a href="https://bj.lianjia.com/ershoufang/shunyicheng/"
target="_blank">顺义城</a> </div>
</div>
<div class="address">
<div class="houseInfo"><span class="houseIcon"></span>3室2厅 | 139.96平米 | 南 北 | 简装 | 低楼层(共20层) | 2009年 | 板塔结合</div>
</div>
<div class="followInfo"><span class="starIcon"></span>12人关注 / 29天以前发布</div>
<div class="tag"><span class="subway">近地铁</span><span class="vr">VR房源</span><span class="taxfree">房本满五年</span><span
class="haskey">随时看房</span></div>
<div class="priceInfo">
<div class="totalPrice totalPrice2"><i> </i><span class="">485</span><i>万</i></div>
<div class="unitPrice" data-hid="101122052862" data-rid="1111027377165" data-price="34653"><span>34,653元/平</span></div>
</div>
</div>
待抓取的房源信息都包含在相应的
<div class="positionInfo">..</div>
<div class="address">...</div>
<div class="priceInfo">...</div>
而每个页面中都包含 30 个房源,因此我们要匹配以下节点的父节点或者先辈节点,从而确定 Xpath 基准表达式:
<div class="info clear"></div>
通过页面结构分析可以得出每页的 30 个房源信息全部包含以下节点中:
<ul class="sellListContent" log-mod="list">
<li class="clear LOGVIEWDATA LOGCLICKDATA">
房源信息..
</li>
</ul>
特别注意:这里可以class="clear LOGVIEWDATA LOGCLICKDATA"需要使用源码查看
因此 Xpath 基准表达式如下所示:
//ul[@class="sellListContent"]/li[@class="clear LOGVIEWDATA LOGCLICKDATA"]
根据页面元素结构确定待抓取信息的 Xpath 表达式,分别如下:
小区名称:position = h.xpath('.//a[@data-el="region"]/text()')[0]
房屋介绍:hourseInfo_list = h.xpath('.//div[@class="houseInfo"]/text()')
单价信息:addresunitPrice = h.xpath('.//div[@class="unitPrice"]//text()')[0].strip()
总价信息:totalPrice = h.xpath('.//div[@class="totalPrice totalPrice2"]//text()')[0].strip()
其中房屋介绍,主要包含了以下信息:
<div class="houseInfo">
<span class="houseIcon"></span>4室2厅 | 133.68平米 | 南 北 | 精装 | 顶层(共6层) | 板楼
</div>
因此,匹配出的 info_list 列表需要经过处理才能得出我们想要的数据,如下所示:
# 房屋信息:3室2厅 | 147.95平米 | 南 东南 | 简装 | 中楼层(共18层) | 塔楼
hourseInfo_list = h.xpath('.//div[@class="houseInfo"]/text()')
if hourseInfo_list:
hourseInfo = hourseInfo_list[0].split("|")
if len(hourseInfo) >= 5:
if hourseInfo:
# 户型
item.append(hourseInfo[0].strip())
# 面积
item.append(hourseInfo[1].strip())
# 方向
item.append(hourseInfo[2].strip())
# 是否精装
item.append(hourseInfo[3].strip())
# 楼层
item.append(hourseInfo[4].strip())
# 楼型
item.append(hourseInfo[5].strip())
为了提高网页信息的抓取质量,减小网络波动带来的响应,我们可以设置一个规则:在超时时间内(3秒),在该时间内对于请求失败的页面尝试请求三次,如果均未成功,则抓取下一个页面。
通过上述分析得出了所有的 Xpath 表达式,下面开始编写爬虫程序,代码如下:
在# coding:utf8
import requests
import random
from lxml import etree
import time
# 提供ua信息的的包
from fake_useragent import UserAgent
# mysql数据库模块
import pymysql
class LianJiaSpider(object):
# 构造方法,设置请求路径,每个页面请求次数,数据库连接对象、游标对象
def __init__(self):
# 爬取路径{}表示第几页的数据
self.url = 'https://bj.lianjia.com/ershoufang/pg{}/'
# 计数,请求一个页面的次数,初始值为1
self.count = 1
# 数据库连接对象
self.db = pymysql.connect(host="127.0.0.1", user='root', password="", db='lianjia')
# 数据库游标对象
self.cursor = self.db.cursor()
# 随机获取UA
def get_headers(self):
ua = UserAgent()
# headers = { # 设置header
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
# # 'Accept-Encoding': 'gzip, deflate, br',
# 'Accept-Language': 'zh-CN,zh;q=0.9',
# 'Cache-Control': 'no-cache',
# 'Connection': 'keep-alive',
# 'User-Agent': 'Mozilla/4.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36',
# 'referer': 'https://passport.meituan.com/',
# 'Cookie': '__mta=42753434.1633656738499.1634781127005.1634781128998.34; uuid_n_v=v1; _lxsdk_cuid=17c5d879290c8-03443510ba6172-6373267-144000-17c5d879291c8; uuid=60ACEF00317A11ECAAC07D88ABE178B722CFA72214D742A2849B46660B8F79A8; _lxsdk=60ACEF00317A11ECAAC07D88ABE178B722CFA72214D742A2849B46660B8F79A8; _csrf=94b23e138a83e44c117736c59d0901983cb89b75a2c0de2587b8c273d115e639; Hm_lvt_703e94591e87be68cc8da0da7cbd0be2=1634716251,1634716252,1634719353,1634779997; Hm_lpvt_703e94591e87be68cc8da0da7cbd0be2=1634781129; _lxsdk_s=17ca07b2470-536-b73-84%7C%7C12'
headers = {'User-Agent': ua.random}
return headers
# 获取html
def get_html(self, url):
# 在超时内,对失败请求页面尝试三次
if self.count <= 3:
try:
# 获取HTTPResponse对象
resp = requests.get(url=url, headers=self.get_headers(), timeout=5)
html = resp.text
# print(html)
return html
except Exception as e:
print(e)
self.get_html(url)
self.count += 1
# 数据的解析
def parse_html(self, url):
# print(url)
html = self.get_html(url)
# print(html)
if html:
parse_html = etree.HTML(html)
# 获取每页显示的三十个房源列表
sellListContent = parse_html.xpath(
'//ul[@class="sellListContent"]/li[@class="clear LOGVIEWDATA LOGCLICKDATA"]')
# print(sellListContent)
# 存储所有的数据列表
h_list = []
for h in sellListContent:
item = []
# 小区
position = h.xpath('.//a[@data-el="region"]/text()')[0]
item.append(position)
# 房屋信息:3室2厅 | 147.95平米 | 南 东南 | 简装 | 中楼层(共18层) | 塔楼
hourseInfo_list = h.xpath('.//div[@class="houseInfo"]/text()')
if hourseInfo_list:
hourseInfo = hourseInfo_list[0].split("|")
if len(hourseInfo) >= 5:
if hourseInfo:
# 户型
item.append(hourseInfo[0].strip())
# 面积
item.append(hourseInfo[1].strip())
# 方向
item.append(hourseInfo[2].strip())
# 是否精装
item.append(hourseInfo[3].strip())
# 楼层
item.append(hourseInfo[4].strip())
# 楼型
item.append(hourseInfo[5].strip())
# 单价
unitPrice = h.xpath('.//div[@class="unitPrice"]//text()')[0].strip()
# 总价
totalPrice = h.xpath('.//div[@class="totalPrice totalPrice2"]//text()')[0].strip()
item.append(unitPrice)
item.append(totalPrice)
h_list.append(item)
return h_list
def save_html(self, url):
try:
h_list = self.parse_html(url)
# 将列表中的每个元素转换为元祖
h_list = list(map(tuple, h_list))
# print(h_list)
sql = "insert into house (position,housetype,area,direction,hardcover,floor,buildtype,unitprice,totalprice)values(%s,%s,%s,%s,%s,%s,%s,%s,%s)"
self.cursor.executemany(sql, h_list)
self.db.commit()
except Exception as e:
self.db.rollback()
print("数据库添加失败", str(e.args))
def run(self):
try:
start = int(input("请输入开始页:"))
stop = int(input("请输入终结页:"))
for i in range(start, stop + 1):
url = self.url.format(i)
self.save_html(url)
time.sleep(random.randint(2, 3))
except Exception as e:
print("抓取失败,", e)
finally:
self.cursor.close()
self.db.close()
if __name__ == '__main__':
begin = time.time()
spider = LianJiaSpider()
spider.run()
end = time.time()
print('数据爬取完毕,总共耗时:%.2f' % (end - begin))
mysql> create database lianjia;
Query OK, 1 row affected (0.00 sec)
mysql> use lianjia
Database changed
mysql> create table house(
-> position varchar(40),
-> housetype varchar(40),
-> area varchar(20),
-> direction varchar(20),
-> hardcover varchar(20),
-> floor varchar(20),
-> buildtype varchar(20),
-> unitPrice varchar(10),
-> totalPrice varchar(10)
-> );
Query OK, 0 rows affected (0.05 sec)