
简介这是一份Python二手房房价数据分析与预测项目的完整源码包适用于计算机相关专业毕业设计、课程设计及需要项目实战练习的学习者。项目经导师指导并通过98分认可包含数据采集、数据清洗、特征分析与房价预测等关键环节能够帮助读者快速搭建同类分析任务框架。资源共17个文件其中12个csv存储房源与交易数据3个ipynb为分步分析笔记1个docx为项目说明文档1个py为可执行脚本压缩包大小6.23MB结构清晰便于按流程学习。目前已有336人学习下载兼具参考与复用价值。读者可直接获得可运行的项目源码并参照ipynb中的分析思路理解数据爬取、探索性分析与建模预测的完整链路适合在毕业设计或期末作业中作为基础方案进行二次开发。1. Python二手房房价分析预测项目先看清这份源码能做什么做二手房房价预测最烦的从来不是建模而是数据。链家网页版字段乱、单位杂爬下来的户型有“2室1厅1厨1卫”也有“2室1厅”朝向有“南北”“东南 北”还有少量商住和车位混在里面。这份“Python的二手房房价数据分析与预测项目源码”恰好把这条链路补齐了链家网爬虫、数据清洗、特征构造、回归预测一条龙跑通是我最近拆得比较舒服的一份毕设级源码重新跑了一遍能直接出图和预测结果。适合正在写毕设、课程设计或者想练手数据分析全流程的人。下面按从爬虫到预测的顺序拆开讲。2. 先拆项目结构文件分工、运行环境与数据链路2.1 目录里每个文件是干什么的拿到压缩包解压后根目录下是一个analysis-and-prediction-of-second-hand-house-price-main文件夹里面东西不算多但每一样都有明确用处。第一件事先别急着跑把文件职责对一遍后面排错会省很多时间。文件/目录角色说明sol.py主流程脚本从数据清洗到建模预测的完整 Python 脚本适合命令行跑批sol.ipynbNotebook 演示版和 sol.py 逻辑对应逐格运行适合课堂演示和毕设答辩展示过程spider/链家网数据爬取.ipynb爬虫脚本基于 requests BeautifulSoup 的链家二手房采集 Notebookdata/数据目录存放爬取或生成的 CSV 数据是后续分析的输入.ipynb_checkpointsJupyter 缓存不用管属于自动生成的检查点文件README.docx说明文档记录了背景、字段说明和运行方式导论里经常要抄的就是这份我一般会先把README.docx翻一遍再看sol.ipynb的目录结构。这份资源里文档写得还算规矩数据链路是“爬虫 → CSV → pandas 清洗 → 特征工程 → sklearn 回归 → 可视化”属于典型的毕设项目式结构没有花哨的分布式、没有深度学习好处是单机 Jupyter 完全能复现。提示如果只想先看数据分析和预测结果可以直接跳过爬虫 Notebook用 data 目录下现成的 CSV 跑 sol.py。爬虫那份是给你补全流程用的不是唯一入口。2.2 依赖环境与 Notebook 启动方式项目基于 Python 3 环境核心依赖是 pandas、numpy、matplotlib、scikit-learn、requests、BeautifulSoup4 和 jupyter。这些库都是数据分析方向的家常便饭不需要特殊安装方式。pip install pandas numpy matplotlib scikit-learn requests beautifulsoup4 lxml jupyter如果网络不稳可以加国内镜像源我习惯用清华源pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pandas numpy matplotlib scikit-learn requests beautifulsoup4 lxml jupyter这里有一点值得注意sklearn 的版本影响不大但 Python 建议用 3.8 以上因为部分老教程的写法在 3.7 上会有 deprecation 警告。装完之后在项目根目录启动 Jupyterjupyter notebook然后在浏览器里依次打开sol.ipynb运行只要 cell 前没有报错红叉数据链路就通了。如果你习惯用 VS Code直接打开文件夹后选中 ipynb 文件也能跑内核选同一个 Python 环境即可。2.3 数据落在哪data 目录与字段说明data 目录下默认放的是链家爬虫落地的 CSV字段大体包含小区名称、户型、面积、朝向、装修、楼层、楼龄、总价、单价、所在区域。链家网页上能看到的信息基本都转成了列。字段示例类型小区天通苑本三区object户型2室1厅1厨1卫object面积89.5float朝向南 北object装修精装object楼层高楼层/共18层object总价450float万元单价50279float元/平区域昌平object注意原始 CSV 里的单价和总价可能混着字符串单位比如“450万”和“50279元/平”这也是后面清洗时要处理的点。拿到数据先df.head()和df.info()各看一眼比直接建模重要得多。3. 链家爬虫实战请求构造、字段解析与断点续爬3.1 爬虫主体翻页循环与单页解析链家二手房列表页的结构是ershoufang/pg{n}n 表示页码。爬虫的核心逻辑不复杂构造 URL带 headers 请求解析 HTML提取房源卡片然后翻页。import time import requests from bs4 import BeautifulSoup import pandas as pd HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://bj.lianjia.com/ershoufang/, Accept-Language: zh-CN,zh;q0.9, } def fetch_page(page): url fhttps://bj.lianjia.com/ershoufang/pg{page} resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() resp.encoding utf-8 return BeautifulSoup(resp.text, lxml) def parse_list(soup): items [] for li in soup.select(li.clear): title li.select_one(.title a) house_info li.select_one(.houseInfo) total_price li.select_one(.totalPrice) unit_price li.select_one(.unitPrice) if not title or not total_price: continue items.append({ 标题: title.get_text(stripTrue), 信息: house_info.get_text(stripTrue) if house_info else , 总价: total_price.get_text(stripTrue), 单价: unit_price.get_text(stripTrue) if unit_price else , }) return items这段里两个关键点。第一requests.get之后要手动指定resp.encoding utf-8链家页面本身是 utf-8但 requests 有时会从 headers 里猜成 gbk导致中文乱码。第二li.clear是列表页房源卡片的稳定选择器比靠 class 全名匹配更抗页面微调但这依赖链家页面结构如果页面改版需要同步更新。3.2 请求头与参数别让反爬卡住第一批数据def crawl_pages(start, end, sleep_range(1, 3)): all_data [] for page in range(start, end 1): try: soup fetch_page(page) rows parse_list(soup) print(f第 {page} 页抓到 {len(rows)} 条) all_data.extend(rows) except Exception as e: print(f第 {page} 页失败: {e}) continue time.sleep(random.uniform(*sleep_range)) return pd.DataFrame(all_data)random.uniform(1, 3)是每次请求之间随机停顿 1 到 3 秒不是固定 sleep。固定间隔反而容易被识别成脚本随机化之后更像人工浏览。headers 里Referer要带上https://bj.lianjia.com/ershoufang/否则部分请求会被直接重定向到验证页面。这套逻辑对应的是“链家网数据爬取.ipynb”里最常见的实现方式原项目里还加了个total_pages参数控制爬取范围避免一次性把几百页全拉下来导致 IP 被限制。3.3 异常重试与结果落盘网络请求一定会失败爬虫里必须带重试逻辑。我在实际跑的时候发现链家偶尔会返回 503 或连接超时一次失败就跳过会导致数据缺口后续清洗会很难看。def fetch_page_with_retry(page, retries3): for attempt in range(retries): try: return fetch_page(page) except Exception: if attempt retries - 1: raise time.sleep(2 ** attempt) # 指数退避time.sleep(2 ** attempt)是让等待时间按 1 秒、2 秒、4 秒递增给服务器喘息空间也提升重试成功率。落盘建议用df.to_csv(data/lianjia_house.csv, indexFalse, encodingutf-8-sig)关键在utf-8-sig这个编码Excel 直接打开不会乱码否则 pandas 写出的普通 utf-8 在 Excel 里中文全是乱码。4. 数据清洗与特征工程从脏数据到可用特征矩阵4.1 缺失值与重复值处理爬下来的 CSV 进入分析阶段第一步永远是df.info()和df.isnull().sum()。链家数据常见问题有三个无电梯小区楼层字段缺、车位房总价异常低、同一条房源在翻页时被重复抓取。import pandas as pd df pd.read_csv(data/lianjia_house.csv, encodingutf-8-sig) df df.drop_duplicates(subset[标题, 小区, 总价]) df df.dropna(subset[面积, 总价]) df[面积] pd.to_numeric(df[面积], errorscoerce) df[总价] pd.to_numeric(df[总价], errorscoerce) df df[(df[面积] 20) (df[面积] 500)] df df[(df[总价] 30) (df[总价] 5000)]drop_duplicates里我用的 subset 是“标题小区总价”三列组合单纯按 id 去重容易漏掉翻页重复。面积和总价的上下限过滤是把车位、商铺、超大平层这种非典型住宅样本筛掉。这里阈值不是硬标准要根据你爬的城市调整比如一线城市 5000 万以上的房源也算正常。4.2 文本字段数值化与单位统一链家的字段几乎全是字符串朝向可能是“南 北”两字带空格楼层是“高楼层/共18层”这种组合文本。不转数值模型没法直接用。def parse_total_price(s): # 从450万里提取 450 return float(s.replace(万, ).strip()) def parse_unit_price(s): # 从50279元/平里提取 50279 return float(s.replace(元/平, ).strip()) df[总价] df[总价].apply(parse_total_price) df[单价] df[单价].apply(parse_unit_price) # 用总价反推单价校验原始字段一致性 df[单价计算] df[总价] * 10000 / df[面积] df[单价偏差] (df[单价计算] - df[单价]).abs()这里有个比较实用的校检思路链家页面同时给出总价和单价但这两个字段可能因为维护原因不一致。用总价和面积反算单价再和原始单价对比偏差大于 2000 的样本很可能是错误数据。面积字段也要处理链家户型信息里有“86.43平米”这种带“平米”后缀的文本抽取时用正则import re def extract_area(text): match re.search(r([\d.])平米, text) return float(match.group(1)) if match else None df[面积] df[信息].apply(extract_area)4.3 特征筛选与探索性分析清洗完就该构造特征了。原项目在 sol.py 里做了一套比较规整的特征工程朝向转方向得分、楼层转中间层标识、楼龄转房龄、区域转类别编码。def direction_score(direction): if 南 in direction: return 2 elif 东 in direction or 西 in direction: return 1 return 0 df[朝向得分] df[朝向].apply(direction_score) def floor_level(floor_text): if 低楼层 in floor_text: return 0 elif 中楼层 in floor_text: return 1 elif 高楼层 in floor_text: return 2 return -1 df[楼层级别] df[楼层].apply(floor_level) df[楼龄] df[年份].apply(lambda x: 2025 - int(x))这里朝向不是直接 one-hot 成“南”“北”“东西”而是换算成一个可比较的得分。南北通透给 2 分东/西给 1 分北向和西北向给 0 分。这么做的好处是特征维度低模型不会因为稀疏 one-hot 而过拟合。楼层级别同理低中高三档比直接对文本编码更有解释性。到这一步特征矩阵基本可用。接着用df.corr()看总价和各特征的关系我跑下来通常“面积、单价、区域、朝向得分、楼龄”这几个特征和总价的相关性最高装修、电梯的影响相对弱一些但也得放进模型让算法自己判断。5. 房价预测避坑手册爬虫与建模最容易翻车的四件事5.1 现象爬虫只爬到前三页后面全是空列表或验证码页当时我自己跑链家爬虫前几页好好的第五页开始返回的列表长度变成 0。原因有三层请求频率固定、没有带完整 Referer、页面反爬升级后返回了验证 HTML。解决方式是给每个请求加随机延迟并把Referer指向对应城市的二手房列表页。如果还不行可以考虑在fetch_page里判断返回内容是否包含“验证”关键词一旦命中就停止爬取并保存已抓数据不要硬刚。链家这类站点的验证触发逻辑黑盒程度很高近似玄学最稳妥的做法是把爬取放到访问低峰时段比如工作日的上午。5.2 现象预测价格系统性偏低误差集中在 10% 到 20%建模之后我拿预测值和真实总价对比发现所有区域都有稳定的负偏差。排查到最后问题出在“总价”和“单价”字段的单位不一致总价单位是万元面积单位是平方米但特征里没构造总价/面积的关系模型默认它们互为独立变量。解决方式是统一构造“单价”作为特征并把总价作为预测目标时保留面积和单价的乘积关系。最常见做法是让模型用单价 总价 * 10000 / 面积作为输入特征之一或者直接把预测目标改成单价最后再乘面积换算回总价。我倾向于后者因为单价受区域和户型影响更集中模型拟合更容易收敛。5.3 现象朝向、装修这些字符串特征直接塞进模型后训练报错或完全没起作用sklearn 的线性回归和随机森林都不能直接吃字符串。有的同学用LabelEncoder把“朝南”“朝北”编码成 0 和 1结果模型认为 1 0学到了错误的单调关系。解决方向是分两类处理类别特征用 one-hot比如装修、区域序数特征用自定义映射比如朝向得分、楼层级别。原项目里区域用的就是pd.get_dummies因为区域之间没有大小关系朝向则用了上一节那个得分映射。实际跑下来这个处理直接决定模型能不能收敛到合理 R2。5.4 现象测试集 R2 高达 0.9但拿真实挂牌数据一测误差照样大这是我拆源码时最有感触的一条。问题出在数据划分方式上普通train_test_split会随机打乱所有样本导致同一小区的相似房源同时出现在训练集和测试集模型本质上是“背题”而不是“做题”。解决方式是换成分组切分。代码可以这样改from sklearn.model_selection import GroupShuffleSplit groups df[小区] gs GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, test_idx next(gs.split(df, groupsgroups)) train_df df.iloc[train_idx] test_df df.iloc[test_idx]GroupShuffleSplit的参数里groups传入小区列它的作用是保证同一个小区要么全进训练集要么全进测试集避免数据泄露。这一步是整套项目里提升真实预测精度最关键的改动比调模型参数重要得多。6. 把预测做成可交付物盲测验证与误差可视化技巧毕设答辩或项目验收时数据图和 R2 指标能用但最能让听的人觉得“模型真的能用”的是拿几套真实在售房源做一次盲测。我一般是这样做的从链家最新列表页抓 5 套房手动把特征填进模型输入格式让模型输出预测总价再和页面挂牌总价对比。import numpy as np from sklearn.metrics import mean_absolute_percentage_error samples [ # [面积, 朝向得分, 楼层级别, 楼龄, 区域编码] [89.5, 2, 1, 12, 0], [120.4, 2, 0, 8, 1], [65.0, 0, 2, 18, 2], [98.2, 1, 1, 15, 0], [76.3, 2, 2, 5, 1], ] real_prices [468, 620, 390, 505, 412] # 实测挂牌总价单位万元 pred_prices model.predict(np.array(samples)) * 10000 / 10000 # 按模型输出的单位换算 mape mean_absolute_percentage_error(real_prices, pred_prices) print(f盲测 MAPE: {mape:.2%})盲测的意义在于验证模型的泛化能力它是你交付项目时的最后一道心理防线。如果马普值在 10% 以内可以放心讲模型可用超过 15% 就老老实实说“本模型适合区域趋势分析不适合定价决策”这句话在答辩里反而加分。误差可视化也要做。我会画一个预测价对真实价的散点图再叠加一条 yx 参考线偏离对角线越远说明误差越大。import matplotlib.pyplot as plt plt.scatter(real_prices, pred_prices, alpha0.7) plt.plot([min(real_prices), max(real_prices)], [min(real_prices), max(real_prices)], r--, labelyx) plt.xlabel(实际总价万元) plt.ylabel(预测总价万元) plt.legend() plt.grid(alpha0.3) plt.show()散点图比 R2 更直观评委一眼就能看出哪些样本偏差大、有没有系统性高估或低估。从那以后我每次做房价预测项目交出去之前都强制盲测几套真实房源再画一张带参考线的散点图确认没有明显的系统性偏差才敢收工。这份源码里的 sol.py 已经把主要链路跑通了你要做的就是把清洗和切分这两步按上面的逻辑补稳。希望帮到你。本文还有配套的精品资源点击获取