Python二手房数据爬虫与可视化分析实战
2026/9/16 4:22:48 网站建设 项目流程

简介:一份面向毕业设计或课程项目的Python网络爬虫与数据分析实战资源包,聚焦二手房市场数据的采集、清洗与可视化分析,适合需要快速搭建完整项目的计算机或数据科学方向学生。压缩包共158个文件,约40MB,核心包含18个Python脚本、18个CSV原始及清洗数据集、15个HTML可视化页面、65张PNG分析图表和1份答辩PPT,另有配置、文本和图像等辅助资源,目录按代码、数据、文档清晰划分。目前已有52人学习。项目代码覆盖Requests或Scrapy发起请求、BeautifulSoup与lxml解析网页,提取位置、面积、价格、描述等字段;随后通过数据清洗去除空值与异常记录,再使用matplotlib和seaborn绘制柱状图、饼图、散点图,呈现价格分布、区域热度及房屋类型占比等关键信息。PPT文档系统梳理项目背景、技术选型、实现流程与最终成果;学习者可参照源代码改造目标网站或数据字段,直接用于毕业设计、课程报告或数据分析入门实践。

1. 毕业设计选爬虫+可视化,真正的工程量在哪

很多人看到“Python网络爬虫收集和可视化分析二手房数据”这个题目,第一反应是“这不就是requests加BeautifulSoup,爬到数据画几张图就完了”。实际做过一轮毕业设计或项目实战的人都知道,真正的工程量不在于“能不能爬到”,而在于“爬下来之后怎么变成能讲清楚结论的数据”。房源字段乱、价格单位不统一、经纬度缺失、重复数据、区域划分不规则,这些才是让一个项目从“演示脚本”变成“完整毕业设计”的分水岭。这篇内容会按我从零搭一个二手房数据项目的习惯路径来走:先定数据字段与存储结构,再写爬虫并处理反爬,接着清洗落库,然后做可视化和结论提炼,最后把代码和PPT串成能答辩的交付物。整个过程基于Python 3.10+环境,适合直接照着改。

2. 用requests+BeautifulSoup搭二手房爬虫:解析、反爬与数据字段设计

2.1 为什么不用Scrapy起步:毕业设计的规模和可控性

Scrapy是高性能爬虫框架,但对于二手房这种单城市、单网站、字段不复杂的场景,它引入了Twisted异步引擎、Item Pipeline、Middleware等概念,学习成本和代码量反而比requests方案高。我一般的做法是:requests负责网络请求,BeautifulSoup负责解析HTML,配合time.sleep控制请求频率,再加一层简单的User-Agent轮换。这样整个爬虫部分只有两个文件,逻辑透明,答辩时被问到每个函数都能说清楚。

2.2 最小可运行的爬虫代码结构

下面的代码实现了一个基础爬虫:输入一个城市和一个区域前缀,抓取该区域内所有二手房的标题、小区名、户型、面积、朝向、装修、楼层、总价和单价。

