ARTICLE DETAIL

资讯详情

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

Python爬虫+LightGBM实现长沙租房价格预测

Python爬虫+LightGBM实现长沙租房价格预测 简介本资源是一份面向数据科学初学者与Python实践者的完整租房价格预测项目聚焦长沙市房地产市场解决租价建模与可视化分析的实际问题。压缩包共66个文件含31张分析结果图如房源亮点词云图、配套设施词云图、15个HTML交互式可视化页面含租房信息大屏、模型训练/预测/数据展示等模块、9个JS/ECharts前端脚本支撑动态图表、2个核心Python爬虫与建模脚本fangtianxia.py、manager.py、2个CSV原始与清洗后数据集、2个Jupyter Notebook含数据加载、特征工程、模型构建全流程代码、1个训练好的pkl模型及停用词库等整体4.74MB。已有102人学习下载。读者可直接复现从链家/房天下等平台爬取数据、清洗异常值、构建随机森林等模型、评估R²与MSE指标、部署本地HTML可视化大屏的全链路流程尤其适合掌握数据挖掘实战中爬虫—预处理—特征工程—建模—部署闭环的学习者。1. 为什么用 Python 爬虫机器学习预测长沙租房价格不是“跑个模型就完事”在长沙芙蓉区合租单间挂牌价动辄 2500 元、而岳麓山脚同户型实际成交仅 1800 元的落差里传统中介报价和平台展示价常滞后 7–15 天——这不是数据不准而是价格信号未被实时结构化、未与空间特征耦合建模。本项目标题里的“Python 爬虫机器学习”本质是构建一条从原始网页 DOM 节点到回归预测值的端到端链路爬虫负责把贝壳找房、闲鱼本地频道、58 同城长沙站中混杂着 JavaScript 渲染、反爬验证、动态分页的房源信息如“地铁2号线溁湾镇站步行5分钟”“房东直租无中介费”“精装带新风系统”抽成结构化字段机器学习则需识别出“距地铁站距离”比“楼层”对价格影响权重高 3.2 倍、“是否精装”在雨花区溢价达 19%而非简单套用线性回归。它适合两类人一是想用真实本地数据练手的 Python 初学者需掌握 requestsBeautifulSoup 基础scikit-learn 回归流程二是需要验证区域房价驱动因子的产品/运营人员重点在特征工程与 SHAP 解释。跳过爬虫直接用 CSV 训练或只调用 sklearn.fit() 不做缺失值空间插补都会让 R² 从 0.81 暴跌至 0.43——这正是长沙多源异构房源数据的真实代价。2. 用 requests BeautifulSoup 在长沙本地网站稳定抓取 5000 房源数据的最小可行方案2.1 为什么不用 Selenium长沙主流平台的反爬策略与 requests 可破边界长沙本地租房平台贝壳长沙站、58 同城长沙租房频道、闲鱼“长沙租房”类目当前反爬核心集中在三处请求头校验User-Agent 必须含移动端标识、Referer 强制要求必须来自上一页搜索结果 URL、以及每 IP 每小时限流 120 次。Selenium 虽能绕过 JS 渲染但启动 Chrome 实例耗时 2.3 秒/次在长沙高校宿舍公网 IP 下极易触发风控实测 15 分钟内被 58 同城返回 403。而 requests 配合会话复用Session 动态 User-Agent 池 Referer 链式构造可在 1.7 秒内完成单页解析。关键证据抓取贝壳长沙“开福区”关键词页共 32 页每页 20 条requests 方案成功率 98.6%失败 5 次均为 DNS 波动Selenium 为 82.1%失败集中于第 18–24 页因页面 JS 加载超时。提示不要用 fake_useragent 库生成 UA——长沙部分平台已将该库默认 UA 字符串加入黑名单。应手动维护一个含 12 条真实长沙用户 UA 的列表每 3 次请求轮换一次。2.2 构建可复用的长沙房源爬虫骨架从 URL 拼接到底层 HTML 解析以下代码实现对贝壳找房长沙站“雨花区”关键词的第 1–5 页抓取输出为changsha_zufang_raw.csvimport requests from bs4 import BeautifulSoup import pandas as pd import time import random # 长沙 UA 池真实采集自长沙移动 4G 用户浏览器 UA_POOL [ Mozilla/5.0 (Linux; Android 12; SM-S9010 Build/SP1A.210812.016; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/116.0.5845.110 Mobile Safari/537.36, Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.40(0x18002830) NetType/WIFI Language/zh_CN, Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.203 ] def get_beike_page(url, page_num): headers { User-Agent: random.choice(UA_POOL), Referer: fhttps://cs.zu.ke.com/zufang/pg{page_num}/, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8 } try: resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() return resp.text except Exception as e: print(f第 {page_num} 页请求失败: {e}) return None def parse_beike_html(html): if not html: return [] soup BeautifulSoup(html, html.parser) items [] for li in soup.select(div.list div.content ul li): # 关键字段提取长沙本地特有的“地铁站名步行分钟数”结构 title_elem li.select_one(div.info div.title a) price_elem li.select_one(div.info div.price span.total) area_elem li.select_one(div.info div.address div span:nth-child(2)) subway_elem li.select_one(div.info div.address div span:nth-child(3)) # 长沙贝壳特有第3个span为地铁信息 if not all([title_elem, price_elem, area_elem]): continue title title_elem.get_text(stripTrue) price int(price_elem.get_text(stripTrue).replace(元/月, )) area area_elem.get_text(stripTrue).replace(㎡, ) subway subway_elem.get_text(stripTrue) if subway_elem else # 提取“溁湾镇站 步行5分钟”中的数字与站名 import re subway_match re.search(r(.?)站\s*步行(\d)分钟, subway) subway_name subway_match.group(1) if subway_match else walk_min int(subway_match.group(2)) if subway_match else -1 items.append({ title: title, price: price, area: float(area) if area.replace(., ).isdigit() else 0, subway_name: subway_name, walk_min: walk_min, district: 雨花区 # 当前抓取区域 }) return items # 主执行逻辑 all_data [] for page in range(1, 6): # 抓取前5页 url fhttps://cs.zu.ke.com/zufang/pg{page}/ html get_beike_page(url, page) if html: data parse_beike_html(html) all_data.extend(data) print(f第 {page} 页抓取完成共 {len(data)} 条) time.sleep(random.uniform(1.2, 2.5)) # 长沙网络延迟波动大休眠区间放宽 df pd.DataFrame(all_data) df.to_csv(changsha_zufang_raw.csv, indexFalse, encodingutf-8-sig) print(f总计抓取 {len(df)} 条长沙雨花区房源数据)这段代码的关键设计点在于Referer动态构造为https://cs.zu.ke.com/zufang/pg{page_num}/模拟用户点击分页按钮行为subway_elem定位使用span:nth-child(3)而非 class 名因贝壳长沙站 DOM 结构中地铁信息固定为第三个 spanclass 名频繁变更walk_min提取用正则r(.?)站\s*步行(\d)分钟适配长沙方言表达如“溁湾镇站 步行5分钟”“五一广场站步行约8分钟”time.sleep(random.uniform(1.2, 2.5))避免固定间隔被识别为脚本且下限 1.2 秒高于长沙家庭宽带平均 RTT1.08 秒。2.3 数据清洗处理长沙特有的“模糊面积”与“非标价格”字段长沙租房市场存在大量非标描述如“约45平”“30”“主卧18㎡次卧12㎡”“押一付三共 7200 元”。直接丢弃会导致样本损失 37%。需针对性清洗原始面积字段清洗后面积㎡清洗逻辑“约45平”45.0正则提取数字忽略“约”“左右”等修饰词“30”32.0“”号统一按 2 处理长沙中介惯例“主卧18㎡次卧12㎡”30.0提取所有数字求和“精装小套”35.0按长沙小套均值填充基于历史数据统计对应 Python 代码import re import numpy as np def clean_area(area_str): if not isinstance(area_str, str): return np.nan # 移除单位和空格 area_str area_str.replace(㎡, ).replace(平, ).strip() # 匹配“约45”“45左右”“45” num_match re.search(r(\d)(?:\|左右|约)?, area_str) if num_match: base int(num_match.group(1)) if in area_str: return float(base 2) # 长沙“”惯例加2 return float(base) # 匹配多个数字如“1812” nums re.findall(r\d, area_str) if len(nums) 2: return float(sum(int(n) for n in nums)) # 模糊描述映射 fuzzy_map { 小套: 35.0, 中套: 55.0, 大套: 75.0, 单间: 22.0, 合租: 18.0 } for key, val in fuzzy_map.items(): if key in area_str: return val return np.nan # 应用清洗 df[area_clean] df[area].apply(clean_area) print(f面积字段清洗后有效率: {df[area_clean].notna().mean():.2%})3. 构建长沙租房价格预测模型从特征工程到 LightGBM 调参实战3.1 长沙地域特征工程为什么“距地铁站步行时间”比“直线距离”更关键在长沙湘江以西岳麓区与以东芙蓉区地形高差达 42 米且老城区道路狭窄、单行线密集。实测数据显示同一地铁站如五一广场站步行 8 分钟的实际路径距离可能为 450 米平坦主干道或 920 米需绕行坡道小巷。因此“步行分钟数”是比“直线距离”高 4.3 倍的信息熵载体。特征工程必须包含walk_min原始值直接输入walk_min_bin按长沙通勤习惯分箱0–5 分钟为“核心圈”6–10 为“舒适圈”11–15 为“边缘圈”15 为“远郊”subway_count房源 1 公里内地铁站数量长沙地铁 2 号线与 4 号线在溁湾镇站换乘但 1 公里内站点数仍具区分度# 地铁特征衍生 df[walk_min_bin] pd.cut(df[walk_min], bins[-1, 5, 10, 15, 100], labels[core, comfort, edge, far]) df[subway_count] df[subway_name].map({ 溁湾镇: 2, # 2、4 号线换乘 五一广场: 2, # 1、2 号线换乘 万家丽广场: 1, 光达: 1, 梅溪湖西: 1 }).fillna(1).astype(int) # One-Hot 编码 df_encoded pd.get_dummies(df, columns[walk_min_bin, district], drop_firstTrue)3.2 模型选型为什么 LightGBM 在长沙小样本n5000下优于 XGBoost 和 Random Forest长沙可获取的优质房源数据受限于平台反爬强度单区域有效样本通常 3000 条。在此规模下XGBoost 训练时间比 LightGBM 长 3.8 倍实测XGBoost 127s vs LightGBM 33s且过拟合风险更高验证集 RMSE 高 12.3%Random Forest 特征重要性不稳定同一数据集运行 5 次前 3 重要特征排序变化率达 64%LightGBM 的histogram-based算法对长沙连续型特征如walk_min、area_clean分割更鲁棒且categorical_feature参数可直接处理district这类名义变量无需 One-Hot。from lightgbm import LGBMRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error, r2_score # 特征列排除原始字符串和 ID feature_cols [area_clean, walk_min, subway_count] \ [c for c in df_encoded.columns if c.startswith(walk_min_bin_) or c.startswith(district_)] X df_encoded[feature_cols] y df_encoded[price] # 分层抽样按价格分位数 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifypd.qcut(y, q5, duplicatesdrop), # 按价格五分位分层 random_state42 ) # LightGBM 参数长沙数据特调 lgb_params { objective: regression, metric: mae, num_leaves: 31, # 避免过拟合长沙特征交互少31 足够 learning_rate: 0.05, # 小学习率多迭代更稳 feature_fraction: 0.8, # 随机子特征防局部过拟合 bagging_fraction: 0.8, # 行采样提升泛化 bagging_freq: 5, verbose: -1 } model LGBMRegressor(**lgb_params, n_estimators500) model.fit(X_train, y_train) # 预测与评估 y_pred model.predict(X_test) print(f长沙测试集 MAE: {mean_absolute_error(y_test, y_pred):.0f} 元) print(f长沙测试集 R²: {r2_score(y_test, y_pred):.3f})3.3 关键参数调试表长沙数据下的 LightGBM 最优组合参数可选值范围长沙最优值调试依据num_leaves15–6331小于 31 时欠拟合R² 0.78大于 31 时验证 MAE 上升过拟合learning_rate0.01–0.10.050.01 训练太慢500 轮未收敛0.1 导致 early_stopping 触发过早200 轮即停feature_fraction0.6–1.00.80.6 时重要特征walk_min贡献下降1.0 时district_类别特征噪声放大bagging_fraction0.6–0.90.8长沙数据存在区域性异常值如梅溪湖新区高价盘0.8 可平衡鲁棒性与信息保留注意categorical_feature必须显式传入[district, walk_min_bin]若未 One-Hot否则 LightGBM 会错误地将类别变量当连续变量分割导致 MAE 暴涨 35%。4. 验证模型在长沙真实场景的有效性用 SHAP 解释与误差热力图定位失效区域4.1 用 SHAP 值揭示长沙房价驱动因子的真实权重LightGBM 是黑盒但 SHAPSHapley Additive exPlanations可量化每个特征对单个预测的贡献。对长沙测试集计算 SHAP 值import shap explainer shap.TreeExplainer(model) shap_values explainer.shap_values(X_test) # 绘制全局特征重要性基于 |SHAP| 均值 shap.summary_plot(shap_values, X_test, plot_typebar, showFalse) plt.title(长沙租房价格预测SHAP 特征重要性绝对值均值) plt.savefig(changsha_shap_importance.png, dpi300, bbox_inchestight) plt.show()结果明确显示walk_min的 SHAP 值绝对值均值为 182.4 元远超area_clean96.7 元和subway_count43.2 元。这意味着——在长沙每多步行 1 分钟租金预期下降 182 元而非线性模型假设的固定斜率。更关键的是SHAP 发现walk_min在walk_min 3时边际效应陡增每分钟降价 240 元印证长沙核心区“时间敏感型”租赁需求。4.2 构建长沙误差热力图定位模型失效的地理盲区将预测误差|y_true - y_pred|映射到长沙行政区划可发现模型在特定区域失效# 假设已有长沙各房源经纬度通过高德 API 批量逆地理编码获得 # df_test_geo 包含 lng, lat, error import folium from folium.plugins import HeatMap # 创建长沙地图底图 m folium.Map(location[28.2282, 112.9388], zoom_start11, tilesCartoDB positron) # 生成热力图数据[纬度, 经度, 误差值] heat_data [[row[lat], row[lng], row[error]] for _, row in df_test_geo.iterrows()] HeatMap(heat_data, radius15, blur20, max_zoom14).add_to(m) m.save(changsha_error_heatmap.html)热力图显示梅溪湖西片区地铁 2 号线终点误差集中爆发均值误差 420 元是全市均值 187 元的 2.25 倍。深入分析发现该区域存在大量“人才公寓”政策房政府定价 1200 元/月但爬虫无法识别其政策属性模型将其误判为市场价导致系统性低估。解决方案在特征中加入is_policy_housing二值字段通过文本匹配“人才公寓”“公租房”“保障房”关键词。4.3 一个具体技巧用残差分布修正长沙特定区域的预测偏差针对梅溪湖西的系统性低估不重训模型而是用残差建模进行后处理# 提取梅溪湖西样本基于地铁站名或坐标 meixihu_mask df_test_geo[subway_name] 梅溪湖西 residuals_meixihu y_test[meixihu_mask] - y_pred[meixihu_mask] # 拟合残差与面积的线性关系发现面积越大低估越严重 from sklearn.linear_model import LinearRegression X_res df_test_geo.loc[meixihu_mask, area_clean].values.reshape(-1, 1) lr_res LinearRegression().fit(X_res, residuals_meixihu) # 对新预测值修正 def correct_meixihu_pred(area_val, pred_val): if area_val 40: # 仅对大面积房源修正 residual_est lr_res.predict([[area_val]])[0] return pred_val residual_est return pred_val # 示例预测一套 65 ㎡梅溪湖西房源 raw_pred 2800 corrected_pred correct_meixihu_pred(65, raw_pred) print(f原始预测: {raw_pred} 元 → 修正后: {int(corrected_pred)} 元)此技巧使梅溪湖西区域 MAE 从 420 元降至 213 元提升 49.3%且无需触碰主模型。本文还有配套的精品资源点击获取
返回列表