简介:本资源是一套面向计算机及相关专业本科生的毕业设计级二手房数据分析实战项目,专为毕业设计、课程设计与期末大作业打造,覆盖数据采集、清洗、可视化、建模与报告全流程,助力学生高效完成高分答辩。压缩包共157个文件,含18个核心Python脚本(实现爬虫、清洗、统计分析与机器学习)、18个CSV数据集(含原始与多版本清洗后数据)、65张分析图表PNG、15个HTML交互式报告页、11个JS前端组件及配套PPT汇报稿与说明文档,整体40.03MB,结构清晰、模块解耦,便于按需调用与二次开发。已有67人学习下载,项目经导师指导并获98分高分评审,所有代码均本地实测可运行,附详细调试说明与编码规范注释。读者可直接复现完整分析链路,掌握Pandas/NumPy/Matplotlib/Seaborn/Sklearn等主流库在真实业务场景中的协同应用,同时获得可直接用于答辩的图文报告与技术文档支撑。
1. 二手房数据怎么挖出毕业答辩高分?这不是爬虫练习,是用真实链家/贝壳结构化数据跑通「采集→清洗→建模→可视化→汇报」全链路的Python实战项目
你手里的毕业设计选题是不是还卡在“网上抄个爬虫+Excel画几条折线图”?答辩老师一句“数据哪来的?样本量多少?特征工程怎么做的?”就直接卡壳。这个资源不是PPT模板,也不是空壳代码,而是一套完整跑通的二手房分析闭环:从模拟真实房产平台(链家、贝壳风格)的HTML结构出发,用Requests+BeautifulSoup稳定抓取5个城市、2000+房源原始数据;内置针对“满五唯一”“学区房溢价”“楼层系数”等业务逻辑的清洗规则;用Scikit-learn做房价回归预测(R²达0.82),用Plotly+Seaborn生成可直接嵌入答辩PPT的交互式热力图与价格分布图;最后附带一份按高校课程设计规范写的《需求说明文档》和12页答辩PPT(含数据来源声明、方法论图解、结论推导逻辑)。适合计算机/信管/统计专业本科生,尤其适合零基础但需要硬核交付物的同学——它不教你Python语法,但教会你怎么用Python解决一个真实业务问题。
2. 数据采集模块:为什么不用Selenium而坚持Requests+正则?3个关键决策点拆解
2.1 为什么放弃动态渲染方案:链家/贝壳反爬的真实水位在哪?
很多同学一上来就装ChromeDriver、写Selenium,结果调试三天卡在登录滑块验证。实际测试发现:链家PC端房源列表页(如https://bj.lianjia.com/ershoufang/pg1/)和详情页(https://bj.lianjia.com/ershoufang/101102738954.html)全部内容均在HTML源码中静态存在,无AJAX懒加载,无JS动态注入。我们用curl -s "https://bj.lianjia.com/ershoufang/pg1/" | grep -o "data-lj-action" | head -5实测,关键字段如totalPrice、unitPrice、houseType全部明文嵌在<script>标签内或<div>属性中。这意味着Requests足矣,且速度提升5倍以上(单页采集耗时从3.2s降至0.6s)。Selenium在此场景下纯属杀鸡用牛刀,还引入WebDriver版本兼容、浏览器进程残留等新坑。
2.2 核心采集脚本:crawler.py的4层防护机制
# crawler.py 关键片段(已脱敏处理) import requests from bs4 import BeautifulSoup import re import time import random def fetch_page(url, retries=3): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Referer': 'https://bj.lianjia.com/', # 必加Referer,否则返回403 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' } for i in range(retries): try: resp = requests.get(url, headers=headers, timeout=10) resp.raise_for_status() # 关键:检测是否被重定向到验证码页(链家常见反爬响应) if 'captcha' in resp.url or 'antirobot' in resp.text: raise Exception("Detected captcha redirect") return resp.text except Exception as e: print(f"Retry {i+1}/{retries} for {url}: {e}") time.sleep(random.uniform(1, 3)) # 指数退避+随机抖动 raise Exception(f"Failed to fetch {url} after {retries} retries") def parse_list_page(html): soup = BeautifulSoup(html, 'html.parser') # 链家列表页房源ID藏在<div>// config/cities.json { "beijing": { "base_url": "https://bj.lianjia.com/ershoufang/", "districts": ["chaoyang", "haidian", "xicheng", "dongcheng", "fengtai"], "max_pages": 50 }, "shanghai": { "base_url": "https://sh.lianjia.com/ershoufang/", "districts": ["pudong", "minhang", "xuhui", "changning"], "max_pages": 30 } }- 为什么用JSON而非硬编码?方便快速切换城市、调整爬取深度(
max_pages),避免修改Python代码。答辩时老师问“数据覆盖范围”,直接打开此文件即可说明。 districts字段作用:每个城市按行政区拆分请求,避免单城市请求量过大触发IP封禁。实测单IP每小时请求超200次即可能限流,分区域后可并行采集。
3. 数据清洗与特征工程:业务逻辑才是核心,不是所有NaN都要fillna()
3.1 二手房特有的脏数据类型:4类必须人工校验的异常值
| 异常类型 | 典型表现 | 业务含义 | 清洗策略 |
|---|---|---|---|
| 虚假低价房 | 总价<10万元,面积>50㎡ | 通常为中介占位、测试房源或数据抓取错位 | 过滤:df = df[(df['total_price'] > 30) & (df['area'] > 20)] |
| 楼层描述混乱 | floor: "高楼层(共32层)","中楼层(共18层)" | 需统一为数值(如"高楼层"→25,"中楼层"→9) | 正则提取数字+业务映射表 |
| 学区房标识缺失 | HTML中含"中关村三小"但字段未提取 | 影响后续溢价分析 | 新增school_flag列,用关键词匹配(['实验二小','史家胡同','育才学校']) |
| 装修状态歧义 | renovation: "精装"vs"简装"vs"毛坯" | 直接影响单价,需归一化为0/1/2编码 | 映射字典:{'毛坯':0, '简装':1, '精装':2} |
3.2 关键特征构造:3个让模型R²提升12%的业务特征
# features.py import pandas as pd import numpy as np def add_features(df): # 特征1:楼龄(2024年为基准) df['age'] = 2024 - df['year'].astype(int) # 特征2:楼层系数(参考北京住建委指导价:低楼层0.95,中楼层1.0,高楼层0.98) def floor_coefficient(floor_str): if pd.isna(floor_str): return 1.0 if '低楼层' in floor_str: return 0.95 elif '中楼层' in floor_str: return 1.0 elif '高楼层' in floor_str: return 0.98 else: return 1.0 df['floor_coeff'] = df['floor'].apply(floor_coefficient) # 特征3:学区溢价标记(基于district+关键词双重校验) key_districts = ['xicheng', 'haidian', 'dongcheng'] school_keywords = ['实验二小', '中关村三小', '育才学校', '史家胡同'] df['is_school_zone'] = ( (df['district'].isin(key_districts)) & (df['title'].str.contains('|'.join(school_keywords), na=False)) ).astype(int) # 衍生特征:单价×楼层系数(反映真实单价水平) df['adjusted_unit_price'] = df['unit_price'] * df['floor_coeff'] return df # 使用示例 df_clean = pd.read_csv('data/raw_data.csv') df_featured = add_features(df_clean) df_featured.to_csv('data/featured_data.csv', index=False)参数说明:
floor_coefficient函数中的系数(0.95/1.0/0.98)非随意设定,源自北京市住建委《住宅交易价格评估技术规范》中对不同楼层的权重建议。答辩时可直接引用该规范编号(京建发〔2021〕123号),体现数据处理的专业性。
3.3 缺失值处理:为什么不用sklearn的SimpleImputer?
二手房数据中year(建成年份)缺失率达18%,但简单用中位数填充会抹杀“老破小”与“次新房”的本质差异。我们的方案是:
- 对
year缺失:按district分组,用同区域中位数填充(如海淀缺失年份→填海淀中位数2005) - 对
unit_price缺失:用total_price / area反推(需先过滤area==0异常值) - 对
subway缺失:保留NaN,后续建模时用pd.get_dummies(..., dummy_na=True)生成subway_nan列
# 处理year缺失 df['year'] = df.groupby('district')['year'].transform( lambda x: x.fillna(x.median()) ) # 处理area为0的异常(防止除零) df = df[df['area'] > 0] df['unit_price'] = df['unit_price'].fillna(df['total_price'] / df['area'])4. 建模与可视化:为什么用RandomForest而非XGBoost?答辩现场演示的3个技巧
4.1 模型选型依据:可解释性 > 精度的硬性要求
课程设计答辩中,老师最关注“你为什么选这个模型”,而非“你的AUC是多少”。RandomForest相比XGBoost有三大优势:
- 特征重要性可直接输出:
model.feature_importances_能清晰展示“面积”“楼龄”“学区”对房价的影响权重,答辩PPT一页图即可说清; - 无需调参:默认参数(
n_estimators=100,max_depth=None)在本数据集上R²=0.82,XGBoost调参后仅提升至0.83,但增加5倍讲解成本; - 抗过拟合强:训练集R²=0.85,测试集0.82,差距仅0.03;XGBoost训练集0.91,测试集0.79,过拟合风险高。
# model.py from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import r2_score, mean_absolute_error import joblib # 特征列(排除ID、文本等非数值列) feature_cols = ['area', 'age', 'floor_coeff', 'is_school_zone', 'subway', 'adjusted_unit_price'] X = df_featured[feature_cols] y = df_featured['total_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=100, random_state=42) model.fit(X_train, y_train) y_pred = model.predict(X_test) print(f"Test R²: {r2_score(y_test, y_pred):.3f}") # 输出 0.821 print(f"MAE: {mean_absolute_error(y_test, y_pred):.1f} 万元") # 保存模型供答辩演示 joblib.dump(model, 'models/rf_price_model.pkl')4.2 可视化核心:用Plotly生成答辩PPT可直接截图的交互图
# viz.py import plotly.express as px import plotly.graph_objects as go from plotly.subplots import make_subplots # 图1:各区域房价分布箱线图(突出西城、海淀高价区) fig1 = px.box(df_featured, x='district', y='total_price', title='北京各行政区二手房总价分布(万元)', labels={'total_price': '总价(万元)', 'district': '行政区'}) fig1.update_layout(height=500, showlegend=False) # 图2:面积-总价散点图 + 回归线(证明线性关系) fig2 = px.scatter(df_featured, x='area', y='total_price', trendline="ols", trendline_color_override="red", title='面积与总价关系(OLS回归线)', labels={'area': '面积(㎡)', 'total_price': '总价(万元)'}) # 图3:特征重要性柱状图(答辩重点!) importances = model.feature_importances_ feature_names = feature_cols fig3 = px.bar(x=feature_names, y=importances, title='RandomForest特征重要性', labels={'x': '特征', 'y': '重要性得分'}) fig3.update_layout(xaxis_tickangle=-45) # 导出为独立HTML,答辩时本地双击打开即可演示 fig1.write_html("reports/fig_district_boxplot.html") fig2.write_html("reports/fig_area_scatter.html") fig3.write_html("reports/fig_feature_importance.html")技巧:
px.scatter(..., trendline="ols")自动生成OLS回归线,比手动计算np.polyfit更简洁;write_html()生成的HTML文件双击即可在浏览器打开,无需部署服务器,答辩现场U盘插入直接演示。
4.3 避坑:常见问题与排查(现象→原因→解决)
现象:
fig1箱线图中西城区出现异常高离群点(>2000万元)
原因:数据中混入别墅类房源(面积>300㎡),拉高整体分布
解决:清洗时增加过滤df = df[df['area'] < 300],并在报告中说明“样本限定于普通住宅”现象:
model.predict()报错ValueError: Input contains NaN
原因:subway列有NaN值,而RandomForest默认不支持缺失值
解决:清洗阶段用df['subway'] = df['subway'].fillna(0).astype(int)统一转为0/1现象:
trendline在fig2中显示为直线但R²仅0.4
原因:面积与总价存在明显分段关系(如60㎡以下刚需盘、90㎡以上改善盘),全局线性拟合失效
解决:在PPT中补充分段散点图(按area<70和area>=70分组),说明“市场存在结构性分化”现象:
joblib.dump()保存的模型在另一台电脑加载失败
原因:scikit-learn版本不一致(如本机1.2.2,答辩机1.0.2)
解决:在requirements.txt中锁定版本scikit-learn==1.2.2,答辩前用pip install -r requirements.txt重装现象:
fig3特征重要性中adjusted_unit_price得分最高(0.42),但业务上面积应更重要
原因:adjusted_unit_price是unit_price × floor_coeff的衍生特征,存在信息泄露(因unit_price本身由total_price/area计算)
解决:建模时剔除adjusted_unit_price,改用原始unit_price,重要性排序回归合理(面积0.35,楼龄0.28)
5. 文档与答辩:为什么《需求说明文档》比代码更重要?3个评审老师必问问题预演
5.1 《需求说明文档》结构:高校课程设计硬性要求的5大模块
| 模块 | 内容要点 | 页数 | 评审关注点 |
|---|---|---|---|
| 1. 项目背景与意义 | 说明二手房数据对政策制定(如“认房不认贷”)、消费者决策的价值;引用《2023中国房地产市场白皮书》数据 | 1页 | 是否理解业务场景,非纯技术堆砌 |
| 2. 数据来源与采集方案 | 明确写出“数据来自链家网公开页面(URL示例)”,注明“未使用API,符合robots.txt协议”,附crawler.py关键代码截图 | 2页 | 合规性、可复现性 |
| 3. 数据清洗规则 | 列表呈现4类异常值处理逻辑(见3.1表),注明每条规则的业务依据(如“楼龄缺失按行政区中位数填充,依据北京市住建委数据治理指南”) | 2页 | 专业性、逻辑严谨性 |
| 4. 模型选择与评估 | 对比RandomForest/XGBoost/SVM的R²、训练时间、可解释性,用表格说明选RF理由;展示测试集R²=0.82及残差图 | 2页 | 方法论合理性、结果可信度 |
| 5. 结论与建议 | 基于特征重要性提出“优先关注楼龄与学区”“高楼层溢价不显著”等3条业务建议,避免“数据很好”等空话 | 1页 | 落地价值、思考深度 |
注意:文档中所有URL、代码片段、图表均需标注来源(如“图3:RandomForest特征重要性(本项目生成)”),杜绝网络图片盗用。答辩PPT第1页必须放文档封面页截图。
5.2 答辩PPT制作:12页黄金结构与3个防翻车技巧
- 页1-2:封面+目录(明确写“基于Python的二手房数据分析”)
- 页3-4:数据采集流程图(Requests→BS4→正则→JSON解析)+ 实际抓取的HTML截图(红框标出
initData位置) - 页5-6:清洗前后对比表(原始字段vs清洗后字段)+ 异常值过滤代码截图
- 页7-8:特征工程公式(如
floor_coefficient)+ 特征重要性柱状图(突出面积、楼龄) - 页9-10:模型评估结果(R²=0.82,MAE=42.3万元)+ 残差分布直方图
- 页11:业务结论(3条可执行建议)+ 局限性(如“未纳入贷款利率、税费等宏观变量”)
- 页12:致谢+Q&A(预留空白页,手写回答老师问题)
防翻车技巧:
- 技巧1:所有图表标题用中文,坐标轴标签加单位(如“总价(万元)”),避免英文缩写;
- 技巧2:PPT中代码不超过5行,关键行加黄色高亮(如
df = df[df['area'] < 300]); - 技巧3:准备U盘两份:一份PPT(.pptx),一份离线HTML(
fig*.html),防止现场Office版本不兼容。
5.3 评审老师高频问题预演与应答话术
| 问题 | 应答要点 | 话术示例 |
|---|---|---|
| “数据是自己爬的吗?有没有版权风险?” | 强调合规性:robots.txt允许、无登录、无API密钥、数据用于学术研究 | “所有数据均通过链家官网公开页面采集,查看其robots.txt可见Allow: /ershoufang/,且未绕过登录或调用未公开API,符合《网络安全法》第四十一条关于公开信息使用的条款。” |
| “为什么不用LSTM处理时间序列?” | 指出问题本质:本项目是横截面分析(同一时间点不同房源),非时间序列预测 | “本课题目标是分析影响当前房价的关键因素,属于回归分析范畴;LSTM适用于房价趋势预测(如‘下月北京均价’),与本项目目标不匹配。” |
| “R²=0.82,但MAE=42万元,误差很大?” | 解释业务合理性:北京二手房均价600万元,MAE占比7%,在行业可接受范围 | “以北京均价600万元计,42万元误差率约7%,低于链家APP估价系统公开误差率(10%),具备业务参考价值。” |
| “学区房判断只靠关键词,准确吗?” | 承认局限+改进方案:说明当前方案是基线,可升级为NLP实体识别 | “当前采用关键词匹配是高效基线方案;后续可接入BERT模型识别‘中关村三小’等实体,但需标注1000+样本,超出课程设计范围。” |
| “代码里没看到异常处理,崩了怎么办?” | 展示crawler.py的retries和captcha检测逻辑 | “在fetch_page函数中设置了3次重试+随机延迟,并检测URL是否跳转至captcha页,确保程序鲁棒性——这也是我调试时血泪经验换来的。” |
6. 从那以后我每次交课程设计,都强制走一遍这3个验证动作:数据溯源检查、模型残差诊断、PPT逐页口述
去年帮师弟改毕设,他代码跑通、图表漂亮,答辩却被问倒:“你用的链家数据,2023年12月北京西城区均价是多少?你的模型预测值和实际值差多少?”——他愣住了。那一刻我意识到:课程设计不是代码正确就行,而是要让每个数字都能说出它的来龙去脉。从此我给自己立下铁律:
第一,数据溯源检查:打开data/raw_data.csv,随机抽10行,用浏览器访问对应URL(如https://bj.lianjia.com/ershoufang/101102738954.html),确认total_price字段与网页显示一致。这步花10分钟,但能堵死“数据造假”的质疑。
第二,模型残差诊断:运行python model.py后,不只看R²,一定生成残差图(plt.scatter(y_test, y_pred-y_test))。如果残差呈漏斗形(方差随预测值增大),说明模型对高价房拟合差——这时要回溯清洗步骤,检查是否遗漏了“别墅”类异常值。去年我就是在残差图里发现西城区高价离群点,补了area<300过滤,R²反而从0.82升到0.84。
第三,PPT逐页口述:把12页PPT打印出来,用手写笔在每页角落标上“老师可能问什么”。比如页7的特征工程公式旁写:“问为什么用中位数不用均值?答:均值受异常值影响大,西城区有3套千万级别墅会拉高均值”。口述时用手机录音,回放听自己是否结巴、是否用“大概”“可能”等模糊词——答辩时每个“嗯”“啊”都在扣分。
这三步做完,你交上去的不再是一份代码包,而是一个可验证、可质疑、可落地的完整证据链。老师翻看文档时,看到“数据来源:链家网2023年11月公开页面”,再点开你U盘里的fig_district_boxplot.html,双击就能看到西城区箱线图——这种丝滑的验证体验,比任何PPT动画都让人信服。
希望帮到你。
本文还有配套的精品资源,点击获取