简介:这份资源围绕成都二手房房价数据分析展开,面向具备一定Python基础、希望练习数据采集与建模的学习者,可用于课程设计、数据分析实战或房价预测赛题参考。压缩包共17个文件,以12个CSV数据文件、3个Jupyter Notebook、1个Python脚本和1份Word报告为主,整体约6.2MB,覆盖从数据爬取、清洗到建模预测的完整流程。数据采集自成都主城区二手房信息,共三万余条,字段包括总价、单价、小区名、位置、户型、楼层、建筑面积、朝向、装修、梯户比例、挂牌时间、交易权属等,房价单位为万元、面积单位为平方米。资源中提供了链家网数据爬取脚本与Notebook,以及针对锦江、双流、龙泉驿、温江、武侯、新都、青白江、金牛、青羊、郫都、成华、高新等区域的原始数据文件,便于分区域对比分析。已有1230人学习下载,读者可据此完成数据预处理、可视化探索影响房价的关键因素,并基于购房需求构建房价预测模型,获得一套可复用的分析思路与代码框架。
1. 成都二手房价数据分析 python:从链家公开页到能解释的单价模型
成都二手房挂牌量常年维持在高位,光是我自己关注的几个板块,同一小区在售房源就能从十几套到上百套不等。很多人第一反应是「用 python 爬下来算个均价」,但真做过就知道,直接对挂牌价求平均几乎必然翻车:楼层、朝向、装修、楼龄、是否满五唯一、甚至房源描述里一句「业主诚心卖」都会让价格偏移几千块一平。这个标题真正要解决的问题,是把散落在公开页面上的房源信息,整理成一张能按板块、面积段、楼龄切片分析的结构化表,再用 python 做描述统计和简单回归,回答「这个小区现在到底值多少」这类具体问题。它适合会一点 python 基础、想拿真实数据练手数据分析的从业者,也适合做房产相关业务、需要自己搭一套价格参考口径的人。下面我按自己实际跑过一遍的流程讲,从取数到建模到排错,尽量把参数和坑说清楚。
2. 数据从哪来:公开房源页的字段拆解与采集边界
2.1 先想清楚要哪些字段,再决定怎么取
做成都二手房价分析,字段设计比爬虫本身重要。我一般会锁定这几列:小区名、所在区域(锦江/高新/武侯等)、板块、总价、单价、建筑面积、户型、楼层、朝向、装修、楼龄、挂牌时间。其中单价是核心因变量,但它和总价、面积存在换算关系,采集时最好三个都留,方便后面交叉校验。区域和板块决定了价格量级,高新南和青白江能差出三倍以上,不分区直接算均价没有意义。楼龄和装修是回归里解释力比较强的自变量,但这两个字段在页面上经常是文本描述,需要额外清洗。
采集边界要提前定好。公开页面通常有翻页限制,一般只展示前几十页,按默认排序拿到的样本会偏向新上架或高价房源。我的做法是按区域分别采集,每个区域再按总价区间分段,这样能缓解排序带来的偏差。采集频率控制在合理范围,不要短时间高频请求同一站点,既是对站点负责,也避免自己的 IP 被限流。
2.2 用 requests + BeautifulSoup 抓一页并解析
下面这段是我常用的最小采集骨架,先跑通单页解析,再套循环翻页。注意选择器要按目标页面实际结构改,我这里用通用写法示意。
import requests from bs4 import BeautifulSoup import pandas as pd import time HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36" } def parse_page(url): resp = requests.get(url, headers=HEADERS, timeout=10) resp.encoding = "utf-8" soup = BeautifulSoup(resp.text, "html.parser") rows = [] # 房源卡片容器,按实际 class 替换 for item in soup.select("div.house-item"): row = { "title": item.select_one("div.title").get_text(strip=True) if item.select_one("div.title") else None, "total_price": item.select_one("div.total").get_text(strip=True) if item.select_one("div.total") else None, "unit_price": item.select_one("div.unit").get_text(strip=True) if item.select_one("div.unit") else None, "area": item.select_one("div.area").get_text(strip=True) if item.select_one("div.area") else None, "region": item.select_one("div.region").get_text(strip=True) if item.select_one("div.region") else None, } rows.append(row) return rows if __name__ == "__main__": all_data = [] for page in range(1, 6): # 先跑 5 页验证 url = f"https://example.com/chengdu/ershoufang/pg{page}/" all_data.extend(parse_page(url)) time.sleep(2) # 控制频率,别把站点打疼 df = pd.DataFrame(all_data) df.to_csv("chengdu_raw.csv", index=False, encoding="utf-8-sig") print(df.shape)逻辑上就是请求、解析、落盘三步。timeout=10防止某个请求卡死整个循环;time.sleep(2)是采集礼仪,也是保命手段。encoding="utf-8-sig"是为了 Excel 打开不乱码。选择器div.house-item这类必须对着真实页面改,改完先打印df.head()确认字段没串位。如果返回空列表,先检查是不是被重定向到验证页,再看resp.status_code和resp.text[:500]。
2.3 翻页与增量采集的取舍
一次性把全量抓完不现实,也没必要。我一般按「区域 + 总价段」组合出 URL,每个组合抓前 10 到 20 页,够做板块级分析就行。增量采集时,用小区名加面积加总价做去重键,重复的丢掉。这里有个血泪经验:不要用标题去重,同一套房源标题可能被改过,用「小区 + 面积 + 总价」三字段组合更稳。
3. 清洗与特征工程:把文本字段变成能进模型的值
3.1 单价、面积、楼龄的数值化
原始数据里单价常带「元/平米」后缀,面积带「平米」,楼龄可能是「2005 年建成」或「满五年」。统一用正则抽数字。
import re import numpy as np def to_number(text): if text is None: return np.nan m = re.search(r"(\d+\.?\d*)", str(text)) return float(m.group(1)) if m else np.nan df["total_price"] = df["total_price"].apply(to_number) # 万元 df["unit_price"] = df["unit_price"].apply(to_number) # 元/平米 df["area"] = df["area"].apply(to_number) # 平米 # 用总价和面积反算单价,和页面单价交叉校验 df["unit_price_calc"] = df["total_price"] * 10000 / df["area"] df["price_diff"] = (df["unit_price"] - df["unit_price_calc"]).abs() df = df[df["price_diff"] < 500] # 差异过大的行先剔掉,多半是解析串位to_number只取第一个数字,对「85.5 平米」这类没问题。反算单价这一步很关键,能抓出解析错误:如果页面单价和总价除以面积差出几千,基本是字段错位。阈值 500 是我试出来的经验值,太松会放过脏数据,太紧会误杀。
3.2 区域、朝向、装修的编码
区域用 one-hot 或者直接映射成有序类别都行,成都主城区价格梯度大致是高新南 > 锦江 > 武侯 > 成华 > 金牛 > 青白江,但板块内部差异更大,所以更细的做法是保留板块字段。朝向里「南」「东南」「南北」通常溢价,用字典映射成分数。
orient_score = {"南": 3, "东南": 2.5, "南北": 3, "东": 2, "西南": 1.5, "北": 0.5, "西": 1} df["orient_score"] = df["朝向"].map(orient_score).fillna(1) df = pd.get_dummies(df, columns=["region"], prefix="reg")朝向缺失的填 1 是中位数思路,别填 0,否则模型会把它当成最差朝向。get_dummies之后列数会涨,样本少的时候注意别过拟合。
3.3 楼龄和挂牌时间的处理
楼龄用「当前年份 - 建成年份」,比直接用年份好解释。挂牌时间转成「挂牌天数」,这个字段能反映房东急售程度,天数越长议价空间通常越大。清洗完存一份chengdu_clean.csv,后面分析都基于它,别每次重跑采集。
4. 描述统计与可视化:先看清分布再谈模型
4.1 分区域单价分布对比
拿到干净数据后,第一件事是看分布,不是直接建模。用 pandas 的groupby加describe快速出各区域单价的分位数。
summary = df.groupby("region")["unit_price"].describe()[["count", "mean", "50%", "std"]] summary = summary.sort_values("50%", ascending=False) print(summary)看中位数比看均值靠谱,挂牌价里总有几套离谱高价拉高均值。如果某个区域count只有个位数,结论不要写进报告,样本太少。std特别大的区域,说明内部板块分化严重,值得再往下拆一层。
4.2 用 matplotlib 画单价分布直方图
import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["SimHei"] # 中文显示 plt.rcParams["axes.unicode_minus"] = False df["unit_price"].hist(bins=50, figsize=(10, 5)) plt.xlabel("单价(元/平米)") plt.ylabel("房源数") plt.title("成都二手房单价分布") plt.savefig("price_dist.png", dpi=120)bins=50是我常用的粒度,太少看不出双峰,太多全是锯齿。如果横坐标标签太密集,用plt.xticks(rotation=45)转一下,这是热词里「python 画图横坐标太密集」的常见解法。中文乱码就靠SimHei,Linux 上没有这个字体就换成系统里有的中文字体。
4.3 面积段与单价的交叉分析
把面积切成 60 以下、60-90、90-120、120 以上几段,看每段的单价中位数。通常小面积单价偏高,因为总价低、流动性好。这个结论对判断「买多大面积划算」有直接参考价值。
bins = [0, 60, 90, 120, 1000] labels = ["60以下", "60-90", "90-120", "120以上"] df["area_bin"] = pd.cut(df["area"], bins=bins, labels=labels) print(df.groupby("area_bin")["unit_price"].median())5. 避坑与排查:采集和分析里最容易翻车的几件事
5.1 页面结构一变,选择器全废
现象:昨天还能跑出几百行,今天df.shape变成 (0, 5)。原因:目标站点改了 class 名或 DOM 结构。解决:把选择器抽成配置项,跑之前先打印soup.select("div.house-item")[:1]看有没有命中;更稳的做法是改用页面里的结构化数据块(如果存在),比纯 class 选择器抗改。
5.2 单价字段解析串位
现象:算出来的单价有的几万,有的几百。原因:正则抓到了总价或面积里的数字,字段错位。解决:解析后立刻做「总价 / 面积」反算校验,差异超过阈值的行直接剔除并打印出来人工看几条,确认是解析问题还是数据本身异常。
5.3 用挂牌价当成交价
现象:分析结论和实际成交感受对不上。原因:挂牌价普遍高于成交价,且高价房源挂牌时间更长,样本里占比偏高。解决:在结论里明确写「基于挂牌价」,如果要贴近成交,可以只取挂牌天数短、降价过的房源,或者用挂牌价乘一个经验折扣系数,但这个系数要按区域分别估,不能一刀切。
5.4 中文乱码和负号显示异常
现象:图表标题全是方框,负号变成方块。原因:matplotlib 默认字体不含中文。解决:设置font.sans-serif为系统里存在的中文字体,并设axes.unicode_minus=False。Windows 用 SimHei,macOS 可以试 Arial Unicode MS,Linux 先fc-list :lang=zh看有哪些。
5.5 样本偏差导致板块结论失真
现象:某板块均价明显低于预期。原因:该板块在售房源集中在某几个低价小区,或者采集时只抓到了低价段。解决:按小区分组看样本量,样本少于 5 套的小区不单独下结论;采集时按总价分段覆盖,别只抓默认排序的前几页。
6. 用线性回归给单价一个可解释的基准,再谈进阶
清洗完的数据可以直接喂给线性回归,目的不是预测得多准,而是看各因素对单价的方向和量级。用statsmodels比sklearn更适合,因为能直接看系数和显著性。
import statsmodels.api as sm features = ["area", "orient_score", "楼龄", "挂牌天数"] X = df[features].fillna(df[features].median()) X = sm.add_constant(X) y = df["unit_price"] model = sm.OLS(y, X).fit() print(model.summary())重点看三处:coef的符号是否符合常识(面积越大单价通常略降,楼龄越大单价越低),P>|t|小于 0.05 的才算显著,R-squared能到 0.5 以上就算不错,毕竟挂牌价里有很多页面抓不到的因素。如果某个区域样本足够,可以分区域各跑一个模型,系数差异本身就是有价值的结论。
进阶一点的做法,是把小区名做成固定效应,或者用sklearn的GradientBoostingRegressor看特征重要性,但可解释性会下降。我自己的习惯是:先用线性回归把基准和方向定下来,再用树模型看有没有非线性关系,两者结论冲突时优先信线性模型的系数方向,因为树模型在小样本上容易过拟合。
最后说个我踩过的坑:一开始我图省事,把所有区域混在一起建模,结果区域这个变量把其他因素的系数全带偏了。后来改成先分区域、再在区域内做回归,系数才稳定下来。做房价这类强地域性的数据,分层永远比一把梭靠谱。希望帮到你。
本文还有配套的精品资源,点击获取