Python爬虫与回归分析:深圳租房租金定价逻辑全解析
2026/9/19 15:13:53 网站建设 项目流程

简介:一份以深圳为例的安居客租房数据分析与可视化实验报告,面向Python爬虫、数据分析与可视化学习者,系统展示从数据获取到建模的全流程。实验爬取了安居客10000多条租房信息,分别用单线程、多线程与Scrapy框架实现,并利用Power Query完成数据清洗,再用Excel和Tableau完成房租均价、各行政区及小区分布的可视化对比。建模阶段采用多元线性回归,以面积、租房方式、房屋类型、地铁距离等为自变量,配合KNN原理删除异常值、Lasso回归筛选变量,最终给出面积每增1平方米租金增55元、距地铁每增100米租金减6元等可解释结论。资源为1个PDF文件,大小912KB,共2545人浏览学习,适合想通过完整案例掌握爬虫、数据清洗、可视化和回归建模的初学者。

1. 从10000条安居客数据看深圳租金的定价逻辑

每年6月毕业季,深圳的租房需求集中爆发。同一个小区里,朝南和朝北的差价能有多少?离地铁站500米和1500米的租金差距,是否值得每天多走十分钟?这些问题散落在上万条挂牌房源里,靠肉眼很难看出规律。这篇实验报告用 Python 爬取安居客深圳站 10000 多条租房信息,经 Power Query 清洗后,用 Excel 和 Tableau 做可视化,最后建立多元线性回归模型,量化面积、租房方式、楼层、地铁距离等因素对租金的影响。比较有参考价值的地方在于,建模时没有直接套回归代码,而是先用 KNN 原理检测并删除异常值,再用 Lasso 回归压缩特征,把变量从 330 个降到 111 个。对正在做租房行情、二手房分析或城市数据项目的从业者来说,这条从采集到建模的完整链路有不少可复用的细节。

2. 爬虫选型与 Power Query 清洗

2.1 单线程、多线程与 Scrapy 的取舍

数据源是安居客的租房列表页和详情页,可获取的字段包括标题、租房方式、有无电梯、地铁、租金、付款方式、户型、面积、朝向、楼层、装修、类型、小区。抓取时分别用单线程爬虫、多线程爬虫和 Scrapy 框架三种方案实现,实验报告对三者做了对比。

爬虫方式优点缺点
单线程爬虫易上手、易理解爬取速度慢、CPU 利用率不高
多线程爬虫效率高、速度快IO 密集型操作下,线程加锁解锁消耗资源
Scrapy 框架灵活高效、开发速度快上手偏难、中间件设计较复杂

对于租房这类页面结构相对规整、数据量在万级的场景,多线程已经够用。我一般用 requests 配合 ThreadPoolExecutor,并发控制在 8 个线程左右,既保证速度又不容易触发反爬。

import requests from concurrent.futures import ThreadPoolExecutor from bs4 import BeautifulSoup headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" } def fetch_page(page_no): url = f"https://sz.zu.anjuke.com/fangyuan/p{page_no}/" try: resp = requests.get(url, headers=headers, timeout=10) resp.raise_for_status() except requests.RequestException as e: print(f"page {page_no} failed: {e}") return [] soup = BeautifulSoup(resp.text, "html.parser") items = [] for li in soup.select(".list-item"): items.append({ "title": li.select_one(".house-title a").text.strip(), "price": int(li.select_one(".price strong").text), "area": li.select_one(".details-item").text.strip(), }) return items if __name__ == "__main__": with ThreadPoolExecutor(max_workers=8) as executor: results = list(executor.map(fetch_page, range(1, 501)))

fetch_page 接收页码参数,请求列表页后用 BeautifulSoup 解析,从.list-item节点里提取标题、租金和面积。executor.map 会把 500 个页面按顺序分发到 8 个线程执行,返回结果顺序与传入顺序一致,方便后续直接拼接。timeout=10是必须的,否则某个响应卡住会拖慢整个线程池;max_workers设到 8 比较稳妥,调到 16 以上容易触发滑块验证。如果抓取规模再上一个量级,才需要考虑 Scrapy 的并发调优和下载中间件设计。

2.2 Power Query:字段拆分与类型统一

爬下来的数据不能直接进入分析。报告用 Power Query 做了七步清洗,核心动作包括删除重复项和不完整记录、去除面积单位并转数值、楼层拆分为低中高、小区细分为行政区商圈小区、布吉替换为龙岗、地铁字段拆分为地铁线地铁站距离、距离统一转成米。这几步在 Excel 的 Power Query 编辑器里依次操作即可,也可以用 M 语言直接记录处理逻辑。

