☰
二手房数据采集清洗与可视化分析实战
2026/10/11 19:10:42 网站建设 项目流程

简介:本资源是一套完整的二手房数据采集与可视化分析毕业设计项目,面向计算机、电子信息工程、数学等专业的本科生,适用于课程设计、期末大作业及毕业设计参考。项目基于Python实现全流程闭环:从链家等平台爬取原始数据(含UTF8/ANSI双编码CSV)、清洗去重、特征工程,到构建交互式可视化看板(HTML+JS+ECharts)及多维度统计分析报告(PPTX),覆盖数据获取、处理、分析、呈现全环节。压缩包共155个文件,含18个核心Python脚本(含爬虫、清洗、分析模块)、18个CSV数据集(含原始、清洗后、采样版等多版本)、65张可视化图表PNG、15个HTML前端页面及配套JS、INI配置文件等,整体35.13MB,结构清晰、模块解耦,便于学习调试与功能扩展。目前已有841人学习下载,附带完整数据集、可运行源码、分析报告PPT及详细字段说明,适合具备Python基础、希望掌握真实业务场景下数据采集与分析实践能力的学生快速上手并完成高质量毕设交付。

1. 二手房数据采集与可视化分析:一个能跑通、能答辩、能改出新东西的毕业设计闭环

你花三天爬了链家、贝壳,结果发现反爬策略一升级,脚本就崩;你用 matplotlib 画了十张图,答辩老师皱着眉问“这能看出房价和学区的关系吗?”;你把清洗后的 CSV 丢进 pandas,df.describe()一跑,发现total_price列里混着“面议”“电话详谈”——这不是数据集,这是玄学现场。这个资源不是“Python 爬虫入门教程”,而是一套已实测跑通、含原始脏数据→清洗逻辑→特征工程→多维可视化→可扩展分析框架的完整毕业设计交付物。它包含真实采集的 20000 条二手房挂牌数据(含城市、区域、小区名、户型、面积、单价、总价、朝向、装修、楼层、建成年份、是否满五唯一等 18+ 字段),配套 Python 源码覆盖 requests + BeautifulSoup 基础采集、正则清洗、pandas 分箱处理、seaborn + plotly 交互图表、以及基于 sklearn 的简单价格预测 baseline。适合计算机、信工、统计类本科生直接复现答辩,也足够作为课程设计拓展基础——比如加个 Flask Web 展示页,或把price_per_sqm按地铁站 500 米缓冲区做空间聚合。它不承诺“一键生成论文”,但保证你从解压.rar开始,2 小时内看到第一张热力图跳出来。


2. 数据采集与清洗:为什么用 UTF-8 而不是 ANSI?三个关键清洗动作拆解

2.1 采集逻辑:requests + BeautifulSoup 的最小可行抓取链

项目中crawler.py并未使用 Selenium 或 playwright,而是基于 requests 构建轻量级采集链。核心在于绕过前端 JS 渲染干扰,直接请求真实 API 接口。以某城市链家为例,实际请求地址形如:

# crawler.py 片段 base_url = "https://bj.lianjia.com/ershoufang/" params = { "city": "bj", "page": 1, "limit": 30 } headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36" } response = requests.get(base_url, params=params, headers=headers, timeout=10)

注意:该代码未硬编码 cookie 或 token,依赖目标站未强制校验 Referer 或 X-Requested-With。若遇到 403,需在 headers 中补全Referer: https://bj.lianjia.com/ershoufang/。项目中ershoufang-origin-utf8.csv即由此逻辑导出,字段顺序与网页 DOM 结构严格对齐,避免因页面改版导致列错位。

2.2 编码陷阱:UTF-8 vs ANSI 的血泪经验

你打开ershoufang-origin-ansi.csv会发现中文全是乱码,而ershoufang-origin-utf8.csv正常——这不是文件本身问题,是采集时响应头未显式声明编码。requests 默认按ISO-8859-1解码,当服务器返回Content-Type: text/html; charset=gbk时,必须手动指定:

response.encoding = response.apparent_encoding # 自动探测 # 或更稳妥写法: response.encoding = 'gbk' if 'gbk' in response.headers.get('content-type', '').lower() else 'utf-8'

项目中clean_data.py开头即强制统一读取为 UTF-8:

import pandas as pd df = pd.read_csv("ershoufang-origin-utf8.csv", encoding="utf-8", dtype=str) # 全部读为字符串,防数字转科学计数法

