目录
get_house_data()函数:用于获取房屋的具体信息
项目背景
目前房源数据作为人们都经常讨论的话题,房源数据又分为新房和二手房,而二手房想对于新房来说,不管是价格还是其他方面,对于居住地不稳定的打工来来说都是一个不错的选择。那么房源数据如何去获取呢,怎样才能得到自己想要的二手房房源信息呢?
这时候我们就会使用一些办法来获取这些房源数据,比如本项目所用到的python爬虫技术就可以快速的获取到自己想要的房源数据。
项目使用的技术栈与环境
所用技术:
- Python(作为该项目实现的编程语言)
- Python.requests(主要用于获取网页中的文本信息)
- Threading(线程——为了加快爬取速率所使用的一种方法)
- Parsel(用于对获取到的网页文本数据进行解析)
平台和环境:
- Windows11
- Pycharm3.8.10
项目功能
- 核心是实现对指定网页(https://xa.lianjia.com/ershoufang)数据进行爬取,并将数据按照指定的格式存储到csv中,方便用户对数据的保存和数据的查看。
- 本项目大致分为三个部分,数据的获取,数据解析和数据的存储,使用python自带的requests库对指定网页的文本数据进行获取;对获取的文本信息使用parsel库的一些方法进行文本解析得到所需要的数据;最后解析后的数据使用CSV文件格式和MYSQL进行存储。
项目的具体实现
-
项目的前期准备:
- 首先进入指定的网址观察网页结构
- 网址首页页面
点击进入详情页
详情页中基本信息页面内容
首页和详情页之间的关联
2. 选择所需要获取到的数据内容
需要获取所指定内容,比如首页中的关注人数和发布月份,还有详情页中的总价和单价以及小区的所在位置和基本信息的获取,这些都是需要获取的文本内容。
3. 需要提前导入以下库,方便后期的函数调用和项目搭建
说明:csv用于处理CSV文件、threading用于多线程处理、datetime用于处理日期时间、ThreadPoolExecutor用于创建线程池、parsel用于解析网页内容、requests用于发送HTTP请求
前期工作已经准备就绪,下面开始具体操作部分
具体代码部分
-
首先创建一个HouseSpider类
说明:
这是一个继承自Thread类的自定义线程类。在该类中定义了初始化方法__init__()、run()方法和一些辅助方法用于获取和处理房屋数据。
run()方法:在该方法中,使用ThreadPoolExecutor创建一个具有最大工作线程数为20的线程池,并调用get_house_data()方法来获取房屋数据。
-
Clear_data()函数:用于处理格式问题
说明:
replace:replace的作用是将字符串中的指定子串替换为新的子串。可以将指定的子串替换为新的子串,也可以指定替换次数。例如,如果要将字符串中的所有'a'替换为'b',可以使用replace函数:s.replace(′a′,′b′)
strip:strip的作用是去除字符串两端的指定字符(默认为空格)。可以指定要去除的字符,也可以不指定,默认情况下会去除空格。
-
get_house_data()函数:用于获取房屋的具体信息
def get_house_data(self, url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0'}
try:
response = requests.get(url=url, headers=headers)
html_data = response.text
selectors = parsel.Selector(html_data)
href_list = selectors.css('.leftContent li .title a::attr(href)').getall()
for href in href_list:
href_res = requests.get(url=href, headers=headers)
href_selector = parsel.Selector(href_res.text)
#标题
title = href_selector.css('.title .main::text').get()
#总价
totalPrice = href_selector.css('.price .total::text').get()
#单价
unitPrice = href_selector.css('.unitPriceValue::text').get()
#关注人数和发布月份
peopleNum = selectors.css('.info .followInfo ::text').get().split('/')
guanzhurenshu = float(peopleNum[0].replace('人关注',''))
fabushijian = float(peopleNum[1].replace('个月以前发布',''))
# print(guanzhurenshu,fabushijian)
if fabushijian==1:
fabushijian = '0-30'
elif fabushijian > 1 and fabushijian <=3:
fabushijian = '30-90'
elif fabushijian > 3 and fabushijian <=6:
fabushijian = '90-180'
elif fabushijian > 6 and fabushijian <=9:
fabushijian = '180-270'
elif fabushijian > 9 and fabushijian <=12:
fabushijian = '270-365'
#小区名称
communityName = href_selector.css('.aroundInfo .communityName a::text').get()
#区名
areaName = href_selector.css('.aroundInfo .areaName a::text').get()
# base_shuxing = href_selector.css('.base .content li .label::text').getall()
#基础属性内容
li_shuxing = href_selector.css('.base .content li::text').getall()
base_data = self.clear_data(li_shuxing)
fangwuhuxing = base_data[0]
suozailouceng = base_data[1]
jianzhumianji = base_data[2]
huxingjiegou = base_data[3]
taoneimianji = base_data[4]
jianzhuleixing = base_data[5]
chaoxiang = base_data[6]
jianzhujiegou = base_data[7]
zhuangxiuqingkaung = base_data[8]
tihu = base_data[9]
gongnuan = base_data[10]
peibeidianti = base_data[11]
loucenggaodu = base_data[12]
# jiaoyi_shuxing = href_selector.css('.transaction .content li span .label::text').getall()
#交易属性内容
juti_shuxing = href_selector.css('.transaction .content li span::text').getall()
jiaoyi_data = self.clear_data(juti_shuxing)
guapai_time = jiaoyi_data[1]
jiaoyiquanshu = jiaoyi_data[3]
shangcijiaoyi_time = jiaoyi_data[5]
guapai_time_obj = datetime.strptime(guapai_time, "%Y-%m-%d")
shangcijiaoyi_time_obj = datetime.strptime(shangcijiaoyi_time, "%Y-%m-%d")
cha_time = (guapai_time_obj - shangcijiaoyi_time_obj).days
yongtu = jiaoyi_data[7]
nianxian = jiaoyi_data[9]
chanquansuoshu = jiaoyi_data[11]
diyaxinxi = jiaoyi_data[13]
fangbentiaojian = jiaoyi_data[15]
data = {
"标题": title,
"总价": totalPrice,
"单价": unitPrice,
"关注人数": guanzhurenshu,
"发布时间": fabushijian,
"小区名称": communityName,
"区名": areaName,
"挂牌时间": guapai_time,
"上次交易": shangcijiaoyi_time,
"时间差": cha_time,
"交易权属": jiaoyiquanshu,
"房屋用途": yongtu,
"房屋年限": nianxian,
"产权所属": chanquansuoshu,
"抵押信息": diyaxinxi,
"房本条件": fangbentiaojian,
"房屋户型": fangwuhuxing,
"所在楼层": suozailouceng,
"建筑面积": jianzhumianji,
'户型结构': huxingjiegou,
'套内面积': taoneimianji,
'建筑类型': jianzhuleixing,
'房屋朝向': chaoxiang,
'建筑结构': jianzhujiegou,
'装修情况': zhuangxiuqingkaung,
'梯户比例': tihu,
"供暖方式": gongnuan,
"配备电梯": peibeidianti,
"楼层高度": loucenggaodu,
}
with self.lock:
with open('二手房test.csv', mode='a', encoding='utf-8', newline='') as f:
csv_writer = csv.DictWriter(f, fieldnames=data.keys())
if f.tell() == 0:
csv_writer.writeheader()
csv_writer.writerow(data)
print(data)
except requests.exceptions.RequestException as e:
print(e)
说明:get_house_data()方法:该方法用于获取每个房屋的详细信息,包括房屋标题、总价、单价、关注人数、发布时间等,并将数据存储到一个字典中。最后,将数据写入CSV文件中。
-
文件的存储:
with open('二手房test.csv', mode='a', encoding='utf-8', newline='') as f: csv_writer = csv.DictWriter(f, fieldnames=data.keys()) if f.tell() == 0: csv_writer.writeheader() csv_writer.writerow(data) print(data)
- 主函数入口
def main(): qu_list = ['beilin', 'weiyang', 'xinchengqu', 'baqiao', 'yanliang', 'changan', 'lianhu', 'yanta', 'lantian', 'huyiqu', 'zhouzhi', 'gaoling', 'xixianxinquxian'] threads = [] for qu in qu_list: urls = [f'https://xa.lianjia.com/ershoufang/pg{i}/'+qu for i in range(1, 3)] spider = HouseSpider(urls) spider.start() threads.append(spider) for thread in threads: thread.join()
说明:main()函数:主函数中定义了一个区域列表qu_list,包含了要爬取的各个区域。这里使用了循环遍历去访问网页中的多页内容(模仿人为的点击下一页操作),然后循环遍历每个区域,生成对应的URL列表,并创建HouseSpider实例来爬取数据。
-
Main():程序的主入口
- 项目的靓点:
- 多线程:在项目中使用了多线程的方式来提高爬取的效率,提升了文件运行的整体性能和响应速度;也从一方面是程序具有并发处理的能力。
- 线程锁:使得数据同步,从而避免了资源的冲突,一定程度上确保了数据的完整性,从而提高了程序的稳定性。
- 使用了try...except..:增加了程序运行的稳定性,提高了代码的容错性
项目结果展示
项目的不足
首先这个项目结构相对完整,但是还有需要改进的地方:
- 效率问题:这里只是用了多线程的方法去提升爬虫爬取速率,提高资源的利用率,是否还有其它方法也可以做到这一点,比如可以尝试去使用异步来完成同样的工作内容,这个有待去进行对比;
- 存储方式问题:这里作者使用的存储方式是使用csv文件格式进行存储的,是否可以添加其他的文件存储方式,比如MYSQL,HDFS等
- 变量命名问题:这里的命名方式是为了作者查看起来方便,所以才这样命名,正常情况下按照大小驼峰命名法去命名即可。