// Power Query M 语言部分处理逻辑 let 源 = Csv.Document(File.Contents("D:/zu_data.csv"), [Delimiter=",", Encoding=65001]), 删除重复 = Table.Distinct(源), 替换布吉 = Table.ReplaceValue(删除重复, "布吉", "龙岗", Replacer.ReplaceText, {"行政区"}), 拆分地铁 = Table.SplitColumn(替换布吉, "地铁", Splitter.SplitTextByDelimiter(" ", 2), {"地铁线", "地铁站"}), 调整类型 = Table.TransformColumnTypes(拆分地铁, {{"面积", Int64.Type}, {"距离", Int64.Type}}) in 调整类型

M 语言的优势是每一步都生成新表,处理链路可以回溯。SplitColumn 按空格拆分地铁字段,第二段里还混着距离文本,实际操作中需要再拆一次或配合正则提取数字。面积字段在源数据里是"85平米"这样的文本,TransformColumnTypes 转数值之前必须先把单位字符替换掉,否则会抛类型转换错误。

2.3 建模字段的取舍逻辑

清洗后的变量说明如下。

变量符号变量说明
租金price数值变量,每月所交的基本房租(元)
租房方式rent字符型,整租或合租
电梯elevator字符型,有或没有
付款方式pay字符型,付1押1、付1押2等
户型layout字符型,3室1厅1卫、2室1厅1卫等
面积area数值变量,大于0的数(平方米)
朝向towards字符型,朝西、东南等
楼层floor字符型,低层、中层或高层
装修decoration字符型,毛坯、简单装修、精装修、豪华装修
类型style字符型,普通住宅、平房、公寓、别墅或其他
小区community字符型,岸芷汀兰、金海花园等
行政区district字符型,南山、福田、罗湖、宝安、龙岗、龙华
商圈business_circle字符型,科技园、海岸城等
地铁线subway_line字符型,2号线、5号线等
地铁站subway_station字符型,科苑站、深大站等
距离distance数值型,到最近地铁站距离(米)

建模前剔除了四个字段。标题和访问链接只是爬虫附带信息,与租金成因无关;小区有 3000 多个不同取值,按虚拟变量处理会让特征矩阵膨胀到三千多列,而且新样本里的小区未必在训练集中出现过,泛化能力差;户型与面积高度相关,保留面积即可;付款方式本质是押金担保,对租金影响微弱。这个取舍思路在真实项目里同样适用:特征不是越多越好,要先考虑取值数量和业务逻辑。

3. 可视化的探索与陷阱

3.1 房屋类型与行政区的租金分层

用 Excel 先画不同房屋类型的租金均价条形图。结果显示别墅均价 38402 元,远远超过其他类型;排在后面的依次是平房、其他、普通住宅和公寓。公寓价格垫底有些反常识,原因是公寓中合租占比高,拉低了整体均价。为了贴近毕业生和普通上班族的租房场景,后续分析排除了别墅,并限制租金在 800~8000 元区间,这样可视化结论不会被极端值带偏。

行政区维度的条形图也能看出明显梯度。

行政区租金均价(元)
南山2705
福田2372
罗湖2302
宝安2169
龙华2049
龙岗1708

南山比龙岗贵接近 1000 元,跟科技园和互联网公司分布直接相关。数据里不包含盐田、坪山和光明,原因是这三个区离地铁站较远,安居客在挂牌时没有标注与地铁站的距离信息,爬虫拿不到完整字段。做数据分析时遇到这种结构性缺失,要明确记录缺失原因,否则后续模型会引入偏差。

3.2 高德 API 地理编码与 Tableau 地图

小区粒度的租金排行容易受个别高端盘影响,报告用高德地图 API 把小区名称转成经纬度,再导入 Tableau 绘制气泡地图。颜色映射行政区,气泡大小映射小区租金均价,一张图就能看出深圳各片区租金的分布格局。

import requests import time def geocode(community, city="深圳"): url = "https://restapi.amap.com/v3/geocode/geo" params = { "key": "your_amap_key", "address": community, "city": city, } resp = requests.get(url, params=params, timeout=5) data = resp.json() if data["status"] == "1" and data["geocodes"]: location = data["geocodes"][0]["location"] # 返回格式为 "lng,lat" lng, lat = location.split(",") return float(lng), float(lat) return None, None communities = df["小区"].drop_duplicates().tolist() coords = {} for c in communities: coords[c] = geocode(c) time.sleep(0.2) # 控制请求频率,避免触发 QPS 限制

地理编码有两个常见坑。第一,key 需要在高德开放平台申请,个人开发者默认配额有限,批量转换时必须在循环里加 sleep 控制节奏;第二,小区名重复率高,不同行政区可能都有"海景花园",只按名称编码会产生漂移,稳妥做法是把"行政区+小区名"拼接后再请求。单次请求限时 5 秒,网络抖动时抛异常会导致某个小区经纬度缺失,所以函数里要处理返回空值的情况,后续在 Tableau 中过滤缺失坐标即可。

