简介一份基于Python机器学习的二手房交易预测及可视化系统毕业设计项目面向计算机相关专业准备毕设的学生和需要项目实战练习的初学者可实现二手房价格预测与数据可视化代码完整可直接运行配套答辩PPT也可用于课程设计或期末大作业。资源共157个文件压缩包约40MB包含18个Python源码文件、18个CSV数据集、15个HTML页面和11个JavaScript脚本组成的可视化界面另有65张截图和PPT演示文稿便于按模块学习与答辩展示。项目完整覆盖二手房数据清洗、特征处理、模型训练、房价预测和可视化分析数据集提供原始及多种清洗版本方便复现实验细节。通过该系统的完整代码和说明读者可掌握机器学习项目从数据到展示的整套流程并借助高分毕设的答辩PPT梳理设计思路。这套项目已有166人学习整体属于经导师认可、评审99分的高分毕业设计。 去年做毕设选这个题目说实话不是因为它多热门而是被链家上那串数字勾起了好奇心——同一栋楼楼层差两层总价能差二十万同一个区域朝向差一个窗户报价能差十几万。谁在定价规则藏在数据里吗带着这个疑问我把毕设题目定成了“基于Python机器学习的二手房交易预测及可视化系统”。这套系统做的事情很直接用Python采集或获取一批真实的二手房挂牌数据完成清洗和特征工程训练若干机器学习回归模型来预测房屋总价然后通过Web页面把数据分布、特征关系、模型效果和在线预测完整展示出来。整个过程覆盖了一个机器学习项目从数据到落地的全链路很适合计算机、数据科学、信息管理相关专业的毕业生作为毕设主体也适合想补一个完整项目经历、或者想找一个“数据→建模→部署→展示”闭环案例练手的学习者。下面的内容就是我完整跑通之后的实现思路、选型理由、踩坑记录和答辩应对方案照着复现基本能少走一半弯路。1. 选题与系统整体设计这个题目为什么“好做又好看”1.1 选题逻辑难度适中故事完整答辩不慌毕设选题最怕两种一种是太简单一个月写完但没含金量答辩老师问两句就露馅另一种是太难做一半卡住最后只能硬着头皮糊弄。二手房交易预测这个方向刚好卡在两者中间一个非常舒服的位置。数据来源现成且真实。链家、贝壳这类平台的挂牌数据是公开的字段齐全有总价、单价、面积、户型、楼层、朝向、装修、区域、建筑年代等等。这些字段既有数值型又有类别型天然适合做机器学习回归任务。哪怕不写爬虫直接找一份整理好的公开数据集也能撑起整个数据预处理和建模环节。模型对比空间大。房价预测是典型的回归问题线性回归能跑决策树能跑随机森林、XGBoost、LightGBM都能跑。模型之间可以做效果对比出对比表格和图表这正好是答辩老师最爱看的东西——有实验、有数据、有结论。可扩展性强能讲业务故事。系统不只是“训练一个模型交差”还能落地成一个带页面的小工具用户输入面积、户型、楼层、区域系统返回一个预估总价。这件小事放到答辩现场演示说服力比十页PPT都强。1.2 系统架构与模块划分我的实现把整个系统拆成了四个模块按数据流动的顺序依次衔接数据采集与预处理、模型训练与评估、结果持久化、可视化与在线预测展示。这样拆的好处是每块都能单独调试出了问题不需要从头排查。技术栈方面我当时选了这样一套组合模块选型说明数据获取requests BeautifulSoup / 公开数据集写爬虫练手可以赶进度建议直接用整理好的CSV数据处理pandas NumPy scikit-learn清洗、特征工程、编码、划分数据全靠它模型训练scikit-learn LightGBM先跑基线模型再上集成模型对比出结论模型持久化joblib保存训练好的模型供Web端加载可视化展示Flask ECharts / Pyecharts图表交互性强页面效果比matplotlib好一个档次在线预测Flask 已训练模型表单输入 → 特征转换 → 模型预测 → 返回价格整体数据流是一条直线原始数据 → 清洗 → 特征工程 → 训练集/测试集划分 → 模型训练 → 评估对比 → 保存最优模型 → Flask加载模型 → Web页面展示图表和在线预测功能。顺着这条线往下做逻辑上不会有大的偏离。提示环境版本尽量固定。我当时用Python 3.9 scikit-learn 1.2.x pandas 1.5.x整套流程非常稳。版本太新某些API有变动太老又跟课程内容脱节。新建一个虚拟环境一次性装齐依赖避免后面边写边补包。装依赖就一条命令pip install pandas numpy scikit-learn lightgbm flask pyecharts joblib下载慢的话加清华镜像源-i https://pypi.tuna.tsinghua.edu.cn/simple。这个细节很多人忽略但能帮你省下不少时间。2. 数据获取与预处理模型的天花板在数据里2.1 数据源与获取思路能动手爬但别陷进去数据获取有三条路效率完全不同。第一条路直接用公开数据集。Kaggle上有波士顿房价、加州房价等经典数据集阿里天池、和鲸社区也有国内二手房数据。这些数据已经过初步整理缺失值和异常值不多适合把精力聚焦在建模和系统上。如果毕设时间紧张这是最推荐的选择。第二条路自己写爬虫抓链家或贝壳。这个方案最有“项目感”展示的时候可以说“数据是我自己采的”但同时也是最容易翻车的环节。一是平台有反爬机制访问频率太高会被封IP二是页面结构经常调整今天能跑的选择器明天可能就失效三是数据量太大反而增加清洗负担。我当时写了一个简单的爬虫思路代码不复杂关键是控制频率、带好Headersimport requests from bs4 import BeautifulSoup def parse_lianjia_url(url): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } resp requests.get(url, headersheaders, timeout10) soup BeautifulSoup(resp.text, html.parser) # 提取小区名称、户型、面积、朝向、装修、总价、单价等字段 # 遍历列表页翻页时在URL上替换页码参数 # 每次请求之间加随机延时比如 time.sleep(random.uniform(1, 3)) return items if __name__ __main__: # 指定城区的二手房列表页 # 循环抓取并保存到CSV pass第三条路找导师要数据。部分学校有合作企业的脱敏数据这种数据最干净、最有说服力但可遇不可求有的话直接选它没有也别强求。我的建议很直接爬虫可以作为系统的“数据获取模块”写进论文里展示思路但实际建模用的数据优先用整理好的公开数据集。把时间留给后面更重要的事情而不是在网页解析上死磕。2.2 清洗与特征工程实操细节决定模型上限数据拿到手之后第一件事不是建模而是看看数据长什么样。我当时用df.info()、df.describe()扫了一遍发现问题比想象中多。整理了一份处理清单基本覆盖了二手房数据的常见坑字段处理方式原因面积过滤掉小于20㎡或大于500㎡的记录极小或极大值多为录入错误或非住宅类房产总价/单价用箱线图识别极端值按需剔除个别挂牌价严重偏离市场影响模型训练朝向把“南、北、东南、西南”保留其余归为“其他”类别太散会导致独热编码维度爆炸楼层从字符串中提取“低/中/高/顶层/底层”楼层高低对房价影响显著属于核心特征装修映射为“毛坯/简装/精装/豪装”这四个档位业务含义清晰直接做序数编码建筑年代计算房龄当前年份-建筑年代缺失用同小区众数填充房龄比绝对年份更有建模意义区域/商圈区域做目标编码或标签编码位置是房价最核心因素但独热编码维度太高关键一步是处理价格长尾分布。房价数据通常右偏严重几百万的房子占多数几千万的豪宅拉高了均值直接丢给线性模型很容易被极端值带偏。我当时对总价做了对数变换import pandas as pd import numpy as np df pd.read_csv(house_data.csv, encodingutf-8) df df[df[面积] 20].copy() df[房龄] 2025 - df[建筑年代] df[朝向_group] df[朝向].apply( lambda x: x if x in [南, 北, 东南, 西南] else 其他 ) df[总价_log] np.log1p(df[总价]) df.to_csv(house_clean.csv, indexFalse, encodingutf-8)np.log1p相当于log(1x)好处是即使价格为0也不会出现负无穷还原的时候用np.expm1就行。区域这种高基数类别特征直接独热编码会让特征矩阵变得非常稀疏。我当时先把区域做了标签编码后来又试了目标编码——用该区域的历史平均单价替代类别值。效果提升很明显但这里有一个必须注意的禁忌目标编码的均值只能在训练集上计算再映射到测试集否则会造成数据泄漏测试指标会虚高得离谱答辩时被老师点出来就很难收场。注意特征工程的顺序千万记牢——先切分训练集和测试集再在训练集上做各种编码和填充。任何用到全局统计量均值、中位数、频次的操作都不能把测试集的信息混进来。3. 机器学习模型选型与调优从基线到最优的完整路径3.1 先跑基线模型再上集成模型很多初学者上来就调XGBoost、LightGBM结果超参调了一整天效果也没比线性回归好多少。正确做法是先跑几个基线模型建立一个参照系再逐步上更复杂的模型。我当时按“线性回归 → 随机森林 → GBDT → LightGBM”的顺序一步步来。每一步都计算MAE、RMSE、R²三个指标记录到表格里。这样产生的模型对比表非常扎实答辩时直接呈现“我的模型是逐步优化出来的”而不是“我随便选了一个模型”。from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score X df_encoded.drop(columns[总价, 总价_log]) y df_encoded[总价_log] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) models { LinearRegression: LinearRegression(), RandomForest: RandomForestRegressor(n_estimators200, max_depth10, random_state42), GBDT: GradientBoostingRegressor(n_estimators200, learning_rate0.05, random_state42), } for name, model in models.items(): model.fit(X_train, y_train) pred model.predict(X_test) mae mean_absolute_error(y_test, pred) r2 r2_score(y_test, pred) print(f{name}: MAE{mae:.4f}, R2{r2:.4f})注意我在划分数据之前把总价和总价_log都从特征里剔掉了这是防止数据泄漏的基本操作。训练目标是总价_log评估出来的MAE是在对数空间里的想转回真实的万元单位要用np.expm1(pred)还原后再算一次误差。3.2 LightGBM与XGBoost的参数实践基线模型跑完正常情况下LightGBM或XGBoost应该能拿到最优结果。我当时选的是LightGBM主要原因是对特征工程的要求相对宽松、训练速度快而且原生接口支持early stopping省去手动调参的大量时间。LightGBM的初始参数和调参思路我整理成了一张表参数建议初值说明learning_rate0.05学习率越小精度上限越高但训练更慢n_estimators / num_boost_round1000配合early stopping实际训练轮数由验证集决定num_leaves31叶子节点数越大越容易过拟合max_depth-1不限制深度时靠num_leaves控制复杂度feature_fraction0.8每轮迭代随机使用80%特征防过拟合bagging_fraction0.8每轮迭代随机采样80%样本防过拟合lambda_l20.1L2正则对高维稀疏特征有稳定作用核心训练代码import lightgbm as lgb params { objective: regression, metric: rmse, learning_rate: 0.05, num_leaves: 31, max_depth: -1, feature_fraction: 0.8, bagging_fraction: 0.8, lambda_l2: 0.1, verbose: -1, seed: 42 } train_data lgb.Dataset(X_train, labely_train) valid_data lgb.Dataset(X_test, labely_test, referencetrain_data) model lgb.train( params, train_data, num_boost_round1000, valid_sets[valid_data], callbacks[lgb.early_stopping(50), lgb.log_evaluation(100)] )early_stopping(50)的意思是连续50轮验证集RMSE没有下降就停止训练返回的最优模型会被保留。这一招对控制过拟合特别有效我在多个项目里实测下来比手动指定轮数稳定得多。3.3 模型评估与特征重要性解读模型跑完要输出一份类似这样的对比表这是论文和答辩的核心素材模型MAE万元RMSE万元R²整体表现线性回归23.535.10.71解释性强拟合不足随机森林18.228.70.81稳健无明显过拟合GBDT16.425.90.84比随机森林更好LightGBM15.824.30.86综合最优训练最快看到这个结果先别急着高兴一定要看看特征重要性这是答辩时的加分项。LightGBM原生接口能直接输出importance pd.Series( model.feature_importance(gain), indexX_train.columns ).sort_values(ascendingFalse) print(importance.head(10))我当时的Top特征基本是区域编码、面积、房龄、楼层、“是否南向”。这些特征放到业务里完全说得通位置决定基础价位面积和房龄决定单价楼层和朝向决定同一小区内部的差价。能用自己的数据解释业务规律这个项目就不再是“调包”而是真正有业务理解力的作品。提示R²不是唯一标准。房价这种波动大的场景MAE的业务含义更直观——预测误差15.8万意味着模型给出的估价与真实挂牌价平均只差15.8万。答辩时先用MAE解释模型价值再用R²说明拟合优度。4. 可视化子系统让数据和模型“讲人话”4.1 技术选型为什么选Flask ECharts可视化是这套系统的门面也是答辩时最容易出彩的部分。可选的方案不少我对比过三条路方案优点缺点matplotlib / seaborn 静态图简单、经典、论文插图顺手交互性差展示效果平淡Pyecharts 生成HTML中文友好生成图表方便适合快速做出Demo版本差异大部分组件定制受限Flask ECharts交互强、可自定义、能结合表单预测前后端联调需要自己写代码最终选了Flask ECharts。原因很简单Flask作为轻量级Web框架几百行代码就能搭起一个完整的展示站点ECharts是纯前端图表库散点图、热力图、地图、漏斗图全都开箱即用交互缩放、悬停提示都是现成的。前后端通过JSON交互逻辑清晰答辩时现场演示一套流程非常流畅。4.2 核心图表与在线预测页面设计系统页面我拆成了两部分数据展示页面和在线预测页面。数据展示页面放了6个核心图表都是答辩时必须能讲清楚的总价分布直方图/KDE曲线展示价格呈长尾分布这也是做对数变换的原因面积-总价散点图按区域着色可以看到不同区域的价格带差异特征相关性热力图展示总价与面积、房龄等特征的相关关系各区域平均单价柱状图支持按商圈下钻直观对比区域价格真实值与预测值对比散点图越接近对角线说明预测越准特征重要性条形图展示模型到底“看中”哪些因素在线预测页面更关键。用户填写面积、户型、楼层、朝向、装修、区域等字段后端把输入转成和训练时完全一致的特征格式调用已保存的LightGBM模型预测总价返回结果。核心代码from flask import Flask, request, jsonify import joblib app Flask(__name__) model joblib.load(lgb_model.pkl) app.route(/predict, methods[POST]) def predict(): data request.get_json() # 把用户输入转换为训练时完全一致的特征格式 features transform_input(data) pred_log model.predict([features])[0] pred_price round(float(np.expm1(pred_log)), 2) return jsonify({price: pred_price}) if __name__ __main__: app.run(debugTrue, port5000)其中transform_input是整个预测接口的灵魂——它必须把用户在网页上填的“原始值”转换成训练时的“模型输入”包括相同的编码映射、相同的填充逻辑、相同的顺序。这段函数直接复用训练脚本里的特征处理函数不要重写一遍否则很容易出现线上预测结果离谱的问题。模型保存我用的是joblibimport joblib joblib.dump(model, lgb_model.pkl)相比picklejoblib对大数组和模型对象的序列化更高效加载速度也更快。5. 答辩PPT设计与项目表达高分的关键在逻辑5.1 PPT结构像讲故事一样讲项目做完系统只是成功了一半答辩PPT决定老师怎么评价你的工作。一个高分毕设PPT核心不是堆砌代码和技术名词而是把“为什么做、做了什么、怎么做、结果如何”讲清楚。我的PPT结构是8个部分选题背景与研究意义用真实数据引出房价预测的实际价值国内外研究现状简单回顾引用3到5篇文献别铺开系统总体设计架构图 技术栈说明数据获取与处理展示原始数据长什么样清洗前后对比模型构建与实验对比核心页放模型对比表和特征重要性图可视化系统展示截图 现场演示或录屏总结与展望突出可扩展方向接入实时房价数据、推荐引擎等致谢第5页模型对比那一页至少留两分钟讲从基线到LightGBM每一步的优化点在哪里最后一页放“模型对比表”直接对比四个模型的MAE、RMSE、R²。这一页就是你的核心论据。演示环节建议提前录制一份完整录屏备份防止现场网络出问题或端口被占用。注意PPT每页的讲解时间控制在10到15秒重点页可以到30秒以上。答辩规定时间通常8到10分钟讲完记得留出时间给老师提问。5.2 老师最爱追问的5个问题答辩前我把老师可能问的高频问题整理成一个速查表尤其针对“创新点”和“模型选择”这两个方向做了准备高频追问回答思路为什么选这几个特征从业务相关性讲面积、区域、房龄是房价的核心影响因素再补充数据上的证据如相关性热力图和特征重要性为什么不用神经网络样本量有限树模型可解释性强模型对比实验更符合毕设工作量如果换更大数据集可以尝试DNN如何避免数据泄漏所有编码和填充操作都只在训练集上拟合后再映射到测试集线上预测复用同一个转换函数模型泛化能力如何交叉验证 独立区域测试集验证展示模型在未见过的数据上依然稳定创新点是什么不追求算法创新强调业务闭环从数据采集、特征工程到模型部署可视化一个完整链路提前找人模拟答辩把自己当听众用最简洁的语言把项目讲一遍直到不卡壳为止。这个环节比多跑十个模型都有用。6. 常见问题与调试排查这些坑我替你踩过了6.1 数据与模型阶段的经典问题整套流程走下来我把最容易踩的坑分类整理了一下都附上了解决方案和问题根源。先看数据和模型部分问题原因解决方案pandas读取CSV中文乱码文件编码是GBK读取时默认UTF-8pd.read_csv(path, encodinggbk)或者先转成UTF-8再读OneHotEncoder报错新版sklearn默认返回稀疏矩阵直接塞进DataFrame会报错指定sparse_outputFalse旧版本参数名是sparseFalsejoblib加载模型报错训练和部署环境的sklearn版本不一致保持同一环境运行写清楚requirements.txt预测结果出现负数对数空间还原后极小值导致用np.clip(pred, 0, None)限制下界LightGBM安装失败网络源不稳定或平台不兼容用清华镜像源或conda install -c conda-forge lightgbm另外提一个容易被忽视的问题Windows下用matplotlib画图时中文显示成方块原因是没有设置中文字体。需要手动指定plt.rcParams[font.sans-serif] [SimHei] # Windows自带黑体 plt.rcParams[axes.unicode_minus] False6.2 可视化与前后端联调的常见问题Web端的排查思路和模型端完全不同核心是搞清楚问题出在浏览器还是服务器问题原因解决方案Flask端口被占用默认5000被上次进程占用换端口app.run(debugTrue, port5001)或先杀掉占用进程ECharts图表加载不出来CDN文件路径失效或网络不通把echarts.min.js下载到本地static目录用相对路径引用表单提交后返回500后端特征处理函数抛异常开启debug模式看浏览器的Network面板和Flask控制台栈信息刷新后页面数据丢失图表数据存在内存变量里用sessionStorage临时存储或者直接重新请求后端接口拉数据排查的顺序也有讲究先看Flask控制台有没有报错再看浏览器Network面板的请求状态码最后看Console的JS错误。大多数问题三步之内就能定位。还有一种情况我遇到过一次前端传过来的字段名和后端函数对不上导致模型预测结果完全不对。后来我在transform_input函数内部加了一行打印把转换后的特征打印出来和训练时的特征做对比问题一眼就暴露了。这种“先打印再调试”的习惯在前后端联调阶段特别管用。提示演示环境记得把Flask的debug关掉否则出错时弹出的调试器页面会暴露源码路径而且不安全。我个人在实际操作中的最大体会是这个题目的价值不是算法有多新而是它逼着你走完一个机器学习项目的完整生命周期——从拿数据、清洗、建模到部署、展示、答辩。房价预测很难做到绝对准确影响一个房子成交价的因素远多于我们数据里能拿到的字段比如学区变动、业主急售、成交周期这些信息根本不在挂牌数据里。但“用数据逼近规律”这个过程本身就很有说服力也是面试官和答辩老师真正看重的能力。如果你也正在做这个题目我最后多说一句不要在爬虫和花哨组件上花超过三分之一的时间把精力留给特征工程和模型对比那才是整个系统真正的核心。本文还有配套的精品资源点击获取
