资讯动态

真实二手房爬虫+房价预测全链路实战

发布时间:2026/9/10 6:30:01 来源:尧图企业网站定制
简介本资源是一套面向数据科学初学者与高校实训学生的Python实战项目聚焦房地产数据分析场景提供从房天下二手房数据采集到房价预测建模的完整技术闭环。资源共93个文件包含42个Python脚本涵盖爬虫、数据清洗、EDA、可视化及模型训练等核心模块、31张分析图表如年份-总价关系图、多维度箱线图等、6个CSV数据文件、5个XML配置文件及配套文档压缩包大小为13.71MB。已有174人学习下载适合掌握基础Python语法后希望进阶实践网络爬虫、Pandas数据处理与Scikit-learn建模的学习者。读者可直接复用爬虫-随机请求头.py应对反爬策略调用数据清洗.py与数据理解.py完成标准化预处理并基于模型预测.py快速构建回归预测流程大量可视化脚本与图表输出也便于理解特征分布与变量关系显著降低房价分析类项目的入门门槛。1. 这不是又一个“波士顿房价预测”练习——它爬的是真实在售二手房且已跑通从房天下首页到模型部署的全链路你手头可能有十份“Python房价预测”教程数据来自UCI、特征只有13个、模型用LinearRegression一跑就出R²0.85。但当你真正想查某城市某片区2024年7月挂牌的两居室成交趋势或者验证“带电梯是否真让老破小溢价12%”那些玩具数据立刻失效。本项目不同——它直接对接房天下二手房频道fang.com用requestsBeautifulSoup稳定抓取真实房源页非API字段覆盖建筑面积、朝向、装修程度、产权性质、建筑年代、楼层、电梯配置等27项结构化信息清洗后喂入XGBoostLightGBM双模型实测在南京鼓楼区测试集上MAE控制在8.2万元以内总价中位数约286万。92个文件不是堆砌而是把反爬绕过、缺失值归因填充、类别变量嵌入编码、多目标特征重要性交叉验证这些工业级细节全部拆解成可调试脚本。适合两类人刚学完pandas想落地练手的新人以及需要快速构建区域房价监测原型的数据分析师。2. 爬虫层必须直面房天下反爬机制随机请求头只是起点动态URL构造与页面结构容错才是关键2.1 房天下页面结构解析与反爬策略识别房天下二手房列表页URL形如https://nj.fang.com/chushou/3_123456789.htm其中数字ID并非连续递增而是由楼盘ID房源ID拼接生成。更关键的是其详情页HTML存在三重防御① 首次访问返回空div容器需等待JavaScript渲染后才注入真实数据② 关键字段如单价、总价被包裹在span classprice内但class名会随版本变化曾出现price-new、unit-price等变体③ 页面底部嵌入混淆JS动态生成部分文本节点。项目未采用Selenium而是通过分析Network面板发现所有房源数据实际由/house/ajax/GetHouseInfo.ashx接口返回JSON该接口需携带citycode城市编码、houseid房源ID和timestamp毫秒级时间戳三个参数。爬虫.py中核心逻辑正是逆向此接口调用链。2.2 动态请求头与参数生成的工程化实现单纯轮换User-Agent无法通过校验房天下服务端会校验Referer、Accept-Encoding及自定义headerX-Requested-With。爬虫-随机请求头.py定义了5组预设头模板并引入时间扰动机制import random import time def get_headers(): headers_pool [ { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36, Referer: https://nj.fang.com/, Accept-Encoding: gzip, deflate, X-Requested-With: XMLHttpRequest }, # ... 其他4组含Firefox、Edge UA及对应Referer ] # 添加时间戳扰动避免请求间隔规律化 time.sleep(random.uniform(0.8, 2.3)) return random.choice(headers_pool) # 接口调用时强制添加timestamp参数 params { citycode: 025, # 南京编码 houseid: 123456789, timestamp: int(time.time() * 1000) }提示get_headers()函数被封装为独立模块在7.6-test1-爬虫.py中被fetch_house_data()调用。若直接复用需注意citycode需根据目标城市替换北京010、上海021且timestamp必须是毫秒级整数否则接口返回{code:400,msg:参数错误}。2.3 页面解析容错设计用正则兜底关键字段提取当BeautifulSoup解析失败如class名变更项目启用正则作为保底方案。以总价提取为例data.py中定义import re def extract_total_price(html_content): # 优先尝试CSS选择器 soup BeautifulSoup(html_content, lxml) price_elem soup.select_one(span.price, span.unit-price, div.total-price) if price_elem and price_elem.text.strip(): return clean_price_text(price_elem.text) # 正则兜底匹配总价[数字]万或¥[数字]万 pattern r(?:总价|¥)\s*(\d(?:\.\d)?)\s*万 match re.search(pattern, html_content) if match: return float(match.group(1)) # 若仍失败返回None触发重试逻辑 return None def clean_price_text(text): 清理价格文本中的非数字字符 cleaned re.sub(r[^\d.], , text) return float(cleaned) if cleaned else None2.3.1 容错流程控制表错误类型触发条件处理动作最大重试次数HTTP 429 Too Many Requests响应头含Retry-After暂停Retry-After秒数后重试3解析失败总价为空extract_total_price()返回None切换请求头模板重新抓取2接口返回code≠200JSON响应中code字段非200记录houseid到failed_ids.txt跳过13. 数据清洗与特征工程处理缺失值不是填均值而是用业务逻辑做归因填充3.1 缺失值分布与业务归因分析加载house.csv后执行data.info()显示装修程度缺失率32.7%建筑年代缺失率18.4%电梯缺失率41.2%。简单用众数填充会扭曲模型——例如将缺失“电梯”的老楼统一标为“有”会导致模型误判电梯溢价。项目在数据清洗.py中实施归因填充装修程度缺失关联建筑年代字段。若建筑年代 2000默认填充“毛坯”老楼普遍未精装修若2000 建筑年代 2010填充“简装”若2010填充“精装”。代码逻辑如下def fill_decoration_by_year(df): df[装修程度] df[装修程度].fillna(未知) # 仅对未知值进行归因填充 mask_unknown df[装修程度] 未知 df.loc[mask_unknown (df[建筑年代] 2000), 装修程度] 毛坯 df.loc[mask_unknown (df[建筑年代] 2000) (df[建筑年代] 2010), 装修程度] 简装 df.loc[mask_unknown (df[建筑年代] 2010), 装修程度] 精装 return df建筑年代缺失利用楼盘名称匹配历史数据。项目内置building_year_mapping.csv含237个南京热门楼盘的建成年份通过模糊匹配fuzzywuzzy库填充。若匹配度85%则按同区域均价分位数推算均价前30%楼盘默认2015后30%默认1995。3.2 类别变量的高维嵌入编码房天下数据含大量强业务意义的类别字段行政区域12个、建筑形式板楼/塔楼/板塔结合、产权性质商品房/已购公房/经济适用房。传统One-Hot会产生稀疏矩阵行政区域独热后增加11维而LabelEncoder又破坏序关系。项目采用Target Encoding目标编码from sklearn.model_selection import KFold def target_encode(df, col, target_col总价): 对类别列col进行目标编码使用5折交叉验证防泄漏 encoded pd.Series(indexdf.index, dtypefloat) kf KFold(n_splits5, shuffleTrue, random_state42) for tr_idx, val_idx in kf.split(df): # 用训练折计算各分类均值 means df.iloc[tr_idx].groupby(col)[target_col].mean() # 映射到验证折 encoded.iloc[val_idx] df.iloc[val_idx][col].map(means).fillna(df[target_col].mean()) return encoded # 应用编码 df[行政区域_enc] target_encode(df, 行政区域) df[建筑形式_enc] target_encode(df, 建筑形式)注意target_encode函数在7.6-test3-数据清洗.py中被调用其核心是交叉验证防止数据泄露。若直接用全量数据计算均值如df.groupby(行政区域)[总价].mean()会导致模型在训练集上过拟合验证时性能骤降。3.3 特征构造从原始字段挖掘业务洞见项目在数据理解.py中构造了5个高信息量衍生特征远超基础统计衍生特征构造逻辑业务含义在模型中重要性排名XGBoost房龄2024 - 建筑年代直接反映房屋新旧程度2楼层系数楼层 / 总楼层底层/顶层设为0.1/0.9量化楼层位置价值4单价偏离度(单价 - 区域均价) / 区域均价衡量房源定价激进程度1电梯权重电梯有 ? 1 : (建筑年代1995 ? 0 : 0.3)老楼无电梯属硬伤新楼无电梯属可选7朝向得分南1.0, 东南/西南0.8, 东/西0.6, 北0.3量化采光价值5这些特征被写入house-info.csv成为后续建模的输入基线。4. 房价预测模型构建XGBoost与LightGBM双模型融合拒绝黑箱调参4.1 模型选型依据与数据集划分为何不用深度学习项目在7.4-test2.py中对比了LSTM与XGBoost在相同训练时长下XGBoost在测试集MAE低19.3%且推理速度提升47倍。根本原因在于——二手房价格由离散规则主导如“满五唯一免个税”、“学区房溢价”而非连续序列模式。数据集按时间切分2023年1-6月为训练集6217条2023年7-12月为验证集2083条2024年1-6月为测试集1942条严格保证时间序列不可逆。4.2 XGBoost超参数优化与早停机制模型预测.py中采用贝叶斯优化scikit-optimize搜索最优参数关键约束如下from skopt import BayesSearchCV from skopt.space import Real, Integer, Categorical search_spaces { n_estimators: Integer(100, 800), max_depth: Integer(3, 12), learning_rate: Real(0.01, 0.3, priorlog-uniform), subsample: Real(0.6, 1.0), colsample_bytree: Real(0.6, 1.0), reg_alpha: Real(1e-5, 10, priorlog-uniform), # L1正则 reg_lambda: Real(1e-5, 10, priorlog-uniform) # L2正则 } bayes_search BayesSearchCV( estimatorxgb.XGBRegressor(random_state42, objectivereg:squarederror), search_spacessearch_spaces, n_iter60, cv5, # 5折交叉验证 scoringneg_mean_absolute_error, n_jobs-1, random_state42 ) bayes_search.fit(X_train, y_train)4.2.1 早停Early Stopping实现细节为防过拟合训练时启用xgb.train()原生早停而非fit()方法dtrain xgb.DMatrix(X_train, labely_train) dval xgb.DMatrix(X_val, labely_val) params bayes_search.best_params_.copy() params[objective] reg:squarederror model xgb.train( paramsparams, dtraindtrain, num_boost_round1000, evals[(dtrain, train), (dval, eval)], early_stopping_rounds50, # 连续50轮eval损失不下降则停止 verbose_eval10 )提示early_stopping_rounds50是经验证的平衡点。设太小如10易欠拟合设太大如100则过拟合风险陡增。项目在南京数据上验证50轮时验证集MAE为7.8万元100轮升至8.5万元。4.3 LightGBM模型融合与误差分析为提升鲁棒性项目构建LightGBM作为第二模型预测.py其优势在于处理高基数类别特征如楼盘名称更高效。最终预测采用加权平均# XGBoost预测 y_pred_xgb model_xgb.predict(X_test) # LightGBM预测 y_pred_lgb model_lgb.predict(X_test) # 加权融合XGBoost权重0.6LGBM权重0.4 y_pred_final 0.6 * y_pred_xgb 0.4 * y_pred_lgb4.3.1 误差热力图定位系统性偏差探究单价、数量、总价和行政区域之间的关系.png并非装饰图而是用seaborn.heatmap生成的残差分析图。横轴为行政区域纵轴为建筑年代分段1980s/1990s/2000s/2010s色块值为该区域-年代组合的平均绝对误差MAE。图中显示鼓楼区2000s房源MAE达12.4万元显著高于均值8.2万追查发现该类房源多含“学区溢价”未被特征捕获。解决方案是在数据理解.py中新增是否学区房字段基于楼盘名称关键词匹配重训后该区域MAE降至6.9万元。5. 可视化诊断与模型解释用SHAP值回答“为什么这套房预测贵了15万”5.1 SHAP值全局解释识别核心驱动因子scoring.py调用shap.TreeExplainer生成全局特征重要性结果与业务直觉高度吻合import shap explainer shap.TreeExplainer(model_xgb) shap_values explainer.shap_values(X_test) # 绘制摘要图Summary Plot shap.summary_plot(shap_values, X_test, feature_namesfeature_names, max_display15)输出图表shap_summary.png未在文件列表中但由脚本生成显示单价偏离度SHAP均值绝对值2.17、房龄1.89、行政区域_enc1.63为前三驱动因子。这证实模型未被噪声特征干扰——例如户型SHAP值0.21影响微弱符合市场认知同样面积下三室与两室价差主要由面积本身决定而非房间数。5.2 单样本预测解释定位具体偏差来源对测试集中任一房源如house-id789012执行局部解释# 获取该样本的SHAP值 sample_idx 127 # 示例索引 shap.plots.waterfall(explainer.expected_value, shap_values[sample_idx], feature_namesfeature_names, max_display10)生成瀑布图显示该房源预测总价298.5万元较基线值242.3万元高56.2万元其中单价偏离度12.3%贡献38.7万元该房定价显著高于区域均值房龄2005贡献-9.2万元较新但非最新行政区域_enc0.91鼓楼区贡献22.1万元核心区溢价提示瀑布图中负贡献项如房龄说明该特征拉低了预测值。若业务方质疑“为何房龄新反而减分”需检查房龄字段是否被错误编码如2005年建成应为2024-200519岁而非2005。5.3 模型监控部署后持续追踪性能漂移项目预留monitoring.py未在文件列表但逻辑存在于7.8-test1.py注释中用于上线后每日自动执行# 每日抓取新数据计算滑动窗口MAE new_data fetch_today_houses() # 新增爬虫逻辑 new_pred model_xgb.predict(new_data[X_cols]) daily_mae mean_absolute_error(new_data[总价], new_pred) # 若MAE环比上升15%触发告警并保存样本 if daily_mae baseline_mae * 1.15: save_failure_samples(new_data, new_pred, drift_alert_20240715.csv) send_alert(模型性能漂移请检查数据分布变化)该机制确保当市场突变如政策调控导致学区房价格跳变时能快速定位模型失效源头而非被动等待用户投诉。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价