Python房屋价格预测系统源码:从数据清洗到模型部署全流程
2026/9/23 7:08:14 网站建设 项目流程

简介:这是一套面向高校计算机相关专业学生的Python毕业设计完整源码,主题为房屋信息可视化与价格预测系统,适合正在准备毕业设计或课程设计、需要可运行项目参考的学习者。系统功能覆盖用户注册登录、首页信息展示、房价数据爬取与分析、房屋预测数据管理等模块,可爬取安居客、58同城等平台的房价数据并做可视化分析。资源包共310个文件,约17.74MB,包含46个py源码文件、43个js脚本、17个html页面、17个css样式及多个gif、png、jpg等界面素材,另有sql数据库脚本、csv数据集、pdf与docx说明文档,结构完整便于二次开发。开发环境为Python 3.6.8搭配MySQL 5.7,使用PyCharm与Navicat11,配套LW论文材料,已有41人学习。读者可据此快速理解爬虫、数据分析与Web可视化整合的实现思路,并对照目录结构完成部署与调试。

1. 从一份房屋数据到能跑通的预测系统:这套 Python 源码到底解决了什么

很多人第一次拿到「基于 Python 的房屋信息可视化及价格预测系统源代码」这类毕业设计包时,第一反应是解压、找main.py、双击运行,然后被一堆ModuleNotFoundError劝退。它本质上不是一个「软件」,而是一条从原始房源表格到可视化图表、再到价格预测模型的最小闭环链路:数据清洗、特征工程、图表渲染、模型训练、结果展示,五段拼在一起。适合两类人:一是计算机、软件工程、大数据方向的毕业设计选题者,需要一套能讲清楚、能改、能答辩的完整代码;二是刚学完 Python 基础语法、想找一个真实数据集练手的入门者。它解决的核心问题不是「预测得多准」,而是「让一条数据流水线在你自己的机器上跑起来,并且每一段你都能改」。

2. 环境搭建与数据准备:让源码在你机器上先跑起来

2.1 Python 版本与依赖安装的取舍

这套源码通常基于 Python 3.8 到 3.10 之间,不建议用 3.12 以上,因为部分可视化库和机器学习库的轮子还没跟上。安装 Python 时勾选「Add to PATH」,这是新手最容易漏的一步,漏了后面pip命令全部报「不是内部或外部命令」。装完在终端验证:

python --version pip --version

如果两条命令都能输出版本号,环境就通了。接下来是依赖。这类项目一般会带一个requirements.txt,但很多毕业设计包里的版本号写得比较随意,直接pip install -r requirements.txt大概率会卡在某个库编译上。我一般会先装核心几个,再补其余:

pip install pandas numpy matplotlib seaborn scikit-learn flask

这里解释一下每个库的角色:pandas负责表格读写和清洗,numpy做数值运算,matplotlibseaborn出静态图表,scikit-learn提供线性回归、决策树、随机森林这些模型,flask用来把可视化结果挂到网页上。如果你的源码用的是pyecharts做交互图表,再补一个pip install pyecharts。注意,不要一次性把所有库都升到最新版,scikit-learnpandas的大版本变动会导致旧代码里的 API 报错,比如sklearn.cross_validation在新版已经移除。

2.2 房源数据集的字段结构与清洗脚本

房屋数据一般长这样:标题、总价、单价、面积、户型、楼层、朝向、建成年代、小区、区域。原始数据里最常见的脏数据是「面积」带「平米」后缀、「总价」带「万」字、「户型」写成「3室2厅1卫」。清洗脚本的核心就是把它们转成数值。

import pandas as pd import re # 读取原始数据,注意编码,中文csv常见gbk和utf-8两种 df = pd.read_csv('house_raw.csv', encoding='gbk') # 面积:去掉“平米”“㎡”等后缀,转float def clean_area(x): if pd.isna(x): return None x = re.sub(r'[^\d.]', '', str(x)) return float(x) if x else None # 总价:去掉“万”字,统一成万元 def clean_price(x): if pd.isna(x): return None x = re.sub(r'[^\d.]', '', str(x)) return float(x) if x else None df['面积'] = df['面积'].apply(clean_area) df['总价'] = df['总价'].apply(clean_price) # 户型拆成三个数值列 df['室'] = df['户型'].str.extract(r'(\d+)室').astype(float) df['厅'] = df['户型'].str.extract(r'(\d+)厅').astype(float) df['卫'] = df['户型'].str.extract(r'(\d+)卫').astype(float) # 丢掉关键字段为空的行 df = df.dropna(subset=['面积', '总价', '室']) # 算单价,作为后续预测的辅助特征 df['单价'] = df['总价'] / df['面积'] df.to_csv('house_clean.csv', index=False, encoding='utf-8-sig') print(df.shape)

