news 2026/10/1 19:07:20

Python二手房房价预测全流程:爬虫、清洗、建模到可视化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python二手房房价预测全流程:爬虫、清洗、建模到可视化

简介:基于Python的深圳二手房房价预测与分析可视化项目,数据来自链家,面向计算机相关专业的毕业设计、课程设计及项目实战学习者,也适合有Python基础、希望了解数据采集、房价回归预测和数据可视化流程的中级开发者。资源共13个文件,以Python脚本、Excel数据表和Markdown文档为主,包含爬虫脚本、预测分析代码、多区域房价明细、README说明与效果图,压缩包仅654KB,结构紧凑清晰。已有236人学习下载。项目覆盖链家二手房数据抓取、多区域房价整理、特征分析与房价预测模型构建,并输出可视化效果图,可用于毕业设计展示或课程作业演示。代码经多轮测试运行成功,文档说明与效果图辅助理解,适合直接运行学习或在原基础上扩展,如增加特征变量、对比不同模型或接入实时数据。整体是完整可复用的房价数据分析课设方案,能帮助快速理解二手房价格影响因素与Python数据挖掘流程。

1. 深圳二手房房价预测:从链家真实数据到可视化,一套代码跑通全流程

做过房价预测相关课设或者毕设的人应该都有同感:大多数教程要么只讲爬虫,要么只给一份 CSV 让你自己调库,真正把「数据采集 → 清洗 → 建模 → 可视化 → 结论」串成完整闭环的很少。这套基于 Python 的深圳二手房房价预测分析及可视化资源,数据来自链家公开房源页面,覆盖南山、福田、宝安、龙岗、龙华、罗湖、盐田、光明、坪山九个行政区,自带爬虫脚本、分区原始数据、分析预测代码、运行效果图和文档说明,适合正在做毕设、课设或者想完整走一遍数据分析流程的人。它解决的核心问题是:在拿到二手房挂牌数据之后,如何用 pandas 清洗成可用样本,用回归模型预测单价,再用可视化把结论讲清楚。

2. 数据从哪来:链家爬虫脚本的采集逻辑与字段清洗

2.1 为什么选链家作为数据源

二手房数据源里,链家是课设和毕设里用得最多的一个,原因是它的挂牌字段足够规范。每个房源卡片上同时给出小区名称、所在区域、户型(室厅卫)、建筑面积、朝向、楼层、建造年份、单价(元/平米)和总价(万元),这些字段直接决定了后续建模时能构造哪些特征。

对比其他平台,链家列表页的数据结构相对稳定,字段命名也比较统一,不需要花大量时间在反爬对抗上。对于个人学习项目来说,数据稳定性比数据量更重要——你更希望在写分析代码时不被打断,而不是三天两头因为页面改版去调整解析逻辑。当然,这里说的采集只针对公开页面信息,频次控制好,仅用于课程学习或毕设研究,不要扩展到商业用途。

2.2 爬虫脚本的请求与解析思路

资源包里自带一个爬虫脚本(Climb Shenzhen second-hand housing data),核心逻辑就是构造列表页请求 → 解析 HTML 页面结构 → 提取房源字段 → 按行政区写入 Excel。整体流程大概是这样:

import time import requests from bs4 import BeautifulSoup headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "Referer": "https://sz.lianjia.com/" } def fetch_house_list(url): # 每次请求之间至少间隔 1 秒,避免对目标站点造成压力 resp = requests.get(url, headers=headers, timeout=10) resp.encoding = "utf-8" time.sleep(1.5) soup = BeautifulSoup(resp.text, "html.parser") return soup def parse_one_page(soup): items = [] # 链家列表页每个房源卡片是一个 li 标签,class 包含 clear for li in soup.select("li.clear"): title_node = li.select_one(".totalPrice span") # 总价,单位万元 if not title_node: continue title = li.select_one(".title a").get_text(strip=True) info = li.select_one(".houseInfo").get_text(strip=True).split("|") unit_price_text = li.select_one(".unitPrice span").get_text(strip=True) items.append({ "title": title, "info": info, "unit_price": unit_price_text, "total_price": title_node.get_text(strip=True), }) return items

这里有几个值得注意的参数。time.sleep(1.5)是请求间隔,课设爬虫不需要追求速度,稳定拿到数据才是第一位的;li.clear是列表页房源的卡片选择器,不同时期链家页面结构可能微调,如果跑的时候发现解析结果为空,优先检查这个选择器是否还匹配。unitPrice后面取span里的文本,拿到的是「XX 元/平」这样的原始字符串,需要后续清洗。

2.3 分区落地:为什么按行政区拆成九个 xls 文件

