十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Python从爬虫到机器学习预测北京二手房价格

用Python从爬虫到机器学习预测北京二手房价格 简介本资源是一套面向Python数据分析初学者与房地产数据爱好者实战项目聚焦北京二手房价格规律挖掘与预测建模。项目完整覆盖数据采集链家网爬虫、多区域CSV数据清洗、探索性分析分布统计、可视化、特征工程及Scikit-learn多种回归模型线性回归、随机森林等训练与评估全流程助力掌握房价预测核心方法论。压缩包共18个文件含16个分区域二手房数据CSV如朝阳、海淀、西城等、1个Jupyter Notebook主分析脚本含代码注释与图表输出、1个Python爬虫源码总大小仅1.81MB结构清晰、即开即用。已有3247人学习下载读者可直接复现从原始数据到预测结果的端到端流程获得可运行的分析模板、标准化预处理逻辑、模型对比实验记录及R²/RMSE等关键评估指标实现代码显著降低入门门槛并提升实战效率。 你在北京看房的时候估计也遇到过这个场景同样面积、同样楼龄的房子朝阳和石景山能差出一倍价格同一个小区朝南和朝北的挂牌价又能拉开几十万。市场里的定价逻辑肉眼看着毫无规律但在数据里其实是能归纳、能预测的。这篇文章我从头拆一个完整的Python项目抓取北京二手房挂牌数据做清洗、特征工程、探索性分析再用scikit-learn训练回归模型预测房价最后对比多种模型的预测效果。源码思路和核心代码我都会贴出来不仅是“能跑”更重要的是把每一步选择背后的原因讲清楚。适合刚学完Python基础想做第一个完整项目的人也适合正在准备数据分析或机器学习作品集的求职者——这类“采集分析建模”的复合项目在简历里是很能打的。1. 项目整体思路与数据准备1.1 这个项目到底在解决什么问题先说个很多人容易忽略的点做数据分析项目第一步不是写代码而是把问题定义清楚。同样是“北京二手房价格预测”在不同目标下方案差别非常大。有人说“预测房价”如果目标是想知道“这个小区均价多少”直接取均值就够了用不上机器学习。如果目标是预测一套具体房源的总价那问题就变成一个标准的回归任务输入户型、面积、楼层、朝向、区域、楼龄这些特征输出一个连续的价格数值。这个项目要做的就是后者。预测对象也要明确是预测房源总价还是预测每平方米单价两个目标在实际建模中差异很大。总价受面积影响极大适合用来做“用户搜索房源时的价格预估”单价则更能反映区域和房屋品质的真实行情更容易被特征解释。这个项目我两个都会做但重点放在总价预测上原因是总价是最直接的业务目标也方便后面做估价小工具时展示给普通用户看。明确了问题之后整个项目的链路就清晰了数据采集、数据清洗、特征工程、探索性分析、建模评估、结果展示。这也是绝大多数数据类项目的通用骨架跑通一次后面再换数据集你就知道该怎么下手了。1.2 数据从哪儿来爬虫还是现成数据集做北京二手房分析最好的数据源其实是链家、贝壳这类房产平台的公开挂牌信息。数据字段齐全——小区名称、户型、面积、朝向、装修、楼层、楼龄、总价、单价都有而且挂牌量足够大能覆盖北京几乎所有主流板块。我在这个项目里用的是链家二手房频道的数据。爬虫部分不难就是发送请求、解析HTML、提取字段然后翻页抓取。需要注意的细节有几个请求头里一定要带User-Agent服务端会校验裸请求很容易被拒。请求频率控制好加个2到4秒的随机延时。别贪快被封IP就白干了。单个城区房源多分城区、分页抓最后再合并。限于篇幅这里不放完整爬虫代码但思路给你参考。核心是解析列表页中每套房源的链接然后进详情页拿完整字段。import requests from bs4 import BeautifulSoup import time import random headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } def fetch_page(city_block, page): url fhttps://xxx.com/{city_block}/pg{page}/ resp requests.get(url, headersheaders, timeout10) if resp.status_code ! 200: return None return resp.text # 解析逻辑略核心是提取每条房源的字段 def parse_html(html): soup BeautifulSoup(html, html.parser) # 在这里定位列表项逐个提取字段 pass for block in [chaoyang, haidian, xicheng, dongcheng]: for page in range(1, 101): html fetch_page(block, page) if html: parse_html(html) time.sleep(random.uniform(2, 4))注意爬取公开数据仅限个人学习和研究用途不要大规模爬、不要用爬回来的数据做商业化服务。房产平台本身也有反爬机制做项目时控制频率抓到够用的样本量就收手。如果你暂时不想折腾爬虫也可以从Kaggle、GitHub上用现成的北京二手房历史数据效果差别不大——因为这类项目的核心难点本来就不在数据获取而在数据质量和特征工程。1.3 字段设计与数据结构我抓下来的原始数据大概长这样小区名称如“荣丰2008”“星河湾”区域所在行政区如朝阳、海淀、丰台板块更细的位置如“望京”“回龙观”户型如“3室1厅1厨2卫”面积单位平米朝向如“南北”“东南”“北”装修毛坯、简装、精装、豪装电梯有/无楼层低楼层、中楼层、高楼层、顶层建筑年份1998、2005等挂牌时间发布日期总价单位万元单价单位元/平方米关注人数、带看次数部分平台有原始字段是散乱的文本建模之前必须完成两类处理一类是把“3室1厅1厨2卫”拆成卧室数、客厅数等数值特征另一类是把“南北”“中楼层”这种类别文本转成模型能理解的数值编码。这部分是下一节的重点。2. 数据清洗与特征工程2.1 让数据“能用”的第一步清洗规则很多新手拿到数据就急着建模结果模型效果差回过头来才发现是数据脏。数据质量和特征工程决定了模型的上限模型只是逼近这个上限。我拿到原始数据后第一个处理的是价格字段。链家网页上的总价长这样“760万”单价是“65210元/平米”里面带单位必须清洗成纯数值。df[总价] df[总价].str.replace(万, ).astype(float) df[单价] df[单价].str.replace(元/平米, ).str.replace(,, ).astype(float)注意如果数据里有价签格式“1,200万”要先把逗号去掉再转float否则会解析失败。面积字段也有坑。原始数据往往带“平米”字样甚至还有“89.5平米”这种带小数的清洗时统一去掉“平米”两字转float。还有少数房源会出现面积和总价的乘积跟单价对不上——这是常见的脏数据我直接用单价乘面积重新算一版总价然后跟原始总价做对比误差超过3%的记录直接删除。楼层字段比较有讲究。链家把楼层划分为“低楼层”“中楼层”“高楼层”“顶层”其中顶层是非标准楼层有些顶层还有阁楼或露台定价逻辑跟普通楼层完全不同。建模时可以保留这个分类特征不要试图去预测“具体是第几层”信息量不够反而引入噪声。再来是户型字段拆分。原始字符串形如“3室1厅1厨2卫”拆的时候注意有些房子只有“2室1厅”没有阳台拆完的列要填充空值。def parse_layout(text): import re rooms re.search(r(\d)室, text) halls re.search(r(\d)厅, text) baths re.search(r(\d)卫, text) return ( int(rooms.group(1)) if rooms else None, int(halls.group(1)) if halls else None, int(baths.group(1)) if baths else None, ) df[[室, 厅, 卫]] df[户型].apply(lambda x: pd.Series(parse_layout(x)))2.2 构造特征模型需要的不是字符串清洗完之后原始数据还远不能直接建模。模型吃的是数值得把“朝阳区”“南北通透”“精装修”这些文本信息转成特征这部分叫特征工程。这里我按特征类别分别说。地理类的特征最粗糙的做法是把行政区做序号编码——但这样做有个问题模型会把“朝阳0、海淀1”当有序数据莫名其妙学出“海淀大于朝阳”这种逻辑。更合理的选择是目标编码target encoding用每个行政区/板块的历史平均单价或平均总价来编码。这个方法对树模型效果不错但注意要在训练集内部计算编码值防止数据泄漏。我会在后面“避免踩坑”部分专门讲这个问题。建筑年份的处理也是典型特征工程。原始字段是“建成年份”模型不能直接理解“2005年”的价值因为年份数字大小本身没有递增含义——2005比1995晚10年不代表2005年的房子一定“属性更强”。更好的做法是把年份换算成“房龄”即当前年份减去建筑年份。房龄才是真正影响房价的连续变量。朝向的处理建议拆成两个维度是不是南北通透以及主朝向是南还是其他。南北通透在北京二手房市场里有明显的溢价这是一个强业务规则直接做成0/1特征。楼层也可以进一步衍生。如果只保留“低/中/高/顶层”四个类别对于某些塔楼小区“中楼层”其实很难说一定优于“低楼层”。但把类别做二值化之后至少能区分“顶层 vs 非顶层”这种明显差异。面积、房龄这些数值特征量纲差别大线性模型需要做标准化对树模型和XGBoost来说不做也没关系。我通常会在代码里保留一个“是否标准化”的参数跑不同模型时切换。2.3 用热力图和直方图验证特征合理性特征工程做完之后别急着建模先用可视化快速看一眼特征跟目标变量的关系。我用seaborn画过一张相关系数热力图里面最直观的现象是面积跟总价的相关系数在0.85以上但跟单价的相关系数却很低。这说明面积主要决定总价而单价更多被位置、楼龄这些因素主导。如果你的目标是预测单价面积就别作为核心特征如果预测总价面积就是最重要的特征。画热力图的代码很简单import seaborn as sns import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False num_cols [面积, 总价, 单价, 房龄, 室, 厅, 卫, 关注人数] corr df[num_cols].corr() sns.heatmap(corr, annotTrue, cmapcoolwarm, fmt.2f) plt.title(数值特征相关系数热力图) plt.show()除了热力图还可以分区域画总价箱线图能直观看出哪些区域的中位数高、哪些区域离散度大。比如西城和东城的中位数和上四分位数都会显著高于丰台和大兴离散度也大说明核心区域内户型、品质差异带来的价格跨度非常大。3. 探索性数据分析先看数据长什么样3.1 北京二手房市场的基本盘做完清洗和特征工程先别急着建模用探索性数据分析EDA把数据的“脾气”摸清楚后面做特征取舍和模型解释时心里才有底。区域分布是最值得先看的维度。按我抓到的样本统计朝阳、海淀、丰台是挂牌量最大的三个区占了总量的一半以上西城、东城因为存量房本身少、价格高、换手率低挂牌量明显小。这个结构跟北京核心区的土地供应和住宅密度是匹配的。从均价看西城和东城领跑海淀紧随其后朝阳内部差异很大——CBD、望京、奥森这些板块能拉高整体均价但东五环外和南边部分区域的单价可能比丰台还低。这种“同区不同价”的现象说明如果只用区做预测特征会损失大量信息所以我在特征工程里保留板块信息并且用板块的目标编码作特征。反过来看另一个维度房龄分布。北京二手房里1995年到2005年之间的房子仍是主流2005年以后的新小区占比不高2010年之后次新房的挂牌量就更少了。这也解释了为什么“房龄”这个特征在模型里那么重要——20年以上房龄的房子管道老化、小区设施旧这些都会直接反映在价格上而且不是线性关系10年房龄到20年房龄的降价幅度远大于30年到40年。树模型能天然学到这种非线性这也是后面明显优于线性回归的原因之一。3.2 影响房价的关键变量可视化在探索性分析里我按照“单变量→双变量→多维交叉”的顺序来做每一步都有明确目的。首先看单价直方图北京二手房单价分布是一个明显右偏分布大量房源集中在3万到7万之间但有一根很长的尾巴延伸到10万以上。这种右偏数据如果直接喂给线性回归长尾部分会强烈影响系数。所以我后续对总价做了log变换让分布更接近正态再做建模。然后是面积和总价的散点图。这个图的形状非常稳定整体呈现出线性上升的带状但带宽越来越大——大面积房源的价格离散度更大。带上颜色看区域会发现西城、东城的点子在上方密云、平谷的点子在下方中心区域高单价、高总价的趋势非常明显。还有一个值得一提的发现是“关注人数”跟总价的关系。关注人数高的房源往往不是最贵的那些而是“性价比高”的房源——价格略低于同小区同类户型性价比优势会吸引更多人关注。反过来关注人数很低的高价房源要么是挂牌价明显高于市场要么是房源本身有问题。所以关注人数这个特征可以间接反映挂牌价偏离市场的程度对价格预测是有辅助价值的。4. 机器学习建模从线性回归到集成模型4.1 模型选型思路数据准备就绪后进入建模环节。模型选型的思路我建议新手不要一上来就上深度学习或者AutoML先跑一个简单模型做基线再用集成模型做对比这样你能直观感受到“模型复杂度带来的收益”和“过拟合的风险”分别是什么。这个项目里我选了三类模型做对比线性回归LinearRegression最简单跑得最快结果容易解释。随机森林RandomForestRegressor对非线性关系拟合能力强几乎不用做特征标准化是树模型里最稳的选手。XGBoostXGBRegressor梯度提升树在很多表格类数据竞赛里都是首选方案预测精度通常比随机森林更高但需要调节的参数也多一些。为什么不用深度学习原因很简单二手房数据是典型的结构化表格数据样本量一般在几万条量级特征维度几十个这个场景下树模型和梯度提升模型的表现已经足够好训练成本低、可解释性强。深度学习更适合图像、文本这种非结构化数据拿到这个项目里只会增加成本收益微乎其微。4.2 数据划分与评估指标模型对比的前提是公平的验证方式。这里的核心原则是训练集、测试集必须完全隔离模型只允许在训练集上学习测试集只能用来做最终评估。我用train_test_split做划分比例是8:2。随机种子固定保证实验结果可复现。from sklearn.model_selection import train_test_split feature_cols [面积, 房龄, 室, 厅, 卫, 区域编码, 板块编码, 南北通透, 电梯, 总楼层, 关注人数, 带看次数] target_col 总价_log X df[feature_cols] y df[target_col] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 )评估指标我用三个R²、RMSE、MAE。R²衡量的是模型对房价方差的解释程度取值范围通常0到1越接近1说明模型拟合越好RMSE是均方根误差对大误差特别敏感如果一套房预测错了100万它的惩罚会非常大MAE是平均绝对误差更直观——预测值和真实值平均差多少万元。我特别强调一点只看R²容易自嗨。R²达到0.9听起来很厉害但如果你把“预测偏差20万元”的房子给用户看用户不会理你的R²只会觉得你这工具不准。所以本项目以RMSE和MAE为主视角来评估模型实用性。4.3 训练与对比结果因为我预测的是log总价评估的时候要换算成真实总价来看否则误差的数字没有直观意义。我先把预测结果做指数还原再计算真实价格上的误差指标。这个细节新手很容易漏掉——直接拿log空间的指标说事最后误差多少万都说不清楚。用同一份训练集和测试集跑完三类模型之后结果是这样的示例数据你跑出来的数值会有差异模型测试集R²RMSE万元MAE万元线性回归0.7841.230.1随机森林0.8928.619.4XGBoost0.9125.316.8线性回归在测试集上的R²只有0.78MAE三十万这个成绩对实际估价场景来说不够用。随机森林和XGBoost都显著优于线性回归说明价格跟特征之间确实存在大量非线性关系而树模型能捕捉到这类关系。我还做了一件事把XGBoost输出的feature importance画出来。最重要的特征依次是面积、区域编码、板块编码、房龄、南北通透。这个排序和直觉一致——位置和面积决定了总价的基本盘房龄和朝向是调节项。这里补一个关于R²的忠告R²高不等于可实际部署。我的模型在测试集上R²已经到0.9以上了但单套房源误差超过20万的情况仍不少。原因在于北京二手房市场里单个房源的定价受到楼层、楼栋位置、装修、税费、房主心态等细颗粒度因素影响很多信息在数据里根本不存在模型再努力也只能抓到整体趋势。4.4 调参与防止过拟合随机森林和XGBoost都有很多超参数我最先调的三个是n_estimators树的数量、max_depth树深度、learning_rate学习率仅XGBoost。树的数量不是越多越好。最初我把n_estimators设为1000训练慢、效果没有明显提升反而容易过拟合。后来用GridSearchCV在100到500之间搜发现200左右就够了再多边际收益很小。max_depth是关键。随机森林不设限制的话每棵树都能长到很深对训练集完美记忆测试集上效果反而下降。我的经验是随机森林max_depth设为10到15XGBoost设为5到8效果最稳。from sklearn.model_selection import GridSearchCV from xgboost import XGBRegressor param_grid { n_estimators: [200, 300], max_depth: [5, 7], learning_rate: [0.05, 0.1], } model XGBRegressor(random_state42) grid GridSearchCV(model, param_grid, cv5, scoringneg_mean_absolute_error) grid.fit(X_train, y_train) print(grid.best_params_)一个操作原则调参要在验证集上做决定而不是在测试集上反复试。如果拿测试集调参你实际上是在把测试集信息偷学进模型最后的评估结果就虚高了。我用的是5折交叉验证每一折都有独立的验证集超参数选择依据的是交叉验证平均得分最后才用测试集评估一次。5. 常见问题与实战避坑5.1 训练集无敌测试集拉胯这个现象在树模型里太常见了——模型在训练集上能拿到98%的R²一到测试集就掉到70%。根因是过拟合典型表现是树长得太深、没有做剪枝、训练轮数过多。解决办法有三个层面。第一是限制模型复杂度比如控制max_depth和min_samples_leaf让每棵树的叶子节点至少有几十个样本避免模型去记忆个别样本的细节。第二是增加正则项XGBoost里可以调reg_alpha和reg_lambda能有效抑制过拟合。第三是用交叉验证选择模型不要只看单次划分结果。一个更隐蔽的坑是树的深度和数量是联动的。在我这个数据集上如果max_depth20、n_estimators1000训练集R²能到0.99测试集R²只有0.87但把max_depth降到7、n_estimators保持300测试集R²反而能到0.9以上。所以不要盲目追求“训练集拟合得多完美”那是自欺欺人。5.2 数据泄漏一个容易忽略的大坑这是这个项目里最值得展开讲的问题因为它往往不是报错而是“效果很好”的时候悄悄坑你。最常见的数据泄漏发生在处理类别特征的目标编码环节。比如计算“朝阳区平均总价”来做区域编码时如果把整个数据集的全量均值算好再划分训练集和测试集那么测试集的信息就提前进入训练过程了测试集评估会虚高很多。正确做法是在训练集内部计算编码规则然后在测试集上只做映射不参与计算。第二个常见泄漏是把“单价”当特征预测“总价”。总价单价×面积如果你把单价放进特征里模型学的不是规律而是除法。这在学术上没问题但业务上毫无意义——你的模型在真实场景里根本拿不到未知房源的单价。实际项目里我把单价完全排除出特征只保留面积和区域特征。第三个泄漏是时间线问题。二手房房源是时序数据如果用2023年底的数据训练去预测2023年初的“历史价格”看起来测试集表现不错但放到未来场景就废了。严格做法是按时间切割拿旧的做训练新的做验证。5.3 数据质量差缺失值、异常值怎么办缺失值处理没有万能公式全看字段含义。像“装修”这种字段缺失反而可能说明房源信息不完整我选择填为“未知”类别而不是随便填个“简装”或删掉。像“建筑年份”这种核心数值字段缺失超过10%的可以直接删掉对应行缺失少的时候可以用同小区的其他房源填充均值或者用房龄推算。异常值方面要分方向看。总价特别高的豪宅和总价极低的“奇葩户型”都是房价分布里的长尾。我处理策略是在清洗阶段用价格和面积的逻辑关系过滤明显错误比如单价超过20万面积超过500平这种明显异常样本直接剔除但不过度剔除“贵”的样本——豪宅少但它们是价格区间的一部分删掉会让模型整体偏向低估。还有一个挺隐蔽的质量问题是小区名称打错。链家平台上的小区名称基本规范但“荣丰2008”和“荣丰2008小区”可能被识别成两个小区。用板块编码做特征能在一定程度上缓解这个问题但如果项目里面出现过拟合先回头检查是不是这类重复样本造成的。5.4 爬虫数据遇到反爬怎么办爬虫反爬这个环节我实际踩过的坑还挺多的。一开始用requests直接请求频率一高就被限制返回的页面全是验证码。后来做了三件事才算稳定降低请求频率把延时从1秒调到3到4秒每次请求之间再加随机抖动模拟真实浏览器行为带上完整的headers有些平台还会校验cookie把抓取任务拆城区分页分时执行而不是一次性全量抓。如果你只是想快速跑通分析流程不想跟反爬斗争建议先下载一份公开数据集把清洗、建模部分跑通再回来加爬虫部分。不然一开始就卡在数据获取上很容易打击学习积极性。6. 项目扩展做成真正能用的估价工具6.1 一个可交互的估价页面模型训练完成后如果你只想在Jupyter Notebook里看结果那这个项目的价值只能发挥一半。建议把模型封装成一个简单易用的网页工具让别人输入面积、位置、户型、楼层这些选项马上就能估出价格区间。实现方案可以选Streamlit特别适合做这种轻量级Demo。核心逻辑就两步加载训练好的模型文件接收前端传入的参数输出预测结果。import streamlit as st import pandas as pd import joblib model joblib.load(xgb_model.pkl) st.title(北京二手房估价助手) area st.number_input(面积平米, min_value20, max_value500, value90) district st.selectbox(区域, [朝阳, 海淀, 西城, 东城, 丰台, 昌平]) # 组装特征调用模型预测这个工具不需要做得很复杂重点是让模型从“代码里的变量”变成“别人能用的产品”。面试或写博客的时候这东西比代码块的冲击力强得多。6.2 扩展到其它城市或细分市场这个项目的整体链路不是北京专属的。把爬虫目标换成上海、深圳、广州的房源页面数据字段基本一致清洗规则也能复用只需要调整区域编码和目标变量分布判断。还有一个思路是换赛道不做二手房做租赁价格预测。租赁市场的数据量更大更新频率更快而且租金跟地理位置、交通便利度的关系比买卖市场更直接做出来的模型在业务上的解释性更强。如果你愿意再多做一步还能引入外部数据提升模型精度——比如小区的周边配套评分、距离最近地铁站的步行距离、对口学校信息、周边商圈密度。这些人文地理维度的变量往往比户型细节更能解释“为什么同样是老破小这个小区就是比隔壁贵”。我实测下来最大的体会是这类数据分析项目的精力分配数据清洗和特征工程占了八成真正调模型的时间反而不多。很多初学者把注意力都放在“用什么高级模型”上结果数据一塌糊涂换什么模型都是白搭。北京二手房这个数据集的魅力在于它字段足够丰富、区域差异足够大、规律足够多非常适合用来练手整套数据科学流程。你先把这个项目跑通再换数据集、换业务场景就会发现方法论都是相通的。本文还有配套的精品资源点击获取
返回列表