这段脚本的逻辑是:先用正则把带单位的字符串剥成纯数字,再把户型这种复合字段拆成独立特征,最后丢掉无法修复的空值行。参数上要注意encoding,读的时候用gbk,写的时候用utf-8-sig,这样用 Excel 打开不会乱码。dropnasubset参数只检查指定列,不要直接df.dropna(),否则会把一些虽然缺失但不影响建模的行也删掉,样本量会骤减。

2.3 数据分布检查与异常值处理

清洗完不要急着建模,先看一眼分布。房价数据几乎必然有极端值,比如 10 平米卖 5000 万,这种要么是录入错误,要么是特殊房源,留着会把模型带偏。

import matplotlib.pyplot as plt # 看总价分布 plt.figure(figsize=(10, 4)) plt.subplot(1, 2, 1) df['总价'].hist(bins=50) plt.title('总价分布') # 用四分位距法标记异常值 Q1 = df['总价'].quantile(0.25) Q3 = df['总价'].quantile(0.75) IQR = Q3 - Q1 lower = Q1 - 1.5 * IQR upper = Q3 + 1.5 * IQR df = df[(df['总价'] >= lower) & (df['总价'] <= upper)] print('清洗后样本量:', len(df))

四分位距法是常见做法,1.5这个系数是经验值,改小会更严格,改大会更宽松。如果你的数据本身偏态严重,也可以直接用quantile(0.01)quantile(0.99)截断。这一步做完,样本量通常会掉 5% 到 15%,属于正常范围。

3. 可视化模块:把房源数据变成能讲清楚的图表

3.1 区域均价对比与户型分布图

可视化不只是为了好看,答辩时老师最常问的是「你这个数据有什么规律」。区域均价柱状图和户型分布饼图是最容易讲出结论的两张图。

import matplotlib.pyplot as plt import seaborn as sns # 设置中文字体,Windows用SimHei,Mac用Arial Unicode MS plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False # 区域均价 region_price = df.groupby('区域')['单价'].mean().sort_values(ascending=False) plt.figure(figsize=(12, 5)) sns.barplot(x=region_price.index, y=region_price.values) plt.xticks(rotation=45) plt.title('各区域平均单价') plt.ylabel('单价(元/平米)') plt.tight_layout() plt.savefig('region_price.png', dpi=150)

groupby后面跟mean()是求均值,也可以换成median()看中位数,中位数受极端值影响更小。dpi=150是保存图片的分辨率,毕业设计论文里插图建议不低于 150,否则打印出来发虚。tight_layout()是防止标签被裁掉,很多人保存的图 x 轴文字缺一半,就是漏了这一句。

3.2 面积与总价的散点回归图

散点图能直观看出面积和总价的相关性,再叠一条回归线,答辩时可以说「面积每增加一平米,总价平均增加多少」。

plt.figure(figsize=(8, 6)) sns.regplot(x='面积', y='总价', data=df, scatter_kws={'alpha': 0.3, 's': 10}, line_kws={'color': 'red'}) plt.title('面积与总价关系') plt.xlabel('面积(平米)') plt.ylabel('总价(万元)') plt.savefig('area_price.png', dpi=150)

alpha=0.3是点的透明度,数据量大时设低一点,否则点会糊成一团黑。s=10是点的大小。regplot默认会画置信区间,如果觉得太乱可以加ci=None关掉。这张图如果散点明显分成两簇,说明数据里混了两种不同类型的房源,比如住宅和别墅,建模前要考虑是否分开处理。

3.3 用 Flask 把图表挂到网页上

如果源码带 Web 展示,一般是用 Flask 起一个本地服务,把生成的图片或pyecharts的 HTML 嵌进去。