提示:dtype=str是关键。否则area列若含“100.0㎡”,pandas 会自动转为 float,后续str.replace("㎡", "")报错。所有字段先保字符串,清洗完成再pd.to_numeric(..., errors='coerce')转数值。

2.3 清洗三板斧:正则提取、分箱归一、缺失值策略

清洗不是删空行,而是构建可复用的转换管道。clean_data.py中定义了clean_pipeline()函数,核心三步:

  1. 价格字段标准化:
    total_price含“320万”“面议”“暂无报价”,用正则提取数字并统一为万元单位:

    import re def extract_price(text): if pd.isna(text) or "面议" in str(text) or "暂无" in str(text): return np.nan match = re.search(r'(\d+\.?\d*)[万]', str(text)) return float(match.group(1)) if match else np.nan df["total_price_wan"] = df["total_price"].apply(extract_price)
  2. 面积字段去单位:
    area列含“100.5㎡”“89平”“约75平米”,统一提取数字并转 float:

    df["area_sqm"] = df["area"].str.extract(r'(\d+\.?\d*)').astype(float)
  3. 楼层字段结构化:
    floor列为“高楼层(共32层)”“低楼层(共18层)”“中楼层(共26层)”,拆解为floor_level(高/中/低)和total_floors(总层数):

    df[["floor_level", "total_floors"]] = df["floor"].str.extract(r'(高|中|低)楼层.*?(\d+)层') df["total_floors"] = pd.to_numeric(df["total_floors"], errors='coerce')

参数说明:errors='coerce'将无法转数字的值设为 NaN,而非报错中断。这是生产级清洗的底线——宁可丢数据,不可崩流程。


3. 可视化分析:从静态图到交互式仪表盘的四层递进

3.1 基础分布:用 seaborn 绘制带统计信息的直方图

eda_basic.py首先验证数据质量。以price_per_sqm(每平米单价)为例,绘制带 KDE 曲线和统计摘要的直方图:

import seaborn as sns import matplotlib.pyplot as plt plt.figure(figsize=(10, 6)) ax = sns.histplot(df["price_per_sqm"].dropna(), kde=True, bins=50, color="steelblue") ax.set_title("二手房单价分布(元/㎡)", fontsize=14) ax.set_xlabel("单价(元/㎡)") ax.axvline(df["price_per_sqm"].median(), color='red', linestyle='--', label=f'中位数: {df["price_per_sqm"].median():.0f}') ax.legend() plt.show()

逻辑说明:kde=True叠加核密度估计曲线,比单纯直方图更能反映分布形态;axvline标出中位数,避免均值被极端高价扭曲。此图直接暴露数据偏态——若右偏严重(如出现 20 万+/㎡ 天价房),需在报告中说明剔除逻辑。

3.2 区域对比:用 boxplot 揭示房价离散度

region_analysis.py对比不同行政区房价差异。关键不是柱状图,而是箱线图(boxplot):

plt.figure(figsize=(12, 8)) sns.boxplot(data=df, x="district", y="price_per_sqm", order=df["district"].value_counts().index[:8]) plt.xticks(rotation=45) plt.title("各行政区单价箱线图(前8名)", fontsize=14) plt.ylabel("单价(元/㎡)") plt.tight_layout() plt.show()

为什么选 boxplot?

  • 箱体高度 = IQR(四分位距),反映价格离散程度;
  • 须线长度 = 1.5×IQR,超出者为异常值(如某区出现 10 万+/㎡ 孤立点);
  • 中位数横线位置,直观比较各区“典型价格”。
    若某区箱体窄且中位数高(如海淀),说明学区房价格稳定且昂贵;若箱体宽(如朝阳),则刚需与豪宅并存。

3.3 多维关联:用 scatterplot_matrix 展示变量关系

corr_analysis.py不止计算相关系数矩阵,更用sns.pairplot可视化关键变量组合:

# 选取业务强相关字段 key_cols = ["area_sqm", "total_price_wan", "price_per_sqm", "year_built", "floor_level"] g = sns.pairplot(df[key_cols].dropna(), hue="floor_level", palette="Set2", diag_kind="hist") g.fig.suptitle("核心变量两两关系图", y=1.02) plt.show()

参数说明:hue="floor_level"用颜色区分楼层类型,diag_kind="hist"在对角线上显示单变量分布。观察area_sqmvstotal_price_wan散点图,若呈明显线性趋势,说明总价主要由面积驱动;若year_builtvsprice_per_sqm出现 U 型(老房与新房贵,次新房便宜),则暗示“学区老破小”和“改善型次新”双轨行情。

