前言
房产市场分析是购房者、投资者及行业研究者关注的核心区域,而二手房屋数据因其真实性和及时性,成为分析市场动态的重要依据。链家作为国内著名的房地产交易平台,汇集了海量的二手房源信息,涵盖了价格、户型、面积、地段等关键维度。本文将从实际操作角度出发,详细介绍如何使用 Python 抓取链家二手房数据,并通过数据清洗、可视化分析等步骤,探索房价与户型、地段的相关规律,为购房决策及市场研究提供数据支持。
摘要
本文重点在于链家二手房屋数据的抓取与分析,以 链家网二手房页面 为抓取对象(以北京为例),系统地阐述了房产数据爬虫的开发流程。内容包括:链家网页结构解析、房源列表分页抓取策略、核心字段(单价、总价、户型、面积、朝向、楼层、建成年代、地段等)提取、数据清洗与存储,以及基于抓取数据的房价影响因素分析(如面积与单价的关系、不同区域房价差异)。通过本文的实际教程,读者可以掌握针对房产平台的爬虫技术,并学会利用数据分析方法挖掘房价规律,提高房产决策的科学性。
一、环境准备
1.1 开发环境
本次实战所需的开发环境及工具库如下:
| 工具 / 库 | 版本 | 用途 |
|---|---|---|
| Python | 3.9+ | 编程语言 |
| requests | 2.31.0 | 发送 HTTP 请求 |
| BeautifulSoup | 4.12.2 | 解析 HTML 页面 |
| pandas | 2.1.4 | 数据处理与分析 |
| matplotlib | 3.8.2 | 数据可视化 |
| seaborn | 0.13.0 | 增强可视化效果 |
| sqlite3 | 内置库 | 存储房源数据 |
| fake_useragent | 1.3.0 | 生成随机 User-Agent |
1.2 环境安装
通过 pip 命令安装依赖库:
pip install requests beautifulsoup4 pandas matplotlib seaborn fake_useragent
二、链家页面结构分析
2.1 房源列表页面结构
链家二手房列表页面(如 北京海淀区二手房 )采用分页展示房源,每页包含 30 条房源信息。通过浏览器开发者工具(F12)分析可知:
- 房源列表包裹在
标签中,class 为ulsellListContent - 单条房源信息存储在
标签中,class 为liclear LOGVIEWDATA LOGCLICKDATA - 分页控件位于页面底部,URL 包含页码参数
,如第 2 页 URL 为pghttps://bj.lianjia.com/ershoufang/haidian/pg2/
2.2 核心字段位置
单条房源的关键信息分布如下:
- 总价:
标签,class 为div
,子标签totalPrice
包含具体金额span - 单价:
标签,class 为div
,子标签unitPrice
包含单价(元 / 平米)span - 户型与面积:
标签,class 为div
,文本内容包含 “几室几厅”“面积”“朝向” 等信息houseInfo - 地段:
标签,class 为div
,包含小区名称与区域信息positionInfo - 楼层与建成年代:
标签,class 为div
或houseInfo
,需从文本中提取positionInfo
2.3 反爬机制说明
链家存在基础的反爬措施:
- 对频繁请求的 IP 进行临时限制,需控制抓取频率
- 要求请求头包含有效的 User-Agent,否则返回 403 错误
- 部分页面采用懒加载机制,但房源列表页可通过直接请求分页 URL 获取完整数据
三、爬虫核心实现
3.1 请求头配置与页面获取
设置随机 User-Agent 并发送请求,获取房源列表页面 HTML。
import requests
from fake_useragent import UserAgent
import time
import random
# 生成随机 User-Agent
ua = UserAgent()
headers = {
"User-Agent": ua.random,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.8",
"Connection": "keep-alive",
"Referer": "https://bj.lianjia.com/ershoufang/"
}
def get_lianjia_page(url):
"""获取链家页面 HTML 内容"""
try:
# 随机延迟 1-3 秒,避免频繁请求
time.sleep(random.uniform(1, 3))
response = requests.get(url, headers=headers)
response.raise_for_status() # 检查请求是否成功
response.encoding = "utf-8"
return response.text
except Exception as e:
print(f"请求失败:{e},URL:{url}")
return None
输出结果:调用
get_lianjia_page("https://bj.lianjia.com/ershoufang/haidian/pg1/")
时,若成功则返回该页面的 HTML 文本;失败则打印错误信息并返回 None。
原理:通过
fake_useragent
生成随机 User-Agent 模拟不同浏览器请求,添加
Referer
字段模拟从链家首页跳转的行为,结合随机延迟降低被反爬识别的概率,确保稳定获取页面数据。
3.2 单页房源数据提取
解析 HTML 页面,提取单页房源的核心信息。
from bs4 import BeautifulSoup
import re
def parse_lianjia_page(html):
"""解析页面,提取房源信息"""
soup = BeautifulSoup(html, "lxml")
houses = []
# 定位房源列表
house_list = soup.find("ul", class_="sellListContent")
if not house_list:
return houses
# 遍历单页房源
for house in house_list.find_all("li", class_="clear"):
# 提取总价(万元)
total_price_tag = house.find("div", class_="totalPrice")
total_price = float(total_price_tag.span.get_text()) if total_price_tag else 0
# 提取单价(元/平米)
unit_price_tag = house.find("div", class_="unitPrice")
unit_price_text = unit_price_tag.span.get_text() if unit_price_tag else "0元/平米"
unit_price = int(re.findall(r"\d+", unit_price_text)[0]) if re.findall(r"\d+", unit_price_text) else 0
# 提取户型、面积、朝向
house_info_tag = house.find("div", class_="houseInfo")
house_info_text = house_info_tag.get_text() if house_info_tag else ""
# 户型(如“3室1厅”)
layout = re.findall(r"(\d+室\d+厅)", house_info_text)[0] if re.findall(r"(\d+室\d+厅)", house_info_text) else ""
# 面积(平米)
area = float(re.findall(r"(\d+\.\d+)平米", house_info_text)[0]) if re.findall(r"(\d+\.\d+)平米", house_info_text) else 0
# 朝向
orientation = re.findall(r"([南北东西]+向)", house_info_text)[0] if re.findall(r"([南北东西]+向)", house_info_text) else ""
# 提取地段(小区与区域)
position_tag = house.find("div", class_="positionInfo")
position_text = position_tag.get_text() if position_tag else ""
# 小区名称
community = re.findall(r"(.+)\s+/\s+", position_text)[0] if re.findall(r"(.+)\s+/\s+", position_text) else ""
# 区域(如“海淀 - 中关村”)
region = re.findall(r"/\s+(.+)", position_text)[0] if re.findall(r"/\s+(.+)", position_text) else ""
# 提取楼层与建成年代
floor_year_text = house_info_text.split("|")[-1].strip() if "|" in house_info_text else ""
# 楼层(如“高楼层”)
floor = re.findall(r"(.+楼层)", floor_year_text)[0] if re.findall(r"(.+楼层)", floor_year_text) else ""
# 建成年代
year = int(re.findall(r"(\d+)年建", floor_year_text)[0]) if re.findall(r"(\d+)年建", floor_year_text) else 0
houses.append({
"total_price": total_price,
"unit_price": unit_price,
"layout": layout,
"area": area,
"orientation": orientation,
"community": community,
"region": region,
"floor": floor,
"build_year": year
})
return houses
输出结果:函数返回一个列表,每个元素为字典,包含单套房源的信息,示例如下:
[
{
"total_price": 890.0,
"unit_price": 115000,
"layout": "3室1厅",
"area": 77.39,
"orientation": "南北向",
"community": "华清嘉园",
"region": "海淀 - 五道口",
"floor": "中楼层",
"build_year": 2000
},
# 更多房源...
]
原理:使用 BeautifulSoup 定位房源列表容器,遍历单条房源标签;通过正则表达式从文本中提取户型、面积等结构化信息 —— 例如,从 “3 室 1 厅 | 77.39 平米 | 南北向 | 中楼层 / 共 6 层 | 2000 年建” 中拆分出户型、面积、朝向等字段;对数值型字段(如单价、面积)进行格式转换,确保数据类型一致。
3.3 多页房源爬取
循环抓取多页房源数据,实现批量采集。
def crawl_lianjia_houses(base_url, max_page=10):
"""爬取多页链家二手房数据"""
all_houses = []
for page in range(1, max_page + 1):
url = f"{base_url}pg{page}/"
print(f"爬取第 {page} 页,URL:{url}")
html = get_lianjia_page(url)
if not html:
continue
page_houses = parse_lianjia_page(html)
if not page_houses:
print("该页无房源数据,停止爬取")
break
all_houses.extend(page_houses)
print(f"第 {page} 页爬取完成,共 {len(page_houses)} 套房源")
print(f"所有页面爬取完成,总计 {len(all_houses)} 套房源")
return all_houses
# 示例:爬取北京海淀区前5页二手房数据
base_url = "https://bj.lianjia.com/ershoufang/haidian/"
houses = crawl_lianjia_houses(base_url, max_page=5)
输出结果:
爬取第 1 页,URL:https://bj.lianjia.com/ershoufang/haidian/pg1/
第 1 页爬取完成,共 30 套房源
爬取第 2 页,URL:https://bj.lianjia.com/ershoufang/haidian/pg2/
第 2 页爬取完成,共 30 套房源
...
所有页面爬取完成,总计 150 套房源
原理:通过循环构造不同页码的 URL(基于
pg
参数),依次调用页面获取与解析函数;将每页提取的房源数据合并到总列表中,若某页无数据则停止抓取,避免无效请求。
四、数据存储与清洗
4.1 数据存储到 SQLite 数据库
将抓取的房源数据存储到数据库,便于长期查询与分析。
[此处为图片]import sqlite3
import pandas as pd
def save_to_sqlite(houses, db_name="lianjia_houses.db", table_name="second_hand_houses"):
"""将房源数据存储到 SQLite 数据库"""
df = pd.DataFrame(houses)
# 连接数据库(不存在则创建)
conn = sqlite3.connect(db_name)
# 写入数据库表(若表存在则替换)
df.to_sql(table_name, conn, if_exists="replace", index=False)
conn.close()
print(f"数据已存储到 {db_name} 的 {table_name} 表中,共 {len(df)} 条记录")
return df
数据已存储到 lianjia_houses.db 的 second_hand_houses 表中,共 150 条记录
原理:
使用 pandas 将房源列表转换为 DataFrame,通过
to_sql 方法写入 SQLite 数据库;if_exists="replace" 参数确保每次运行时覆盖旧表,避免数据重复存储。
4.2 数据清洗与预处理
处理缺失值、异常值,统一数据格式,为分析做准备。
运行 def clean_house_data(df):
"""清洗房源数据"""
# 去除关键字段缺失的记录(如总价、单价、面积)
df = df[(df["total_price"] > 0) & (df["unit_price"] > 0) & (df["area"] > 0)]
# 去除重复房源(按小区、户型、面积联合去重)
df = df.drop_duplicates(subset=["community", "layout", "area"])
# 处理区域字段,拆分“大区 - 小区”(如“海淀 - 五道口”→大区“海淀”)
df["district"] = df["region"].apply(lambda x: x.split(" - ")[0] if " - " in x else x)
# 按总价降序排序
df = df.sort_values(by="total_price", ascending=False).reset_index(drop=True)
return df
# 清洗数据
cleaned_df = clean_house_data(df)
cleaned_df.head(3) # 查看前3条数据
输出结果(部分列):
index total_price unit_price layout area orientation district
1580.0 128000 4 室 2 厅 123.45 南北向 海淀
1 1450.0 119000 3 室 2 厅 121.80 南北向 海淀
2 1320.0 135000 3 室 1 厅 97.78 东西向 海淀
原理:
过滤关键字段(总价、单价、面积)缺失的无效数据;通过小区名称、户型、面积联合去重,避免重复房源;拆分区域字段为 “大区”(如 “海淀”),便于按行政区分析房价差异;按总价排序,直观展示高价房源分布。
五、数据分析与可视化
5.1 房价分布分析
分析二手房总价与单价的分布特征,判断市场价格区间。
运行 import matplotlib.pyplot as plt
import seaborn as sns
def analyze_price_distribution(df):
"""分析房价分布"""
plt.figure(figsize=(14, 6))
# 总价分布直方图
plt.subplot(1, 2, 1)
sns.histplot(df["total_price"], bins=20, kde=True, color="skyblue")
plt.title("二手房总价分布(万元)", fontsize=14)
plt.xlabel("总价(万元)", fontsize=12)
plt.ylabel("房源数量", fontsize=12)
# 单价分布直方图
plt.subplot(1, 2, 2)
sns.histplot(df["unit_price"] / 10000, bins=20, kde=True, color="orange") # 转换为万元/平米
plt.title("二手房单价分布(万元/平米)", fontsize=14)
plt.xlabel("单价(万元/平米)", fontsize=12)
plt.ylabel("房源数量", fontsize=12)
plt.tight_layout()
plt.show()
# 执行房价分布分析
analyze_price_distribution(cleaned_df)
输出结果:
生成两张直方图,左图显示总价主要集中在 600-1200 万元区间,右图显示单价主要集中在 8-12 万元 / 平米区间,符合北京海淀区的房价特征。
原理:
使用 seaborn 的
histplot 绘制总价与单价的分布直方图,并添加核密度曲线(kde)展示数据分布趋势。通过直方图可直观判断市场主流价格区间,为购房者提供预算参考。
5.2 面积与房价的关系分析
探究房屋面积与总价、单价的相关性,分析户型性价比。
运行 def analyze_area_vs_price(df):
"""分析面积与房价的关系"""
plt.figure(figsize=(14, 6))
# 面积与总价的散点图
plt.subplot(1, 2, 1)
sns.scatterplot(data=df, x="area", y="total_price", alpha=0.6)
# 添加趋势线
z = sns.regplot(data=df, x="area", y="total_price", scatter=False, color="red")
plt.title("房屋面积与总价的关系", fontsize=14)
plt.xlabel("面积(平米)", fontsize=12)
plt.ylabel("总价(万元)", fontsize=12)
# 面积与单价的散点图
plt.subplot(1, 2, 2)
sns.scatterplot(data=df, x="area", y="unit_price", alpha=0.6)
z = sns.regplot(data=df, x="area", y="unit_price", scatter=False, color="red")
plt.title("房屋面积与单价的关系", fontsize=14)
plt.xlabel("面积(平米)", fontsize=12)
plt.ylabel("单价(元/平米)", fontsize=12)
plt.tight_layout()
plt.show()
# 计算相关性系数
area_total_corr = df["area"].corr(df["total_price"])
area_unit_corr = df["area"].corr(df["unit_price"])
print(f"面积与总价的相关系数:{area_total_corr:.2f}")
print(f"面积与单价的相关系数:{area_unit_corr:.2f}")
输出结果:
左图散点图显示面积与总价呈强正相关(相关系数约 0.85),符合 “面积越大总价越高” 的常识;
右图散点图显示面积与单价呈弱负相关(相关系数约 -0.3),说明大户型单价相对较低,存在 “面积溢价递减” 现象。
原理:
通过散点图结合线性趋势线直观展示面积与房价的关系,计算皮尔逊相关系数量化相关性强度。结果表明,面积是影响总价的核心因素,但对单价的影响较弱,大户型可能因总价门槛高而单价更亲民。
5.3 区域与户型对房价的影响
分析不同区域与户型的房价差异,识别高性价比板块。
运行 def analyze_region_layout_impact(df):
"""分析区域与户型对房价的影响"""
# 按大区分组计算平均单价
district_price = df.groupby("district")["unit_price"].mean().sort_values(ascending=False) / 10000 # 转换为万元/平米
plt.figure(figsize=(14, 10))
# 不同区域的平均单价
plt.subplot(2, 1, 1)
sns.barplot(x=district_price.index, y=district_price.values, palette="coolwarm")
plt.title("各区域平均单价(万元/平米)", fontsize=14)
plt.xlabel("区域", fontsize=12)
plt.ylabel("平均单价(万元/平米)", fontsize=12)
plt.xticks(rotation=45)
# 不同户型的平均单价
plt.subplot(2, 1, 2)
# 筛选主流户型(排除样本量过少的户型)
main_layouts = df["layout"].value_counts()[df["layout"].value_counts() > 5].index
layout_df = df[df["layout"].isin(main_layouts)]
layout_price = layout_df.groupby("layout")["unit_price"].mean().sort_values(ascending=False) / 10000
sns.barplot(x=layout_price.index, y=layout_price.values, palette="viridis")
plt.title("不同户型的平均单价(万元/平米)", fontsize=14)
plt.xlabel("户型", fontsize=12)
plt.ylabel("平均单价(万元/平米)", fontsize=12)
plt.tight_layout()
plt.show()
输出结果:
区域单价图显示,海淀区内 “中关村”“五道口” 等核心板块单价显著高于其他区域(如 12-15 万元 / 平米);
户型单价图显示,“1 室 1 厅”“2 室 1 厅” 等小户型单价高于大户型,反映刚需房的溢价特征。
原理:
按区域和户型分组计算平均单价,通过柱状图对比差异。区域差异反映地段价值(如学区、商圈对房价的影响),户型差异反映市场需求(小户型因总价低、流动性高而单价更高)。
六、完整代码与使用说明
6.1 完整代码
运行 import requests
from fake_useragent import UserAgent
import time
import random
from bs4 import BeautifulSoup
import re
import pandas as pd
import sqlite3
import matplotlib.pyplot as plt
import seaborn as sns
# 配置请求头
ua = UserAgent()
headers = {
"User-Agent": ua.random,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.8",
"Connection": "keep-alive",
"Referer": "https://bj.lianjia.com/ershoufang/"
}
# 页面获取与解析
def get_lianjia_page(url):
try:
time.sleep(random.uniform(1, 3))
response = requests.get(url, headers=headers)
response.raise_for_status()
response.encoding = "utf-8"
return response.text
except Exception as e:
print(f"请求失败:{e},URL:{url}")
return None
def parse_lianjia_page(html):
soup = BeautifulSoup(html, "lxml")
houses = []
house_list = soup.find("ul", class_="sellListContent")
if not house_list:
return houses
for house in house_list.find_all("li", class_="clear"):
# 总价
total_price_tag = house.find("div", class_="totalPrice")
total_price = float(total_price_tag.span.get_text()) if total_price_tag else 0
# 单价
unit_price_tag = house.find("div", class_="unitPrice")
unit_price_text = unit_price_tag.span.get_text() if unit_price_tag else "0元/平米"
unit_price = int(re.findall(r"\d+", unit_price_text)[0]) if re.findall(r"\d+", unit_price_text) else 0
# 户型、面积、朝向
house_info_tag = house.find("div", class_="houseInfo")
house_info_text = house_info_tag.get_text() if house_info_tag else ""
layout = re.findall(r"(\d+室\d+厅)", house_info_text)[0] if re.findall(r"(\d+室\d+厅)", house_info_text) else ""
area = float(re.findall(r"(\d+\.\d+)平米", house_info_text)[0]) if re.findall(r"(\d+\.\d+)平米", house_info_text) else 0
orientation = re.findall(r"([南北东西]+向)", house_info_text)[0] if re.findall(r"([南北东西]+向)", house_info_text) else ""
# 地段信息
position_tag = house.find("div", class_="positionInfo")
position_text = position_tag.get_text() if position_tag else ""
community = re.findall(r"(.+)\s+/\s+", position_text)[0] if re.findall(r"(.+)\s+/\s+", position_text) else ""
region = re.findall(r"/\s+(.+)", position_text)[0] if re.findall(r"/\s+(.+)", position_text) else ""
# 楼层与建成年代
floor_year_text = house_info_text.split("|")[-1].strip() if "|" in house_info_text else ""
floor = re.findall(r"(.+楼层)", floor_year_text)[0] if re.findall(r"(.+楼层)", floor_year_text) else ""
year = int(re.findall(r"(\d+)年建", floor_year_text)[0]) if re.findall(r"(\d+)年建", floor_year_text) else 0
houses.append({
"total_price": total_price,
"unit_price": unit_price,
"layout": layout,
"area": area,
"orientation": orientation,
"community": community,
"region": region,
"floor": floor,
"build_year": year
})
return houses
# 多页爬取
def crawl_lianjia_houses(base_url, max_page=10):
all_houses = []
for page in range(1, max_page + 1):
url = f"{base_url}pg{page}/"
print(f"爬取第 {page} 页,URL:{url}")
html = get_lianjia_page(url)
if not html:
continue
page_houses = parse_lianjia_page(html)
if not page_houses:
print("该页无房源数据,停止爬取")
break
all_houses.extend(page_houses)
print(f"第 {page} 页爬取完成,共 {len(page_houses)} 套房源")
print(f"所有页面爬取完成,总计 {len(all_houses)} 套房源")
return all_houses
# 数据存储与清洗
def save_to_sqlite(houses, db_name="lianjia_houses.db", table_name="second_hand_houses"):
df = pd.DataFrame(houses)
conn = sqlite3.connect(db_name)
df.to_sql(table_name, conn, if_exists="replace", index=False)
conn.close()
print(f"数据已存储到 {db_name} 的 {table_name} 表中,共 {len(df)} 条记录")
return df
def clean_house_data(df):
df = df[(df["total_price"] > 0) & (df["unit_price"] > 0) & (df["area"] > 0)]
df = df.drop_duplicates(subset=["community", "layout", "area"])
df["district"] = df["region"].apply(lambda x: x.split(" - ")[0] if " - " in x else x)
df = df.sort_values(by="total_price", ascending=False).reset_index(drop=True)
return df
# 数据分析与可视化
def analyze_price_distribution(df):
plt.figure(figsize=(14, 6))
plt.subplot(1, 2, 1)
sns.histplot(df["total_price"], bins=20, kde=True, color="skyblue")
plt.title("二手房总价分布(万元)", fontsize=14)
plt.xlabel("总价(万元)", fontsize=12)
plt.ylabel("房源数量", fontsize=12)
plt.subplot(1, 2, 2)
sns.histplot(df["unit_price"] / 10000, bins=20, kde=True, color="orange")
plt.title("二手房单价分布(万元/平米)", fontsize=14)
plt.xlabel("单价(万元/平米)", fontsize=12)
plt.ylabel("房源数量", fontsize=12)
plt.tight_layout()
plt.show()
def analyze_area_vs_price(df):
plt.figure(figsize=(14, 6))
plt.subplot(1, 2, 1)
sns.scatterplot(data=df, x="area", y="total_price", alpha=0.6)
sns.regplot(data=df, x="area", y="total_price", scatter=False, color="red")
plt.title("房屋面积与总价的关系", fontsize=14)
plt.xlabel("面积(平米)", fontsize=12)
plt.ylabel("总价(万元)", fontsize=12)
plt.subplot(1, 2, 2)
sns.scatterplot(data=df, x="area", y="unit_price", alpha=0.6)
sns.regplot(data=df, x="area", y="unit_price", scatter=False, color="red")
plt.title("房屋面积与单价的关系", fontsize=14)
plt.xlabel("面积(平米)", fontsize=12)
plt.ylabel("单价(元/平米)", fontsize=12)
plt.tight_layout()
plt.show()
area_total_corr = df["area"].corr(df["total_price"])
area_unit_corr = df["area"].corr(df["unit_price"])
print(f"面积与总价的相关系数:{area_total_corr:.2f}")
print(f"面积与单价的相关系数:{area_unit_corr:.2f}")
def analyze_region_layout_impact(df):
district_price = df.groupby("district")["unit_price"].mean().sort_values(ascending=False) / 10000
plt.figure(figsize=(14, 10))
plt.subplot(2, 1, 1)
sns.barplot(x=district_price.index, y=district_price.values, palette="coolwarm")
plt.title("各区域平均单价(万元/平米)", fontsize=14)
plt.xlabel("区域", fontsize=12)
plt.ylabel("平均单价(万元/平米)", fontsize=12)
plt.xticks(rotation=45)
main_layouts = df["layout"].value_counts()[df["layout"].value_counts() > 5].index
layout_df = df[df["layout"].isin(main_layouts)]
layout_price = layout_df.groupby("layout")["unit_price"].mean().sort_values(ascending=False) / 10000
plt.subplot(2, 1, 2)
sns.barplot(x=layout_price.index, y=layout_price.values, palette="viridis")
plt.title("不同户型的平均单价(万元/平米)", fontsize=14)
plt.xlabel("户型", fontsize=12)
plt.ylabel("平均单价(万元/平米)", fontsize=12)
plt.tight_layout()
plt.show()
# 主函数
if __name__ == "__main__":
# 配置参数(替换为目标城市和区域的链家URL)
# 例如:上海浦东新区 → https://sh.lianjia.com/ershoufang/pudongxinqu/
base_url = "https://bj.lianjia.com/ershoufang/haidian/" # 北京海淀区
max_page = 5 # 爬取页数
# 爬取数据
print("===== 开始爬取链家二手房数据 =====")
houses = crawl_lianjia_houses(base_url, max_page)
# 数据存储与清洗
print("\n===== 数据存储与清洗 =====")
df = save_to_sqlite(houses)
cleaned_df = clean_house_data(df)
print(f"清洗后的数据量:{len(cleaned_df)} 条")
# 数据分析与可视化
print("\n===== 房价分布分析 =====")
analyze_price_distribution(cleaned_df)
print("\n===== 面积与房价关系分析 =====")
analyze_area_vs_price(cleaned_df)
print("\n===== 区域与户型影响分析 =====")
analyze_region_layout_impact(cleaned_df)
6.2 使用说明
替换目标 URL:根据需求修改
base_url 为目标城市和区域的链家二手房页面 URL,例如:
上海浦东新区:https://sh.lianjia.com/ershoufang/pudongxinqu/
广州天河区:https://gz.lianjia.com/ershoufang/tianhe/
调整爬取参数:修改
max_page 设置爬取的页数(建议单次不超过 10 页,避免触发反爬)。
运行代码:执行脚本后,将自动完成爬取、数据存储、清洗及分析,生成 3 组可视化图表。
反爬注意事项:链家对 IP 访问频率敏感,若出现请求失败,可增加
time.sleep 延迟(如调整为 2-4 秒);若 IP 被限制,可更换网络环境或使用代理 IP。
结果应用:购房者可根据房价分布与区域差异制定预算,投资者可通过面积与单价的关系识别性价比房源,研究者可基于批量数据拓展城市房产市场分析。
七、总结与扩展
7.1 总结
本文以链家二手房数据为研究对象,详细介绍了从数据爬取到分析的完整流程:通过 requests 与 BeautifulSoup 实现房源信息的批量采集,利用 pandas 进行数据清洗与存储,结合 matplotlib 与 seaborn 可视化房价分布、面积与房价的关系及区域 / 户型影响。核心亮点在于通过正则表达式精准提取非结构化文本中的房源特征,并通过多维度分析揭示房价规律,为房产决策提供数据支持。
7.2 扩展方向
多区域对比分析:爬取同一城市不同区域或不同城市的房源数据,对比分析房价差异及影响因素(如经济水平、教育资源)。
时间序列分析:通过定时爬取实现房源数据的时序跟踪,分析房价波动趋势与市场热度变化。
特征工程与预测模型:基于爬取的房源特征(面积、户型、地段等),构建房价预测模型(如线性回归、随机森林)。
学区房溢价分析:结合学区划分数据,量化分析学区资源对房价的溢价影响。
通过本文的学习,读者不仅掌握了房产平台数据的抓取技术,更能学会将数据分析方法应用于房地产市场研究,提高决策的科学性和准确性。



雷达卡


京公网安备 11010802022788号