from flask import Flask, render_template app = Flask(__name__) @app.route('/') def index(): return render_template('index.html') if __name__ == '__main__': app.run(debug=True, port=5000)

debug=True只在开发时用,改代码会自动重启,但正式部署要关掉。port=5000是默认端口,如果被占用改成 5001。模板文件放在templates文件夹下,静态图片放static文件夹,这是 Flask 的默认约定,放错位置会报TemplateNotFound

4. 价格预测模型:从线性回归到随机森林的完整训练流程

4.1 特征选择与训练集划分

建模前先确定用哪些特征。常见的是面积、室、厅、卫、楼层、建成年代,区域这种类别特征要做独热编码。

from sklearn.model_selection import train_test_split from sklearn.preprocessing import OneHotEncoder from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline # 数值特征和类别特征分开 num_features = ['面积', '室', '厅', '卫', '建成年代'] cat_features = ['区域', '朝向'] # 预处理:数值列不动,类别列独热编码 preprocessor = ColumnTransformer( transformers=[ ('num', 'passthrough', num_features), ('cat', OneHotEncoder(handle_unknown='ignore'), cat_features) ]) X = df[num_features + cat_features] y = df['总价'] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42) print('训练集:', X_train.shape, '测试集:', X_test.shape)

test_size=0.2是常见的八二开,数据量小可以调到 0.3。random_state=42是固定随机种子,保证每次划分结果一样,方便复现。handle_unknown='ignore'是防止测试集里出现训练集没见过的区域时报错,这个参数不加,线上预测遇到新区域直接崩。

4.2 线性回归基线模型

先用线性回归跑一个基线,看看到底能到多少。

from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, r2_score lr_pipeline = Pipeline([ ('prep', preprocessor), ('model', LinearRegression()) ]) lr_pipeline.fit(X_train, y_train) y_pred_lr = lr_pipeline.predict(X_test) print('MAE:', mean_absolute_error(y_test, y_pred_lr)) print('R2:', r2_score(y_test, y_pred_lr))

MAE是平均绝对误差,单位是万元,比如输出 30 就代表平均预测偏差 30 万。R2是决定系数,越接近 1 越好,0.6 以上在房价数据里就算能看。线性回归的优点是解释性强,系数可以直接说「面积每多一平米,总价涨多少」,缺点是拟合非线性关系能力弱。

4.3 随机森林与参数调优

随机森林通常比线性回归准,但调参更麻烦。

from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import GridSearchCV rf_pipeline = Pipeline([ ('prep', preprocessor), ('model', RandomForestRegressor(random_state=42)) ]) param_grid = { 'model__n_estimators': [100, 200], 'model__max_depth': [10, 20, None], 'model__min_samples_split': [2, 5] } grid = GridSearchCV(rf_pipeline, param_grid, cv=5, scoring='neg_mean_absolute_error', n_jobs=-1) grid.fit(X_train, y_train) print('最优参数:', grid.best_params_) y_pred_rf = grid.predict(X_test) print('R2:', r2_score(y_test, y_pred_rf))

n_estimators是树的数量,越多越稳但越慢,100 到 200 通常够用。max_depth控制树深,太深会过拟合,None表示不限制。cv=5是五折交叉验证,n_jobs=-1表示用满所有 CPU 核心。scoring用负的 MAE,因为 GridSearchCV 默认找最大值,误差取负才能让「越大越好」等价于「误差越小越好」。这一步跑完,如果 R2 比线性回归高 0.05 以上,就值得在论文里重点写。

5. 避坑与排查:这套源码跑不起来时先看这几条

5.1 中文乱码:图表全是方框

现象:matplotlib画出来的图,标题和轴标签全是小方块。原因:默认字体不支持中文。解决:在画图前加plt.rcParams['font.sans-serif'] = ['SimHei'],Mac 用户改成['Arial Unicode MS'],Linux 用户先确认系统装了中文字体,没有就apt install fonts-wqy-zenhei再指定WenQuanYi Zen Hei

5.2 路径问题:读不到 CSV 文件