3.4 交互式仪表盘:用 plotly express 快速生成可筛选图表

dashboard.py使用 plotly express 构建交互式看板,支持下拉筛选区域、滑块调节面积范围:

import plotly.express as px fig = px.scatter( df, x="area_sqm", y="price_per_sqm", color="district", size="total_price_wan", hover_data=["community", "house_type"], title="面积-单价散点图(按区域着色,圆圈大小=总价)" ) fig.update_layout(height=600) fig.show()

价值点:答辩时鼠标悬停即可显示小区名、户型,点击图例可隐藏某区数据,比静态图更具说服力。部署时只需fig.write_html("dashboard.html"),无需 Flask 后端。


4. 预测建模与报告落地:从 baseline 到答辩话术的实战衔接

4.1 价格预测 baseline:用 sklearn 实现可解释的线性回归

model_price.py不追求复杂模型,而是构建可解释、易答辩的 baseline:

from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.preprocessing import StandardScaler from sklearn.metrics import mean_absolute_error, r2_score # 特征工程:构造衍生变量 df["age"] = 2024 - df["year_built"] # 房龄 df["is_old"] = (df["age"] > 20).astype(int) # 是否老房 X = df[["area_sqm", "age", "is_old", "total_floors"]] y = df["price_per_sqm"] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) model = LinearRegression() model.fit(X_train_scaled, y_train) y_pred = model.predict(X_test_scaled) print(f"MAE: {mean_absolute_error(y_test, y_pred):.0f} 元/㎡") print(f"R²: {r2_score(y_test, y_pred):.3f}") print("特征系数:", dict(zip(X.columns, model.coef_)))

答辩话术重点:

  • “R²=0.62 说明 62% 的单价波动可由面积、房龄、总楼层解释”;
  • “面积系数为正(+3200),即每增 1 ㎡,单价升 3200 元,符合市场认知”;
  • “房龄系数为负(-180),说明房龄每增 1 年,单价降 180 元,体现折旧效应”。

4.2 PPT 报告结构:三页讲清技术深度

项目附带的report.pptx并非模板堆砌,而是紧扣答辩评委关注点设计:

页码内容要点技术细节
P3 数据采集展示crawler.py关键代码截图 + 请求成功率统计(98.2%)标注 headers 中 User-Agent 和 Referer 的必要性,说明未用 Selenium 降低资源消耗
P5 清洗逻辑用流程图展示“原始文本 → 正则提取 → 类型转换 → 异常值标记”四步强调errors='coerce'的鲁棒性设计,对比清洗前后price_per_sqm缺失率(从 12.7% 降至 0.3%)
P7 可视化结论左图:海淀区单价箱线图(中位数 8.2 万);右图:朝阳区散点图(面积与总价强相关)标注图中异常值坐标(如某朝阳楼盘单价 15 万+),引出“高端改善盘”细分市场

提示:答辩时切忌念 PPT。指着箱线图说:“您看海淀箱体窄、中位数高,说明学区房价格共识强;而朝阳箱体宽,意味着同样面积,有 4 万一平的老破小,也有 12 万一平的江景豪宅——这正是我们后续可做的聚类分析方向。”

4.3 毕业设计加分项:两个低成本高价值扩展点

不必重写全部代码,两个微调即可提升项目深度:

  1. 加入地理信息:
    用geopy根据小区名获取经纬度,再用folium绘制热力图:

    from geopy.geocoders import Nominatim geolocator = Nominatim(user_agent="ershoufang") location = geolocator.geocode("北京市海淀区中关村南一街1号") print(location.latitude, location.longitude) # 输出:39.985, 116.315

    注意:Nominatim 有调用频率限制,建议先批量查好存入community_geo.csv,避免答辩演示时卡顿。

  2. 增加时间维度:
    若采集多月数据,可计算各小区月度均价变化率,用plotly.graph_objects.Scatter绘制趋势线:

    fig.add_trace(go.Scatter(x=df_month["date"], y=df_month["avg_price"], name="中关村某小区"))

5. 避坑指南:五个让答辩老师点头、让调试不崩溃的真实问题

5.1 现象:pandas.read_csv()报错UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd0

  • 原因:文件实际为 GBK 编码,但代码强制指定encoding="utf-8"。常见于ershoufang-origin-ansi.csv。
  • 解决:先用chardet库探测编码:
    import chardet with open("ershoufang-origin-ansi.csv", "rb") as f: rawdata = f.read(10000) encoding = chardet.detect(rawdata)["encoding"] # 输出 'GBK' df = pd.read_csv("ershoufang-origin-ansi.csv", encoding=encoding)

