
简介基于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, headersheaders, timeout10) 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(stripTrue) info li.select_one(.houseInfo).get_text(stripTrue).split(|) unit_price_text li.select_one(.unitPrice span).get_text(stripTrue) items.append({ title: title, info: info, unit_price: unit_price_text, total_price: title_node.get_text(stripTrue), }) 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_indexTrue)实际运行前建议先看一眼每个文件里到底有哪些列。不同区数据字段可能会有细微差异比如有的区「朝向」列有空值有的区「建筑年代」混进了字符串。pd.concat默认按列名对齐列名不一致会直接生成两列这个问题很隐蔽经常到建模阶段才发现特征数量不对。合并完成后做一轮基础清洗把总价里的「万」字去掉、把单价字段的「元/平」和千分位逗号去掉、把面积里的「平米」后缀去除全部转成数值类型。这里我一般会用astype配合字符串替换一步到位但更稳妥的方式是pd.to_numeric(..., errorscoerce)出错的置成缺失值等下一轮统一处理至少不会因为某个脏字符串直接中断整条流程。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(subsetfeature_cols [unit_price]) X pd.get_dummies(df_ml[feature_cols [district, orientation]], drop_firstTrue) y df_ml[unit_price] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) model RandomForestRegressor( n_estimators300, max_depth12, min_samples_leaf3, n_jobs-1, random_state42, ) 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_state42必须固定否则每次跑出来的评测数字都不一样答辩时前后对不上会很被动。评估指标用 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, annotTrue, cmapcoolwarm, fmt.2f, linewidths0.5) plt.title(房价相关特征热力图) plt.tight_layout() plt.savefig(corr_heatmap.png, dpi150)plt.rcParams[font.sans-serif]这两行是中文显示的关键不设置的话所有中文标签都会变成方框。annotTrue会在每个格子里面标出相关系数数值fmt.2f控制显示两位小数数值太密时可以改成.1f。保存图片用savefig并指定dpi150这样放进论文或答辩 PPT 里不会糊。4.2 分区域价格分布箱线图比柱状图更有说服力柱状图只能展示均值但深圳各行政区的房价分布差距巨大——福田、南山和高价豪宅集中区域会拉高均值而龙岗、坪山有大量低价房源均值无法反映真实分布。箱线图能把中位数、四分位距、异常值一次性展示出来是区域房价对比里最合适的图形。plt.figure(figsize(12, 6)) order df_ml.groupby(district)[unit_price].median().sort_values(ascendingFalse).index sns.boxplot(datadf_ml, xdistrict, yunit_price, orderorder) plt.xticks(rotation30) plt.title(深圳各行政区二手房单价分布元/平) plt.ylabel(单价元/平) plt.tight_layout() plt.savefig(district_boxplot.png, dpi150)这里有一个容易被忽略的细节order参数传的是按中位数排序后的区名序列这样画出来的箱线图从左到右价格递减一眼就能看出梯度。不排序的话箱线图按字母顺序排列南山和盐田这些名字排在一起信息传递效率低很多。rotation30是因为区名横着排会重叠旋转后更干净。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(,, , regexFalse) .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 一般是 SimHeimacOS 可以用PingFang SCLinux 常见Noto Sans CJK SC。最不济的办法是用matplotlib.font_manager手动指定字体文件路径但这属于下策尽量通过fc-list :langzh先查一遍系统已有的字体再配置。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_firstTrue)测试时输入的一行记录必须经过相同的编码转换否则特征顺序对不上。用model.feature_names_in_拿到训练时的列名列表再在输入行里把缺失的列手动补 0是最笨但最稳的做法。我第一次写这个函数时偷懒直接拼接编码后的数组结果字段顺序错位预测结果完全是玄学。从那以后我每次做这类项目都强制走一遍「训练时保存特征列名 推理时按列名对齐」的流程再小的 demo 也不例外。模型的精度可以慢慢调但接口必须先稳下来。希望这套深圳二手房数据分析和预测资源能帮你在毕设或者课设里少走一段弯路把时间花在真正有价值的地方。本文还有配套的精品资源点击获取