资源包里把数据拆成了szfj_futian.xls、szfj_baoan.xls这样九个文件,每个对应一个行政区。这种拆分方式不是随手为之,而是有实际好处的:爬虫脚本按行政区 URL 分段跑,每次只写一个文件,即使某个区跑挂,也不会影响已经保存的数据,方便断点续跑。

从后续分析的角度看,分区存储也让「区域房价对比」这件事变得非常简单——直接遍历所有 Excel 文件,用文件名里的行政区信息当作一个分组列,不需要额外维护映射关系。如果是课设答辩,这一设计也能讲清楚:数据组织方式服务于分析目标,而不是为了存数据而存数据。

3. 房价预测怎么建模:特征工程与回归模型的参数调优

3.1 从多个 Excel 到一份训练集

九个区的 xls 文件读进来之后,第一件事是合成一张总表。每个文件的路径里都带着区名,所以可以用glob遍历,再结合文件名字符串生成district字段,这样后续按区做 one-hot 编码或者分组统计都直接有据可依。

import pandas as pd import glob file_map = { "szfj_futian.xls": "福田", "szfj_baoan.xls": "宝安", "szfj_longgang.xls": "龙岗", "szfj_nanshan.xls": "南山", "szfj_longhua.xls": "龙华", "szfj_luohu.xls": "罗湖", "szfj_yantian.xls": "盐田", "szfj_guangming.xls": "光明", "szfj_pingshan.xls": "坪山", } df_list = [] for fname, district in file_map.items(): tmp = pd.read_excel(fname) tmp["district"] = district df_list.append(tmp) df = pd.concat(df_list, ignore_index=True)

实际运行前,建议先看一眼每个文件里到底有哪些列。不同区数据字段可能会有细微差异,比如有的区「朝向」列有空值,有的区「建筑年代」混进了字符串。pd.concat默认按列名对齐,列名不一致会直接生成两列,这个问题很隐蔽,经常到建模阶段才发现特征数量不对。

合并完成后做一轮基础清洗:把总价里的「万」字去掉、把单价字段的「元/平」和千分位逗号去掉、把面积里的「平米」后缀去除,全部转成数值类型。这里我一般会用astype配合字符串替换一步到位,但更稳妥的方式是pd.to_numeric(..., errors="coerce"),出错的置成缺失值,等下一轮统一处理,至少不会因为某个脏字符串直接中断整条流程。

3.2 特征选择:哪些字段真正影响深圳房价

建模阶段我用的是挂牌价里的单价(元/平)作为预测目标,而不是总价。原因是总价受面积影响太大,两套面积不同的房子总价差异可能只是由面积造成的,模型很难学到价格本身的规律;单价则更能反映「这个地段这个小区值多少钱」。

特征方面,常见的做法是保留这几个:面积、室、厅、卫(从户型文本拆分)、建筑年代、朝向(做类别编码)、所在行政区(做 one-hot)。其中「室」「厅」「卫」在原始数据里通常是「3室1厅1卫」这样的字符串,需要str.extract拆开。朝向则不需要保留全部分类,把「南北」「南」「东南」「西南」等合并成「南向」「北向」「东西向」「其他」几类就够了,类别太少会丢失信息,类别太杂则会让 one-hot 之后特征空间过大,对树模型来说性价比不高。

3.3 模型选型与参数配置

这种表格型回归任务,我的习惯是先用线性回归跑一个基线,再上随机森林,两个模型对比着看。线性回归的好处是可解释性强,答辩时可以直接说「面积每增加一平米,单价下降多少、置信区间是多少」;随机森林则能捕捉面积和单价之间的非线性关系,通常在 RMSE 和 R² 上表现更好。

from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, r2_score feature_cols = ["area", "bedrooms", "living_rooms", "bathrooms", "age"] df_ml = df.dropna(subset=feature_cols + ["unit_price"]) X = pd.get_dummies(df_ml[feature_cols + ["district", "orientation"]], drop_first=True) y = df_ml["unit_price"] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) model = RandomForestRegressor( n_estimators=300, max_depth=12, min_samples_leaf=3, n_jobs=-1, random_state=42, ) model.fit(X_train, y_train) y_pred = model.predict(X_test) print("MAE:", mean_absolute_error(y_test, y_pred)) print("R2:", r2_score(y_test, y_pred))

参数里有几个坑要提前说清楚。n_estimators我设成 300,往上加到 500 对结果提升很小,但训练时间翻倍,课设机器没必要硬扛;max_depth限制为 12,防止树在样本量不大时过度拟合训练集;random_state=42必须固定,否则每次跑出来的评测数字都不一样,答辩时前后对不上会很被动。评估指标用 MAE 比 RMSE 更直观,因为 RMSE 对高价房源的误差做了平方放大,一个 3000 万的豪宅预测偏了 500 万,RMSE 就一下子被拉爆,整个模型的真实水平反而不容易看清。