现象:FileNotFoundError: [Errno 2] No such file or directory。原因:代码里写的是相对路径,但运行时的工作目录不是脚本所在目录。解决:要么在代码里用os.path.dirname(__file__)拼绝对路径,要么在终端先cd到脚本目录再运行。VS Code 里可以在launch.json里配cwd,这是很多人忽略的一步。

5.3 版本冲突:sklearn 的 API 找不到

现象:ImportError: cannot import name 'cross_validation'。原因:旧代码用的是sklearn.cross_validation,新版本已经改成sklearn.model_selection。解决:全局搜索替换,把cross_validation改成model_selection。类似的还有sklearn.preprocessing.Imputer改成了SimpleImputer,遇到报错先查官方迁移文档,不要盲目降级。

5.4 内存溢出:数据量大时卡死

现象:跑随机森林时程序卡住,内存占用飙升。原因:n_estimators设太大,或者数据没做采样。解决:先把n_estimators降到 50 试跑,确认流程通了再往上加。数据超过十万行时,考虑用LightGBM替代随机森林,速度快一个量级,内存占用也低。

5.5 预测结果为负:模型输出不合理的价格

现象:预测出来的总价是负数。原因:线性回归没有约束输出范围,遇到极端特征组合会外推到负值。解决:在预测后加一层截断y_pred = np.maximum(y_pred, 0),或者改用对数变换np.log1p(y)训练,预测后再np.expm1还原。后者更优雅,但要注意还原后的误差评估要在原始尺度上做。

6. 进阶技巧:把预测系统做成能交互的工具

6.1 用 joblib 持久化模型,避免每次重新训练

训练一次随机森林可能要几分钟,没必要每次启动都重跑。用joblib把训练好的 pipeline 存下来。

import joblib # 保存 joblib.dump(grid.best_estimator_, 'house_price_model.pkl') # 加载 model = joblib.load('house_price_model.pkl') pred = model.predict(X_test[:5]) print(pred)

joblibpickle更适合存 numpy 数组大的模型,压缩率更高。存的时候用.pkl后缀是惯例,加载时确保特征列顺序和训练时完全一致,否则预测结果会错得离谱。

6.2 加一个命令行预测入口

让用户输入面积、室、厅、区域,直接输出预测价格。

def predict_price(area, rooms, halls, region): input_df = pd.DataFrame([{ '面积': area, '室': rooms, '厅': halls, '卫': 1, '建成年代': 2010, '区域': region, '朝向': '南' }]) return model.predict(input_df)[0] if __name__ == '__main__': price = predict_price(89, 3, 2, '朝阳区') print(f'预测总价: {price:.1f} 万元')

这里把缺失的特征用默认值填上,实际使用时可以改成input()交互输入。注意input_df的列名和顺序必须和训练时的X完全一致,差一个列名就会报KeyError

6.3 模型评估的三种验证方式对比

验证方式适用场景优点缺点
留出法数据量大简单快速结果受单次划分影响
K折交叉验证数据量中等结果稳定训练次数多,慢
时间序列划分有时间顺序符合真实预测场景需要时间字段

房价数据一般没有严格时间顺序,用 K 折就够。如果你的数据带成交日期,想模拟「用过去预测未来」,就要按时间切分,不能随机打乱。

6.4 特征重要性的解读与论文写法

随机森林可以直接输出特征重要性,这是答辩时的加分项。

import pandas as pd # 获取特征名 feature_names = (num_features + list(model.named_steps['prep'] .named_transformers_['cat'] .get_feature_names_out(cat_features))) importances = model.named_steps['model'].feature_importances_ feat_imp = pd.Series(importances, index=feature_names).sort_values(ascending=False) print(feat_imp.head(10))

输出后通常会发现「面积」和「区域」排最前,这符合直觉。论文里不要只贴图,要写一句「面积是影响房价的首要因素,重要性占比约 XX%,其次是区域,说明地段仍然是核心变量」。这种结论比单纯说「模型 R2 达到 0.8」更有说服力。

我自己做这类项目最大的教训是:不要一上来就调模型,先把数据清洗和可视化做扎实。答辩老师问得最多的不是「你用了什么算法」,而是「你的数据从哪来、怎么洗的、异常值怎么处理的」。模型换一个最多差几个点,数据错了整个结论都是空中楼阁。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询