import time import random import requests from bs4 import BeautifulSoup HEADERS_POOL = [ {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}, {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"}, ] def fetch_page(url: str, retry: int = 3) -> str: for attempt in range(retry): headers = random.choice(HEADERS_POOL) try: resp = requests.get(url, headers=headers, timeout=10) if resp.status_code == 200: return resp.text elif resp.status_code == 403: time.sleep(5) else: resp.raise_for_status() except requests.RequestException as e: print(f"请求失败,第{attempt + 1}次重试: {e}") time.sleep(2) return "" def parse_house_list(html: str): soup = BeautifulSoup(html, "html.parser") items = soup.select("div.info") # 列表页房源信息容器 for item in items: title_tag = item.select_one("div.title a") flood_tag = item.select_one("div.flood") price_tag = item.select_one("div.totalPrice span") if not title_tag or not price_tag: continue yield { "title": title_tag.get_text(strip=True), "href": title_tag["href"], "flood": flood_tag.get_text(strip=True) if flood_tag else "", "total_price": float(price_tag.get_text(strip=True)), } url = "https://example-city.lianjia.com/ershoufang/pg1/" html = fetch_page(url) if html: for house in parse_house_list(html): print(house)

代码逻辑说明:fetch_page采用随机请求头和重试机制,retry=3表示单页最多请求3次,每次失败后等2秒再试,这样能处理偶发的超时和连接重置。parse_house_list通过CSS选择器定位列表页的房源条目,使用select_one精确抓取标题、链接、位置和总价。为什么用select而不是find_all,因为select支持CSS选择器语法,写复杂层级时更简洁。

注意,示例中的URL是占位地址,实际使用时要替换为具体城市的域名。二手房列表页会分页,一般URL中的pg1pg2是页码标记,可以循环构造分页URL,直到当前页没有数据时停止。

2.3 单页解析到详情页补全:哪些字段必须进详情页拿

列表页通常只给总价、小区名和基础信息,面积、户型、朝向、楼层、年代、产权年限这些字段要到详情页才有。所以爬虫要分成两层:第一层遍历列表页收集详情页链接,第二层逐个请求详情页并解析字段。

def parse_detail(html: str) -> dict: soup = BeautifulSoup(html, "html.parser") intro = soup.select_one("div.introContent") items = {} if intro: for li in intro.select("li"): text = li.get_text(" ", strip=True) if ":" in text: key, value = text.split(":", 1) items[key.strip()] = value.strip() price_tag = soup.select_one("span.total") items["total_price"] = float(price_tag.get_text(strip=True)) if price_tag else 0 return items

这里把详情页里“房屋户型:3室2厅”“所在楼层:中楼层”这类键值对统一拆进字典。后面所有字段清洗都会基于这个字典操作。要注意不同网站详情页的键名不一致,可能叫“房屋户型”也可能叫“户型结构”,写代码之前先手动打开两三套详情页看结构,比反复猜测标签更省时间。

2.4 反爬策略的三个必要配置

二手房网站对爬虫的检测通常集中在三个层面:请求头识别、请求频率识别、行为轨迹识别。我一般会做以下配置,这也是三个必调参数:

参数/策略推荐取值说明
请求间隔2~5秒随机time.sleep(random.uniform(2, 5))避免固定节奏
User-Agent池至少5个不同UA包含Windows/Mac主流浏览器的UA即可
重试退避403后等待5秒再重试固定次数不如固定等待时间有效

提示:如果爬取过程中出现大量403且重试无效,先停掉脚本,用浏览器手动访问目标页面确认是否正常。频繁爬取会导致IP被临时封禁,这时候等待几小时再继续比换UA更有效。

def crawl_with_interval(urls): for u in urls: html = fetch_page(u) if html: new_rows = parse_detail(html) save_rows(new_rows) time.sleep(random.uniform(2, 5))

这段代码的核心思想是把“请求-解析-存储”放进一个带节流控制的循环。random.uniform(2, 5)保证每次请求间隔不同,在服务端看来像真实用户的浏览节奏。

3. 数据清洗与结构化:让爬下来的字段变成可分析的DataFrame

3.1 清洗的四个标准动作:单位统一、空值处理、类型转换、去重

爬下来的数据不能直接用于分析,至少要做四件事。第一,总价和单价要转成数值类型,很多原始字段是“450万”“52673元/平”这种带单位文本,必须提取数字。第二,面积字段统一为平方米的浮点数。第三,“近地铁”“满五年”这类布尔信息要转成0/1。第四,同一套房源可能被多次抓取,需要按详情页链接做去重。

import re import pandas as pd def clean_total_price(value: str) -> float: match = re.search(r"(\d+\.?\d*)", value) return float(match.group(1)) if match else None def clean_unit_price(value: str) -> float: match = re.search(r"(\d+\.?\d*)", value.replace(",", "")) return float(match.group(1)) if match else None df["total_price"] = df["total_price"].map(clean_total_price) df["unit_price"] = df["unit_price"].map(clean_unit_price) df["area"] = df["area"].astype(float) df["follow_info"] = df["follow_info"].str.extract(r"(\d+)").astype(float) df = df.drop_duplicates(subset=["href"], keep="first")

逻辑说明:clean_total_price用正则从“450万”里提取450,clean_unit_price把“52,673元/平”中的逗号去掉再提取数字。follow_info是“34人关注”这类文本,提取数字后变成数值列。drop_duplicates按详情页链接去重,同一房源即使被重复抓取,也只保留第一条。

3.2 字段类型推断与异常检测

清洗过程中最容易忽略的是字段类型不匹配。比如面积字段里混入了“暂无数据”这样的字符串,astype(float)会直接抛异常。我建议清洗时分两步:先看类型分布,再处理异常。

print(df.dtypes) print(df["area"].value_counts().head(20)) print(df[df["area"].isna()])

这一步更重要的是发现脏数据形态。举例来说,面积字段里的“暂无数据”值在value_counts里能直接看到。如果该类占比低于2%,直接删除;高于2%,说明爬虫解析详情页时漏掉了字段,应该回去检查详情页解析逻辑,而不是在清洗阶段硬扛。

3.3 存储选SQLite还是CSV:毕业设计的合理选择

数据量在1万条以内,CSV完全够用;超过3万条或者需要做多表关联,SQLite更合适。我一般先用CSV做探查,等字段稳定后再导入SQLite。这样做的原因是CSV直接用pandas读取,探查性分析省去写SQL的时间,而SQLite适合答辩时展示“数据能持久化存储”这一环节。

import sqlite3 conn = sqlite3.connect("house_data.db") df.to_sql("house_info", conn, if_exists="replace", index=False) conn.commit() conn.close()

这段代码把清洗后的DataFrame直接写入SQLite的house_info表。if_exists="replace"表示如果表已存在就替换,适合重复跑清洗流程时保持幂等。注意to_sql默认会保留DataFrame的行索引,这里index=False明确不写索引列。

3.4 必查的3个数据质量指标

清洗完以后不是直接进入可视化,要先跑三个校验:第一,总价最小值是否大于0;第二,单价最低值是否异常低,比如低于3000元/平,大概率是地下室或车位混入;第三,面积异常大的记录,超过500平米的多为别墅或商业性质房源,分析住宅均价时需要单独标记。

assert df["total_price"].min() > 0 print(df["unit_price"].describe()) print(df[df["area"] > 500].head())

describe()看单价分位数能快速判断是否有极端值,比如95分位和99分位之间的跨度是否过大。如果99分位单价是95分位的3倍以上,说明存在豪宅房源影响整体均值,后续可视化里应该用中位数而非均值。

4. 用Matplotlib和Pyecharts做二手房可视化分析:从价格分布到区域对比

4.1 分析框架:先定问题再看图表,而不是先画图再想结论

可视化是手段,不是目的。毕业设计答辩时最常被追问的是“你为什么选这张图来表达这个结论”。我习惯把分析拆成四个问题:整体房价水平如何、不同区域之间差距多大、面积和总价之间相关性如何、楼层和装修是否影响单价。每个问题对应一张主图和一张辅助图就够了,总量控制在6到8张图,比堆十几张无效图表更有说服力。

4.2 房价分布与区域对比的核心代码

import matplotlib.pyplot as plt import pandas as pd plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"] plt.rcParams["axes.unicode_minus"] = False df = pd.read_csv("house_clean.csv") fig, axes = plt.subplots(1, 2, figsize=(12, 4)) axes[0].hist(df["total_price"], bins=30, color="#4C72B0", edgecolor="white") axes[0].set_title("二手房总价分布") axes[0].set_xlabel("总价(万元)") axes[0].set_ylabel("房源数量") avg_price_by_region = df.groupby("region")["unit_price"].median().sort_values(ascending=False) avg_price_by_region.plot(kind="bar", ax=axes[1], color="#DD8452") axes[1].set_title("各区域单价中位数") axes[1].set_xlabel("区域") axes[1].set_ylabel("单价(元/平)") plt.tight_layout() plt.savefig("analysis_1_price.png", dpi=150)

代码逻辑:左侧图用hist展示总价分布,bins=30表示把数据分成30个区间,太密会看到毛刺,太疏会掩盖峰值。右侧图用groupby按区域求单价中位数,这里用中位数而不是均值,原因是二手房单价受豪宅影响大,均值可能被拉高,中位数更代表普通住宅水平。figsize=(12, 4)控制两个子图横排的总尺寸,dpi=150保证导出图片在PPT里够清晰。

4.3 Pyecharts做交互式图表:区域均价与户型占比

Matplotlib适合放在论文里,Pyecharts适合放在PPT和演示环境里。Pyecharts生成的是HTML文件,可以在浏览器里缩放、悬停查看数值。我一般用它画两张图:一张是区域均价柱状图,一张是户型占比饼图。

from pyecharts.charts import Bar, Pie from pyecharts import options as opts bar = ( Bar() .add_xaxis(avg_price_by_region.index.tolist()) .add_yaxis("单价中位数(元/平)", avg_price_by_region.round(0).tolist()) .set_global_opts( title_opts=opts.TitleOpts(title="各区二手房单价中位数对比"), yaxis_opts=opts.AxisOpts(name="元/平方米"), ) ) bar.render("region_price_bar.html") room_counts = df["room_type"].value_counts().head(5) pie = ( Pie() .add("", [list(z) for z in zip(room_counts.index, room_counts.values)]) .set_global_opts(title_opts=opts.TitleOpts(title="户型占比 Top5")) .set_series_opts(label_opts=opts.LabelOpts(formatter="{b}: {d}%")) ) pie.render("room_type_pie.html")

formatter="{b}: {d}%"的含义是标签显示户型名称和百分比占比。这里Pyecharts的链式调用风格提前把数据算好再传入,避免在模板里做运算。set_series_opts里的label_opts控制饼图标签格式,这是Pyecharts里最容易漏掉的一个参数。

4.4 面积与总价的回归分析:用散点加趋势线

面积和总价的关系几乎是二手房分析的必做项。我会画一张散点图,叠加一条线性拟合线,直观展示“面积每增加一平方米,总价平均增加多少”。

import numpy as np from scipy import stats df_valid = df[(df["area"] > 20) & (df["area"] < 300)] x = df_valid["area"] y = df_valid["total_price"] slope, intercept, r_value, p_value, std_err = stats.linregress(x, y) plt.figure(figsize=(8, 5)) plt.scatter(x, y, alpha=0.4, s=10) line_x = np.linspace(x.min(), x.max(), 100) plt.plot(line_x, slope * line_x + intercept, color="red", linewidth=2) plt.title(f"面积与总价关系(R² = {r_value ** 2:.2f})") plt.xlabel("面积(平方米)") plt.ylabel("总价(万元)") plt.savefig("analysis_2_area_price.png", dpi=150)

linregress是scipy里的线性回归函数,返回斜率、截距、相关系数等。alpha=0.4控制散点透明度,数据点密集时不至于叠成黑色一团。s=10是散点大小,默认大小在几千个点时看起来过于醒目,调小一点更清爽。R²的值能直接回答“面积能否解释总价的变化”,低于0.5时要补充说明还有楼层、位置等其他因素。

4.5 图表在PPT里怎么摆:结论先行,图表做证据

答辩PPT里图表的排版逻辑通常是:左上角放关键结论的粗体字,右侧或下方放图表,图表的标题必须是结论而不是描述。比如“近地铁房源单价中位数高出约18%”是结论,而“地铁房与非地铁房单价对比”只是描述。图表标题直接写结论,能让评审老师在10秒内抓到重点。

5. 毕业设计交付:源代码组织、PPT展示结构与答辩准备

5.1 源代码与PPT文档的目录组织

交付物里“包含源代码与PPT文档”意味着评阅老师会直接打开项目结构来看,目录组织本身就是评分点。我建议按下面的结构整理:

house-price-project/ ├── data/ │ ├── raw/ # 爬虫原始输出 │ └── clean/ # 清洗后CSV和SQLite ├── crawler/ │ ├── spider.py # 列表页爬虫 │ ├── detail.py # 详情页爬虫 │ └── settings.py # 请求头、延迟、页码配置 ├── analysis/ │ ├── clean_data.py # 清洗脚本 │ ├── plot_price.py # Matplotlib图 │ └── interactive.py # Pyecharts图 ├── docs/ │ ├── 开题报告.md │ ├── 答辩PPT.pptx │ └── 论文.md └── requirements.txt

设置项单独放一个settings.py,是毕业设计代码里很加分的习惯。把UA池、请求间隔、目标URL、起始页码这些集中管理,老师要改参数时不需要翻爬虫主文件。requirements.txt里固定关键依赖版本,避免答辩换电脑后因版本差异跑不起来。

5.2 PPT文档的黄金结构:背景、方法、结果、验证

一份毕业设计答辩PPT,控制在12到15页,每一页只讲一个信息点。我习惯把重点放在“数据是怎么清洗的”和“可视化揭示了什么结论”上,而不是放大量爬虫截图。爬虫代码有200行就截200行放进PPT是常见错误,老师更想看到的是你如何定义字段、如何保证数据质量、如何从图表得出可验证的结论。

第1页:标题页(题目、姓名、学号、指导老师) 第2页:研究背景与选题动机 第3页:技术选型对比(requests vs Scrapy) 第4页:数据结构设计(字段说明表) 第5页:爬虫架构图(列表页/详情页/频率控制) 第6页:数据清洗流程 第7页:数据质量校验结果 第8页:可视化分析1(价格分布) 第9页:可视化分析2(区域对比) 第10页:可视化分析3(面积与价格回归) 第11页:主要结论 第12页:不足与改进方向 第13页:源码结构说明 第14页:致谢

5.3 讲清代码的边界条件:答辩提问重点

答辩老师最常问的方向有三个:爬虫被封怎么办、数据量不够怎么分析、图表结论是否可信。对应答案是:被封后优先降低请求频率并等待解封,不鼓励绕过验证码;数据量不足时用5000条以上数据做区域聚合,而不是逐条展示;图表结论需要补充样本量的说明,例如“本样本共5321条房源,其中医院附近占比18%”。

提示:在PPT最后一页源码结构说明里,放一张tree /F命令生成的目录截图,比文字描述项目组织更直观。如果答辩环境是Windows,用tree /F > structure.txt导出目录再贴进PPT。

5.4 演示环境的三个必做检查

答辩当天跑代码演示前,确认三件事:第一,目标网站仍能正常访问,爬虫示例的URL是否有效;第二,data目录下的CSV文件存在且非空;第三,Pyecharts生成的HTML文件用默认浏览器能打开。现场网络故障时不要试图爬新数据,直接展示已有的清洗结果和图表即可,这份保险能避免大部分演示事故。

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

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

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

立即咨询