4. 可视化呈现:从热力图到分区域价格分布

4.1 先做相关性分析,再决定展示什么

可视化不是上来就画图,而是要回答具体的业务问题。我拆这套资源时,发现它的可视化思路是「先宏观、再微观」:先用相关性矩阵看哪些特征和目标变量线性相关最强,再用箱线图看不同行政区的价格中位数和离散程度,最后用散点图验证面积与单价的关系。

相关性热力图用seaborn一行就能出:

import matplotlib.pyplot as plt import seaborn as sns plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["axes.unicode_minus"] = False corr_cols = ["area", "bedrooms", "living_rooms", "bathrooms", "age", "unit_price"] corr = df_ml[corr_cols].corr() plt.figure(figsize=(8, 6)) sns.heatmap(corr, annot=True, cmap="coolwarm", fmt=".2f", linewidths=0.5) plt.title("房价相关特征热力图") plt.tight_layout() plt.savefig("corr_heatmap.png", dpi=150)

plt.rcParams["font.sans-serif"]这两行是中文显示的关键,不设置的话所有中文标签都会变成方框。annot=True会在每个格子里面标出相关系数数值,fmt=".2f"控制显示两位小数,数值太密时可以改成.1f。保存图片用savefig并指定dpi=150,这样放进论文或答辩 PPT 里不会糊。

4.2 分区域价格分布:箱线图比柱状图更有说服力

柱状图只能展示均值,但深圳各行政区的房价分布差距巨大——福田、南山和高价豪宅集中区域会拉高均值,而龙岗、坪山有大量低价房源,均值无法反映真实分布。箱线图能把中位数、四分位距、异常值一次性展示出来,是区域房价对比里最合适的图形。

plt.figure(figsize=(12, 6)) order = df_ml.groupby("district")["unit_price"].median().sort_values(ascending=False).index sns.boxplot(data=df_ml, x="district", y="unit_price", order=order) plt.xticks(rotation=30) plt.title("深圳各行政区二手房单价分布(元/平)") plt.ylabel("单价(元/平)") plt.tight_layout() plt.savefig("district_boxplot.png", dpi=150)

这里有一个容易被忽略的细节:order参数传的是按中位数排序后的区名序列,这样画出来的箱线图从左到右价格递减,一眼就能看出梯度。不排序的话,箱线图按字母顺序排列,南山和盐田这些名字排在一起,信息传递效率低很多。rotation=30是因为区名横着排会重叠,旋转后更干净。

5. 避坑指南:房价预测项目最常见的五个翻车现场

5.1 单价字段里混杂着「暂无」和千分位逗号

第一个坑在数据清洗阶段就会遇到。链家列表页的单价字段有时候是「暂无数据」,有时候是「86,500元/平」,直接astype(float)会当场报错。原因很简单:字符串里既带了逗号又带了汉字,pandas 无法自动推断。解决方式是先正则提取数字部分:

unit_series = df["unit_price"].astype(str) df["unit_price_num"] = ( unit_series.str.extract(r"([\d,]+)")[0] .str.replace(",", "", regex=False) .astype(float) )

str.extract的作用是从字符串里扣出第一段连续的数字和逗号,replace(",", "")把千分位去掉,最后转 float。「暂无」这类文本提取出来是 NaN,后续统一 drop 掉就可以了。

5.2 「万」和「元/平」藏在字符串只能截取到一半

总价字段是「680万」这样的格式,有人习惯用str.strip("万")去处理,但如果某一行是「680万(含车位)」就会翻车。更稳的写法是用str.replace("万", "")之后再astype(float),这样不会误删字符串末尾的其他内容。血泪经验是:爬虫数据里什么意外都有,尽量做「精确替换」而不是「截断」。

5.3 面积和楼龄的缺失值不能一删了之

原始数据里楼龄字段缺失比例很高,有些区域甚至超过 20%。如果直接dropna(),会连同大量有效样本一起丢掉,导致训练集偏向有完整信息的房源。我一般用中位数填充,同时新增一个age_missing标志列,告诉模型「这个样本的楼龄是填出来的」。树模型能利用这个标志列自动调整分支策略,效果比单纯填充好得多。

5.4 豪宅样本把误差指标拉爆

深圳总价 3000 万以上的房源虽然数量少,但单位价格波动极大,预测误差动辄每平两三万。MAE 和 RMSE 都会被这些样本拉高,导致模型看起来「整体很差」。这里有两种处理方式:把单价超过 12 万的样本单独拿出来做一份分析,或者干脆在训练时先过滤掉极端值,在文档里说明适用上限。做课设时这种取舍要写进文档说明里,答辩老师问起来能讲清楚,而不是被发现后手忙脚乱。

5.5 中文可视化字体变成方框