5.2 现象:seaborn.boxplot()报错ValueError: object of too small depth for desired array

  • 原因:district列含大量空值或特殊字符(如“\xa0”不间断空格),导致value_counts()返回空 Series。
  • 解决:清洗时统一处理空格和空值:
    df["district"] = df["district"].str.strip().replace("", np.nan) df = df.dropna(subset=["district"])

5.3 现象:plotly.express.scatter()生成的 HTML 文件打开空白

  • 原因:plotly 默认使用 CDN 加载 JS 库,内网环境或禁用网络时无法加载。
  • 解决:离线模式导出:
    fig.write_html("dashboard.html", include_plotlyjs='cdn') # 改为 include_plotlyjs='directory' # 或更彻底:include_plotlyjs='https://cdn.plot.ly/plotly-latest.min.js'(需联网)

5.4 现象:LinearRegression训练后model.coef_全为 0

  • 原因:特征X中存在全零列(如total_floors有大量 NaN,fillna(0)后整列变 0),导致矩阵奇异。
  • 解决:检查特征矩阵秩:
    import numpy as np print(np.linalg.matrix_rank(X_train_scaled)) # 应等于 X_train_scaled.shape[1] # 若不等,用 df.dropna() 或删除低方差列

5.5 现象:答辩演示时geopy地理编码超时或返回 None

  • 原因:Nominatim 服务限流(1 秒/次),且中文地址解析准确率低于英文。
  • 解决:预处理 + 备用方案:
    # 1. 先用百度地图 API(需申请 key)批量获取 # 2. 若失败,回退到规则匹配:df["district"].map({"海淀":"39.985,116.315", ...}) # 3. 最终缺失值用区域中心点填充

6. 从那以后我每次开新项目,都强制走一遍“三验流程”

你拿到这个.rar包,解压后看到十几个 CSV 和 Python 文件,第一反应可能是“先跑main.py看效果”。但我的血泪经验是:任何毕业设计源码,在运行前必须完成“三验”——验编码、验路径、验依赖。这不是多此一举,而是避免在答辩前夜发现pandas版本冲突导致read_csv行为突变。

6.1 验编码:用file命令一眼识别文件真实编码(Linux/macOS)

file -i ershoufang-origin-utf8.csv # 输出:ershoufang-origin-utf8.csv: text/plain; charset=utf-8 file -i ershoufang-origin-ansi.csv # 输出:ershoufang-origin-ansi.csv: text/plain; charset=iso-8859-1

为什么不用 Notepad++ 查?因为 GUI 工具可能缓存旧编码标识。file命令读取文件二进制头,结果绝对可信。若charset=unknown,立即用iconv -f gbk -t utf-8 input.csv > output.csv转换。

6.2 验路径:用os.path.abspath()打印所有数据路径

在clean_data.py开头插入:

import os print("当前工作目录:", os.getcwd()) print("ershoufang-origin-utf8.csv 绝对路径:", os.path.abspath("ershoufang-origin-utf8.csv"))

玄学时刻:曾有学生把项目从 D 盘移到 E 盘,pandas.read_csv("data/ershoufang.csv")突然报错FileNotFoundError。打印绝对路径后发现,他误将data文件夹放在了父目录,而代码在子目录执行——路径相对基准错了。绝对路径是唯一真理。

6.3 验依赖:用pipreqs生成精准 requirements.txt

不要手写requirements.txt,用工具自动生成:

pip install pipreqs pipreqs ./ --encoding=utf8 --force

参数说明:--encoding=utf8避免中文注释报错;--force覆盖已有文件。生成的requirements.txt会精确列出pandas==1.5.3,seaborn==0.12.2等版本,而非笼统的pandas>=1.0。答辩前在干净虚拟环境中pip install -r requirements.txt,确保环境可重现。

最后说个习惯:我给每个毕业设计项目建一个checklist.md,里面只写三件事——
✅ 已验证ershoufang-clean-utf8-v1.1.csv能被pandas正确读取,price_per_sqm列无字符串;
✅ 已确认dashboard.py导出的 HTML 在 Chrome/Firefox/Edge 均可交互;
✅ 已备份report.pptx为 PDF,防止答辩电脑 Office 版本不兼容字体。

这三件事做完,答辩前一晚才能安心睡觉。希望帮到你。

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

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

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

立即咨询