基于python的二手房数据爬取与分析
本次项目对58同城的上海、广州、重庆、杭州和郑州五个城市在售二手房信息进行分析,实施路线为:在58同城网站获取基础信息、使用python爬虫进行数据采集、数据清洗、数据可视化及分析.
数据可视化
数据可视化是将数据以图形或图表的形式呈现出来,以便更直观地理解和分析数据。它将复杂的数据转化为易于理解的视觉元素,帮助人们发现数据中的模式、趋势和关系。常见的数据可视化形式包括柱状图、折线图、饼图、散点图、热力图等。这些图表可以根据数据的特点和需求进行选择和定制,以最好地展示数据的信息。数据可视化的过程通常包括以下几个步骤:
1、在爬虫的基础上选择可视化工具和技术:根据数据的特点和需求选择合适的可视化工具和技术,如 Excel、Python 中的 Matplotlib、Seaborn 等。
2、设计可视化布局和样式:根据数据的内容和目的设计可视化的布局和样式,使图表更具吸引力和可读性。
3、绘制可视化图表:使用选择的可视化工具和技术绘制可视化图表,将数据以图形的形式呈现出来。
4、解释和分析可视化结果:对绘制好的可视化图表进行解释和分析,发现数据中的模式、趋势和关系,并做出相应的决策。
数据获取
3.1.1 分析网页
在爬取 58 同城的二手房源信息之前,我们需要对目标网页进行详细的分析。这有助于我们确定需要提取的数据和数据所在的位置以及如何解析网页内容。
网页节点信息如下:
抓取网页源码
以58同城为例,分析如何爬取二手房源信息。首先找到58同城的URL,其中“{k}”表示城市,“{i}”表示页码,通过改变城市和页码就可以获取到不同城市的所有二手房源信息。
Python通过Requests库的requests.get()向网页发送请求,请求通过后,使用lxml库的etree.HTML()方法将网页内容解析为HTML文档对象。然后,通过Xpath表达式定位到需要提取信息的节点,并使用相应的方法提取出所需的信息,如房屋地址、名称、房间数目、朝向、其他信息、总价、每平方的价钱和建造年份等。
在提取信息时,我们需要需要处理 一些异常情况,如节点信息存在部分为空,这可能导致我们提起到数据不完整或不准确,也可能或造成提取过程中出现报错。此外,数据格式不一致也是一个常见的问题,不同的房源信息可能采用不同的格式进行描述,我们需要对这些格式进行统一处理,以确保数据的一致性和可用性。并且,我们还需要设置合适的请求头headers,其中包括Cookie和User-Agent,模拟正常用户访问行为,减少被网站识别为爬虫的风险。另外还要设置合理的延迟时间,避免过于频繁地发送请求,给网站服务器造成过大的压力。这些设置可以让我们能在爬取数据的过程种避免被网站封禁或者限制访问,也可以保证数据的准确性。
数据爬取结果如下:
数据总量:7787条
数据存储
在存储文件时,我们选择了CSV模块。CSV是一种简单的文本文件格式,每行表示一条记录,各个字段之间用逗号分割,CSV文件数据交换方便,还可以使用Excle方式查看。CSV过程如下。
1、写入数据:通过创建csv.writer对象,然后使用writer.writerow()方法逐行写入数据。
2、读取数据:创建csv.reader对象,通过迭代reader可以逐行读取数据,并进行相应处理。
3、处理特殊字符:如果数据中包含逗号等可能影响格式的特殊字符,需要进行适当处理,确保数据的完整性。
4、表头:通过创建csv.DictWriter(),我们可以方便的定义CSV文件的表头信息。
存储代码如下:数据存储结果:
数据获取
通过data = pd.read_csv('二手房信息.csv', header=0, sep=',')我们成功的从指定的“二手房信息.csv”文件种读取数据。其中“header=0”表示将文件的第一行作为表头,明确了各列数据的属性含义,而“sep=','”则指定了数据的分隔符为逗号。
数据清洗
描述性统计分析
通过执行print(data.describe())这一操作,我们能够获取到关于数据集的关键统计描述信息。这一输出为我们呈现了数据的计数、均值、标准差、最小值、最大值以及不同分位数等重要统计指标。这些指标对于全面理解和把握二手房信息数据集的特征和分布情况具有至关重要的作用。它们可以帮助我们快速了解数据的集中趋势、离散程度以及分布范围等关键特性,为进一步深入分析数据、挖掘潜在规律以及做出合理决策提供了有力的数据支撑。
Count:样本数量;mean:均值;std标准:标准差;min:最小值;max:最大值;25%:表示将数据从小到大排序后,处于前25%位置的数据值。它反映了有25%的数据小于或等于这个值,这可以让我们了解到数据分布中较低部分的一个典型值。;50%:中位数;75%:表示处于前75%位置的数据值,有75%的数据小于或等于这个值,它能反映数据分布中较高部分的大致情况。
重复值检测
我们运用data.duplicated()方法来检测数据集中是否存在重复观测。通过print('数据集中是否存在重复观测:\n', any(data.duplicated()))语句,我们能够明确地得知数据集中是否存在重复情况。接着,使data.duplicated().sum()可以准确地统计出重复值的数量,这为我们全面了解数据的重复性特征提供了直观的数据支持。
为了更清晰地查看具体的重复值,我们借助print(data[data.duplicated()])打印出了重复值的详细信息,为了确保数据的准确性和简洁性,我们采用了data.drop_duplicates(inplace=True)方法来删除当前的重复项。最后通过print(data)展示出处理后的数据集,从而获得了一个去重后的高质量数据集。
删除重复值后的数据:
缺失值检测
运用isnull()函数来判断变量值是否为空值,如果有空值,则返回True,否则返回False,这一操作有助于我们快速全面了解数据的完整性。 数据保存
在完成了一系列的数据处理和分析步骤后,我们通过运用data.to_csv('二手房信息(处理后).csv', index=False)这一操作实现了数据的保存。
数据可视化
为了深入探究不同地区二手房的特征,我们对清洗后的数据进行了细分。通过data[data['地址'] == '上海二手房']等语句,分别提取出上海、广州、重庆、杭州和郑州的二手房数据子集,即data_sh、data_gz、data_cq、data_hz 和 data_zz。随后打印这些子集,以初步观察其具体情况。接着我们对数据中的“面积”列进行了转换处理。利用data['面积'].str.replace('㎡', '').astype(float)将其转换为浮点数类型,为后续的分析奠定了基础。
基于Matplotlib的数据可视化分析
分析不同城市房价总价的特点
15:不同城市房价总价箱线图
分析:由图可以看出超一线城市【上海】的平均房价最高,其次时新一线城市【杭州】。新一线城市【郑州】和【重庆】的平均房价差不多,而且数据的波动也较小。一线城市【广州】的房价数据波动最大。
5.1.2 分析不同城市房价单价的特点
分析房屋面积与房价的特点
分析:从图中清晰可见,住房面积与总价之间呈现出显著的正相关关系。这表明,一般情况下,住房面积越大,其总价往往也会越高。并且值得注意的是,随着面积的不断增大,二手房房价上升的幅度相当可观。这可能意味着,在住房市场中,面积因素对房价有着重要且较为直接的影响。较大面积的住房可能在市场上具有更高的价值,并且这种价值的提升随着面积的增加而表现得更为突出。这种现象或许反映了消费者对于更大居住空间的需求和偏好,以及较大面积住房所可能附带的更多功能和优势。同时,也反映出市场对于住房资源的一种定价规律,即面积越大,其在总价上的体现越明显,上升的幅度越高。
源码:
import requests from lxml import etree import csv import time csvfile = open("二手房信息.csv", "w", encoding="utf-8", newline="", ) file = csv.DictWriter(csvfile, fieldnames=['地址', '房屋名', '房间数目', '面积', '朝向', '其他', '总价', '每平方的价钱', '建造年份']) file.writeheader() for i in range(1, 30, 1): for k in ['sh', 'zz', 'gz', 'cq', 'hz']: url = f'https://{k}.58.com/ershoufang/p{i}/?PGTID=0d30000c-0015-6380-001d-4d19ee0ed4dd&ClickID=1' headers = { 'Cookie': 'f=n; f=n; commontopbar_new_city_info=3%7C%E5%B9%BF%E5%B7%9E%7Cgz; myLat=""; myLon=""; ' 'id58=61OTVWZyTbVMple1R8BFSQ==; mcity=cq; 58tj_uuid=cdd7097f-096a-41ab-b9b9-93f6325f50bc; new_uv=1; ' 'utm_source=market; spm=u-2d2yxv86y3v43nkddh1.BDPCPZ_BT; ' 'init_refer=https%253A%252F%252Fwww.baidu.com%252Fother.php%253Fsc.000000jPDQBGTF0l55FzTFDiv9' '-1C8rWaEogXvX-hRFCjC5iVzWvWBNnigUS_HTCEnUQi4pJb39cUpWMm05xSL6lNkF-M8gZAR' '-3tGKj5rm4JaiuAn5OWFxTZsuDs4ZQwj91upcd_1IdU9yID68gvU6OuCsGU4cGZsvDBmL8dkW_L3xDp74jb8tIlvZD8CtYl6JRqDI5MY838A6z5fWisDyXyzji.DY_NR2Ar5Od66z3PrrW6ButVvkDj3n-vHwYxw_vU85YIMAQV8qhORGyAp7WIu8L6.TLFWgv-b5HDkrfK1ThPGujYknHb0THY0IAYqPHWPoQ5Z0ZN1ugFxIZ-suHYs0A7bgLw4TARqnsKLULFb5HR31pz1ksKzmLmqn0KdThkxpyfqnHRdPWmzP1D4P0KVINqGujYkPH63nHn1PfKVgv-b5HD1njmLP1Dk0AdYTAkxpyfqnHc3nWm0TZuxpyfqn0KGuAnqHbC0TA-b5Hn10APGujYYPjb0mLFW5HDYrjbY%2526ck%253D5406.1.103.261.157.260.150.26; als=0; sessionid=44f3e020-808a-4bd2-a3b3-1a31a101a777; fzq_h=32e5c78efcde74eab113a42bb058c04e_1718767045718_9b9a7de231b14e0f88e27ad8ede06b8e_47896431291917374806487980456114946949; new_session=0; wmda_uuid=c0482b60f900f4cc465bd321b37c09e8; wmda_new_uuid=1; wmda_session_id_1731916484865=1718767047178-06392013-e8ac-2946; qz_gdt=; xxzl_cid=27c2a330fd4d4726a39af39bf1e47a45; f=n; commontopbar_new_city_info=3%7C%E5%B9%BF%E5%B7%9E%7Cgz; 58home=gz; commontopbar_ipcity=cq%7C%E9%87%8D%E5%BA%86%7C0; JSESSIONID=8D7B5357F0DF186D7A8A5DD88F1FF65E; city=bj; xxzlclientid=4ff4e7e8-3812-4aea-916f-1718767107702; xxzlxxid=pfmxmR074SsO0/4r9O15OydChPCXpvZsGge/nKwJXdh2bYb4a7SP4m9078IqfmKtC9qX; xxzlbbid=pfmbM3wxMDI3M3wxLjcuMHwxNzE4NzY3MTA4NDMxfE9VbTZnYU9TcEZCbWhoMGlWZDBqQ2hLLzBKU1B3YjNlZHBzN0ZjQ1pkc1k9fDVkMGU0ZTkwZmRjMTRiM2UzZjBlNDViYTRkZmZhNDM5XzE3MTg3NjcxMDY1MzlfNzdhYjQ2NTcyYmE5NGQyZjgwMGQxZDRlZmU3ZTVlMTVfMjA3MzI5Mzk4M3w3ODVkZjU5OWFkMDM4ODU2YzRjN2JiNzQxYzcwZjE4Yl8xNzE4NzY3MTA3MTAxXzI1Ng==; xxzl_deviceid=XMX1T0%2FBzdegWhEjrxZjqQeCk248UK1dFKCSh3A7ANa2yWPzdqVaoLaeYvBaP5ub; wmda_session_id_2385390625025=1718767114824-8ff3bcbe-13c1-bd4e; wmda_visited_projects=%3B1731916484865%3B2385390625025; aQQ_ajkguid=06DF61B5-5827-4488-91D4-867A04A918D2; sessid=B540481B-010D-4A48-A087-1CCF74FAD65E; ajk-appVersion=; ctid=3; xxzl_cid=27c2a330fd4d4726a39af39bf1e47a45; xxzl_deviceid=XMX1T0/BzdegWhEjrxZjqQeCk248UK1dFKCSh3A7ANa2yWPzdqVaoLaeYvBaP5ub', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/126.0.0.0 Safari/537.36' } response = requests.get(url=url, headers=headers) time.sleep(2) host_list = etree.HTML(response.text) host = host_list.xpath('/html/body/div[1]/div/div/section/section[3]/section[1]/section[2]/div[*]') for j in host: address = j.xpath('//*[@id="esfMain"]/section/section[1]/div[4]/div/a[3]/text()')[0].strip() name = j.xpath('a/div[2]/div[1]/div[1]/h3/text()')[0].strip() rooms = j.xpath('a/div[2]/div[1]/section/div[1]/p[1]/span/text()') roominfo = ''.join(rooms) area = j.xpath('a/div[2]/div[1]/section/div[1]/p[2]/text()')[0].strip() fang_xiang = j.xpath('a/div[2]/div[1]/section/div[1]/p[3]/text()')[0].strip() qi_ta = j.xpath('a/div[2]/div[1]/section/div[3]/span/text()')[0].strip() total_price = j.xpath('a/div[2]/div[2]/p[1]/span[1]/text()')[0].strip() price = j.xpath('a/div[2]/div[2]/p[2]/text()')[0].split('元/㎡')[0].strip() jian_zao_time = j.xpath('a/div[2]/div[1]/section/div[1]/p[5]/text()') for m, a, b, c, d, e, h, l, g in zip(address, name, roominfo, area, fang_xiang, qi_ta, total_price, price, jian_zao_time): print(address, name, roominfo, area, fang_xiang, qi_ta, total_price, price, jian_zao_time) # print(len(host)) data = { '地址': address, '房屋名': name, '房间数目': rooms, '面积': area, '朝向': fang_xiang, '其他': qi_ta, '总价': total_price, '每平方的价钱': price, '建造年份': jian_zao_time, } file.writerow(data)
数据清洗:
import pandas as pd import matplotlib.pyplot as plt data = pd.read_csv('二手房信息.csv', header=0, sep=',') data.duplicated() print('数据集中是否存在重复观测:\n', any(data.duplicated())) data.duplicated().sum() print(data[data.duplicated()]) data.drop_duplicates(inplace=True) print(data) print(data.isnull()) print(data.describe()) data_sh = data[data['地址'] == '上海二手房'] data_gz = data[data['地址'] == '广州二手房'] data_cq = data[data['地址'] == '重庆二手房'] data_hz = data[data['地址'] == '杭州二手房'] data_zz = data[data['地址'] == '郑州二手房'] print(data_sh, data_gz, data_cq, data_hz, data_zz) data.drop('建造年份', axis=1, inplace=True) # 定义一个函数来清洗数据 def clean_data(cell): if isinstance(cell, str) and cell.startswith("['") and cell.endswith("']"): # 去除括号和单引号,并将逗号分隔的部分连接成一个字符串 return ''.join(cell[2:-2].split("', '")) else: return cell # 应用清洗函数到指定列 data['房间数目'] = data['房间数目'].apply(clean_data) print(data['房间数目']) houseFace = { '东': 1, '西': 2, '南': 3, '北': 4, '东西': 5, '西北': 6, '东北': 7, '西南': 8, '东南': 9, '南北': 10 } data['朝向'] = data['朝向'].map(houseFace) data['面积'] = data['面积'].str.replace('㎡', '').astype(float) data.to_csv('二手房信息(处理后).csv', index=False)
可视化:
plt.rcParams['font.sans-serif'] = ['SimHei'] # 绘制五个地区的房价箱线图造 data.boxplot(column='总价', by='地址') plt.title('不同城市房价总价箱线图') plt.xlabel('城市') plt.ylabel('总价/万元') plt.show() data.boxplot(column='每平方的价钱', by='地址') plt.title('不同城市房价单价箱线图') plt.xlabel('城市') plt.ylabel('单价/元') plt.show()
作者:【鹿桓】