macOS 和 Linux 上尤其容易出现:plt.rcParams["font.sans-serif"] = ["SimHei"]设置了中文字体,但系统里根本没有 SimHei,照样一堆方框。解决方式是先确认系统有哪些中文字体,Windows 一般是 SimHei,macOS 可以用PingFang SC,Linux 常见Noto Sans CJK SC。最不济的办法是用matplotlib.font_manager手动指定字体文件路径,但这属于下策,尽量通过fc-list :lang=zh先查一遍系统已有的字体再配置。

6. 进阶技巧:把预测模型封装成「迷你估价器」

前面做的都是训练和评估,但对课设和毕设来说,一个能实际调用的预测函数会让整套东西完整很多。思路很简单:训练完随机森林后,用joblib把模型和特征列名一起保存,再写一个函数,输入面积、户型、行政区和楼龄,直接输出预测单价。这样答辩现场可以现场演示「输入 89 平三房、南山、楼龄 8 年,模型给出预测价」,比扔出一堆图表更打动人。

import joblib import pandas as pd FEATURE_COLS = model.feature_names_in_.tolist() def predict_price(area, bedrooms, living_rooms, bathrooms, age, district, orientation): raw = { "area": area, "bedrooms": bedrooms, "living_rooms": living_rooms, "bathrooms": bathrooms, "age": age, "district": district, "orientation": orientation, } # 把输入映射成 one-hot 后的特征向量,缺失列补 0 input_df = pd.DataFrame([raw]) for col in FEATURE_COLS: if col not in input_df.columns: input_df[col] = 0 result = model.predict(input_df[FEATURE_COLS])[0] return round(result, 2) # 示例:南山 89 平三房,楼龄 8 年,朝南 print(predict_price(89, 3, 1, 1, 8, "南山", "南向"))

这里的关键坑是FEATURE_COLS。训练时用了pd.get_dummies(..., drop_first=True),测试时输入的一行记录必须经过相同的编码转换,否则特征顺序对不上。用model.feature_names_in_拿到训练时的列名列表,再在输入行里把缺失的列手动补 0,是最笨但最稳的做法。我第一次写这个函数时偷懒直接拼接编码后的数组,结果字段顺序错位,预测结果完全是玄学。

从那以后,我每次做这类项目都强制走一遍「训练时保存特征列名 + 推理时按列名对齐」的流程,再小的 demo 也不例外。模型的精度可以慢慢调,但接口必须先稳下来。希望这套深圳二手房数据分析和预测资源能帮你在毕设或者课设里少走一段弯路,把时间花在真正有价值的地方。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 19:07:10

AI Agent 架构分层实战:MCP、A2A 与 Agent Skills 协作指南

1. 从一堆协议名词说起:Agent 生态到底在分层什么过去一年里,只要你在做 AI Agent 相关的东西,几乎不可能绕开三个词:MCP、A2A、Agent Skills。我第一次同时看到这三个概念摆在一起的时候,脑子里第一反应是——这不就是…

作者头像 李华
网站建设 2026/10/1 19:05:38

MiMo-V2.6全面解析:双版本选择、AA指数与部署实战

MiMo-V2.6 的发布消息,这两天在开源模型圈子里讨论度确实不低。这次小米一口气放出 Pro 和 Flash 两个版本,价格维持原样,同时在 AA 指数上的排名超过了 Kimi K3 和 GLM-5.3,直接成为开源阵营里排名最高的模型。这个信息量其实挺大…

作者头像 李华
网站建设 2026/10/1 19:05:19

全卷积网络FCN实战:语义分割数据集制作与PyTorch训练避坑指南

简介:图像分割是计算机视觉的核心任务,其中语义分割要求对每个像素进行类别预测,是自动驾驶、医学影像等场景的基础技术。全卷积网络(FCN)通过将分类网络的全连接层替换为卷积层,实现了端到端的像素级分类&…

作者头像 李华
网站建设 2026/10/1 19:05:17

Python遗传算法标定VISSIM跟驰参数实战

简介:本资源为基于Python遗传算法实现VISSIM模型标定的完整设计源码,面向交通工程、交通仿真方向的学习者与研究人员,用于解决微观交通模型中参数繁多、人工标定效率低且难以获得全局最优配置的问题。压缩包共23个文件、约443KB,涵…

作者头像 李华
网站建设 2026/10/1 19:05:16

科幻实验室内景漫游全流程:建模、材质、光照与交互实现

做科幻实验室的内景漫游,算是我这几年碰过最"既要又要"的项目:既要场景细节经得起近距离特写,又要保证漫游时帧率不掉链子;既要科技感拉满,又不能堆得像夜市招牌那么俗气。最近刚完成一套完整的内景科幻实验…

作者头像 李华