气泡图比单纯看表格信息密度高得多,适合放进项目汇报或数据分析看板,这也是为什么可视化类工具在这类场景中特别受欢迎。

3.3 租房三要素与数值变量的可视化陷阱

租房三要素的条形图结果显示:整租均价 2850 元,合租 1367 元,整租是合租的两倍多;有电梯均价 2629 元,无电梯 2195 元;高层 3121 元,中层 2436 元,低层 2248 元。三个变量指向一致:居住品质越高,租金越贵。

但单变量对比存在一个本质问题:没有控制其他因素。高层的房子可能同时面积更大、装修更好、离地铁更近,它的高租金不能全部归因于楼层。面积与租金的关系在组合图中出现波动,10~40 平方米区间房屋占比高、均价稳步上升,超出这个区间后频数太低,均价走势抖动剧烈。更反常的是距离与租金的关系,居然出现"越远越贵"的走势。这不是理论错了,而是数据可视化在单维分析时天然无法控制混杂变量。要回答"每个因素到底贡献多少租金",必须进入回归建模阶段。

4. 多元线性回归、KNN 异常值与 Lasso 特征选择

4.1 模型设定与评价指标

根据前面的变量筛选,模型以租金 price 为因变量,以 rent、elevator、area、towards、floor、decoration、style、district、business_circle、subway_line、subway_station、distance 为自变量。多元线性回归的矩阵形式为 y = Xβ + ε,最小二乘估计量为 b = (X'X)⁻¹X'y。当分类变量较多时,X 的维度会迅速膨胀,这也是后续要用 Lasso 压缩特征的原因。

评价回归模型时,常用指标如下。

指标含义用途
MAE真实值与估计值差值的平均直观反映平均偏差
MSE差值平方的平均放大较大误差
RMSEMSE 开方单位与因变量一致,跨模型可比
1 - RSS/TSS自变量解释因变量变异的比例
调整 R²对特征数量加惩罚比较不同特征数量的模型

用 sklearn 实现回归的代码结构如下。

from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error, r2_score import pandas as pd df = pd.read_csv("data/clean_rent.csv", encoding="utf-8") X = df.drop(columns=["price", "title", "link", "pay", "layout", "community"]) y = df["price"] # 分类变量做独热编码,drop="first" 避免完全共线性 X = pd.get_dummies(X, drop_first=True) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) model = LinearRegression() model.fit(X_train, y_train) y_pred = model.predict(X_test) rmse = mean_squared_error(y_test, y_pred, squared=False) print(f"RMSE = {rmse:.1f}, R2 = {r2_score(y_test, y_pred):.3f}")

get_dummies 把每个分类变量拆成若干 0/1 列,drop_first 去掉每个变量的第一个类别作为参照组,这样 X 矩阵列满秩,回归系数才有唯一解。random_state 固定后多次运行结果一致,方便复现。报告第一次运行得到 RMSE=936.7,R²=0.701,调整 R²=0.685,特征能解释约七成租金变异,但仍有提升空间。

4.2 用 KNN 距离检测并删除异常值

数据里存在"面积很大但租金很低"这类样本,会直接拉偏回归平面。KNN 检测异常值的思路很直接:异常点远离大部分正常点,它与最近 K 个邻居的平均距离必然偏大。计算出每个样本的 K 近邻平均距离后,用分位数法设定阈值,超过 Q3 + 1.5×IQR 的点判为异常值。

import numpy as np from sklearn.neighbors import NearestNeighbors def detect_outliers(features, k=5, multiplier=1.5): nn = NearestNeighbors(n_neighbors=k + 1) nn.fit(features) dist, _ = nn.kneighbors(features) avg_dist = dist[:, 1:].mean(axis=1) # 第一个点是自身,距离为0,跳过 q1, q3 = np.percentile(avg_dist, [25, 75]) iqr = q3 - q1 threshold = q3 + multiplier * iqr return np.where(avg_dist > threshold)[0] outlier_idx = detect_outliers(X_train[["area", "distance"]].values) X_train_clean = X_train.drop(index=outlier_idx) y_train_clean = y_train.drop(index=outlier_idx)

k 和 multiplier 是两个关键参数。k 太小,平均距离受局部密度影响大,容易误伤正常样本;k 太大,异常点会被周围的正常点稀释,检测不出极端值。一般取 5~10。multiplier 沿用箱线图的 1.5 倍 IQR,想更保守可以取 3。因为租金模型主要依赖面积和距离两个连续变量,检测时只取这两列计算 KNN 距离,虚拟变量参与计算意义不大。

提示:删除异常值前先观察分布直方图,样本量小于一万时 KNN 检测效果不稳定,建议结合业务规则交叉验证。

删除异常值后重新拟合,RMSE 降到 655.6,R² 升到 0.818。但此时特征数有 330 个,虚拟变量过多既容易过拟合,又存在多重共线性,部分回归系数符号与实际相反,朝北的租金反而比朝南贵,这明显不合理。

4.3 Lasso 回归压缩特征

Lasso 在损失函数中加入 L1 范数惩罚项 λ‖ω‖₁,可以把不重要的回归系数压缩到 0,天然适合做特征选择。

from sklearn.linear_model import Lasso from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler # 连续变量标准化,让惩罚对每个特征公平 lasso_pipeline = make_pipeline( StandardScaler(), Lasso(alpha=0.001, max_iter=100000) ) lasso_pipeline.fit(X_train_clean, y_train_clean) coef = lasso_pipeline.named_steps["lasso"].coef_ active_cols = X_train_clean.columns[coef != 0]

alpha 是 Lasso 的惩罚强度。alpha 越大,被压缩到 0 的系数越多,模型越简单,但可能欠拟合;alpha 太小则接近普通线性回归。报告采用 0.001 量级的 alpha 后,特征从 330 个降到 111 个。实际项目里我更推荐用 LassoCV 做交叉验证选择最优 alpha,而不是手调。

Lasso 之后 RMSE=641.6,R²=0.807,调整 R²=0.803。R² 比上一轮略降,但 RMSE 下降说明泛化能力更好。再按 p 值 ≤ 0.1 筛掉不显著变量后重新拟合,RMSE=644.0,R²=0.805,调整 R²=0.802,模型更精简,性能损失很小。

阶段特征数RMSE调整 R²
初始线性回归330936.70.7010.685
KNN 删除异常值后330655.60.8180.808
Lasso 筛选后111641.60.8070.803
p 值 ≤ 0.1 筛选后更少644.00.8050.802

这组对比清晰展示了每一步操作的价值:KNN 异常值处理贡献了最大幅度的 RMSE 下降,Lasso 和 p 值筛选主要是精简模型、控制过拟合。

5. 回归系数的业务解读与改进方向

5.1 看懂系数,回答业务问题

最终模型通过 F 检验,关键回归系数可以直接换算成业务语言。

变量系数业务含义
面积+55.28面积每增加 1 平方米,月租金平均上涨 55 元
距离-0.058距地铁站每增加 100 米,月租金平均下降 6 元
整租(参照:合租)+452.62整租比合租平均贵 453 元
无电梯(参照:有电梯)-70.91无电梯比有电梯平均便宜 71 元
高层(参照:中层)+46.47高层比中层平均贵 46 元
宝安(参照:南山)-838.33宝安比南山平均便宜 838 元
松岗商圈-1104.42松岗比万众城商圈平均便宜 1104 元

距离系数是 -0.058,每远 1 米递减不到 6 分钱,但换算成 100 米就是 6 元,500 米就是 30 元,说明地铁距离对租金的影响相对温和。面积是最强的连续变量,55 元的边际效应在 800~8000 元租金区间内占比可观。这些系数可以直接用于房源比价,比如两个房源相差 20 平方米和 500 米距离,粗略估算价差在 20×55 - 5×6 = 1070 元左右。

5.2 高基数分类变量的业务级压缩

回归结果里商圈和地铁站的虚拟变量数量仍然庞大,报告提出了几个改进方向,对实际项目很有借鉴意义:朝向按深圳气候特点划分为优、良、差,朝南、东南为优,朝北为差;小区按总建面积、坐落位置、开发商划分为高档、中档和普通;商圈按商业发展程度划分大、中、小;地铁站按人流量、是否始发站或中转站划分为热门和冷门。这样处理的本质是把高基数分类变量做归并,让模型更简洁、泛化能力更强。业务方有成熟分类体系时优先直接采用,没有的话可以先聚类再打标签。

5.3 用相对误差口径验证租金模型

回归评估通常看 RMSE 和 R²,业务方更关心"预测准不准"。报告定义了一个实用口径:满足 |ŷ - y| / y ≤ α 的样本视为预测有效,统计占比得到准确率 φ = n / m。

α准确率
0.0512.51%
0.1025.42%
0.1538.97%
0.2049.16%
0.2558.77%
0.3067.49%

α=0.3 时准确率约 67%,等于面对 1000 元的房源,模型预测结果落在 700~1300 元范围内的概率约三分之二。这个口径比抽象指标更贴近业务沟通,可以直接用作日报或数据大屏上的 KPI。验证时建议按租金分层评估,低价房源的相对误差天然偏大,整租和合租样本也应分别计算准确率,避免单一指标掩盖局部失真。

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

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

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

立即咨询