news 2026/9/16 4:25:47

Python爬虫与数据可视化:二手房毕业设计实战全流程解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python爬虫与数据可视化:二手房毕业设计实战全流程解析

简介:一份面向毕业设计的Python二手房数据采集与分析项目,压缩包内含完整源码和PPT演示文档。项目从真实网页中抓取房源信息,覆盖请求发送、页面解析、字段提取、数据清洗、统计分析与可视化展示等环节;通过爬虫框架高效获取位置、面积、户型、单价、总价等字段,并对空值、异常值进行处理。分析阶段基于清洗后的数据完成区域供求、价格水平、户型结构等统计,借助常用可视化库绘制分布图、对比图和占比图,直观反映市场特征。压缩包共158个文件、约40MB,主要包含18个爬虫脚本、18个CSV数据文件(含原始与清洗后多版本)、15个HTML展示页面、65张PNG结果图表和1份PPT演示文档。源码模块划分清晰,便于二次修改和学习;PPT对项目背景、实现过程和最终成果做了系统梳理,可直接用于毕业设计汇报。已有52人学习/下载,适合计算机相关专业学生作为课程设计或毕设参考,也可供爬虫与数据分析初学者动手实践。

1. 毕业设计选 Python 爬虫做二手房数据可视化,别只看房价

链家和贝壳这类平台上,一个城市几万套挂牌房源字段高度结构化:小区、户型、面积、朝向、楼层、总价、单价、关注人数。这个数据量级对爬虫和可视化来说,既不像电商评论那样需要处理反爬极强的验证码,也不像微博热搜那样字段混乱,恰好适合在毕业设计周期内从零到一完整跑通。很多人以为这个题目的核心是“爬得多快”,实际上在答辩现场,评委更在意的是原始数据是否干净、分析结论是否自洽、图表能不能回答“这个城市的二手房价格到底受什么因素影响”。

这篇东西按我自己做类似项目的路径来讲:从 Python 环境与虚拟环境配置开始,到爬虫模块的字段设计、反爬参数调整,再到用 Pandas 做清洗、用 Matplotlib 和 PyECharts 做静态与交互可视化,最后落在 PPT 演示和论文配图上。源码和文档的组织方式会穿插在各章里,因为毕业设计交的不只是代码,还要让老师能顺着目录看懂你在每一步做了什么。

2. 开发环境与源代码组织:Python 版本、虚拟环境和项目结构

很多人拿到“含源代码”的参考项目后,第一件事就是双击运行,结果报错一堆 ModuleNotFoundError。原因通常不在代码本身,而是解释器版本和依赖库版本不匹配。先说清楚环境怎么搭,再谈怎么组织代码,这样后面写爬虫和画图时不会因为环境问题反复打断。

2.1 Python 安装与 VS Code 环境配置

Python 版本建议直接上 3.10 或 3.11。3.12 之后的版本对部分第三方库的二进制 wheels 支持晚一些,遇到 lxml、pandas 装不上时反而浪费时间。官网下载安装包时勾选 “Add Python to PATH”,这一步能省掉后面一堆“python 不是内部或外部命令”的报错。

python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate pip install requests beautifulsoup4 lxml pandas matplotlib pyecharts flask

激活后命令行前缀会出现(venv),说明当前 shell 已经切到虚拟环境。这样做的意义是:不同项目依赖互相隔离,毕业设计文件夹打包给老师时,只要附带 requirements.txt,对方在新机器上pip install -r requirements.txt就能复现环境,避免“在我电脑上能跑”的尴尬。

requests 用于发起 HTTP 请求,获取房源列表页和详情页 HTML beautifulsoup4 解析 HTML 并提取字段 lxml BeautifulSoup 的解析器,比 Python 内置 html.parser 快数倍 pandas 数据清洗、过滤、分组聚合 matplotlib 生成论文里用的静态图表(柱状图、散点图、直方图) pyecharts 生成可交互的 HTML 图表,放入毕设系统展示更出效果 flask 可选,用来把图表和数据包装成一个本地可视化看板

VS Code 里按Ctrl+Shift+P打开命令面板,输入 “Python: Select Interpreter”,选刚才那个 venv 路径下的解释器。这一步不做的后果是:终端里明明安装了包,编辑器右下角却一直报红色波浪线,因为 IDE 还指着系统中另一个 Python。环境变量 VIRTUAL_ENV 的值能帮你确认当前终端是否真的在虚拟环境内,打印一下就知道。

2.2 毕业设计源代码如何按功能拆分

很多毕业设计代码是个几百行的单文件,从 import 到爬虫再到画图全塞一起。这种写法自己调试没问题,写论文时却很难组织章节:第三章讲数据采集、第四章讲数据处理、第五章讲可视化,代码里却没有对应模块,只能硬生生在论文里贴大段代码,答辩效果很差。

second_hand_house/ ├── config.py # 请求头、目标城市、基础 URL、延迟区间 ├── requirements.txt # 依赖清单 ├── spiders/ │ ├── collector.py # 请求列表页、处理翻页和重试 │ └── parser.py # 解析 HTML、提取房源字段、返回结构化字典 ├── analysis/ │ ├── clean.py # 清洗 CSV、类型转换、异常值过滤 │ └── visualize.py # 生成静态图和交互图 ├── data/ │ └── houses.csv # 爬取结果 └── README.md # 运行步骤和模块说明

config.py 单独放的意义是:请求头、目标城市、爬取页数这些“会变的东西”和“逻辑代码”分离。比如今天爬杭州,明天改成南京,只需要改 config.py 里的城市拼音和 URL 模板,不用动爬虫逻辑。我在实际项目中会额外加一个delay_range = (1, 3),每次请求之间随机 sleep,既是对目标网站的基本尊重,也是减少 IP 被临时限制的有效手段,后面爬虫章节会细说。

源代码里还要注意路径兼容。用Path(__file__).parent.parent / "data"来定位文件,而不是写死C:/Users/xxx/...,换机器运行才不容易报 FileNotFoundError。

3. 二手房数据采集:从网络爬虫原理到字段解析与翻页

第一个版本别急着上 Scrapy。Scrapy 功能强,但它的信号机制、中间件、Item Pipeline 对初学者来说是个不小的陡坡,而且调试起来不如 requests 直观。毕业设计要的是你能在答辩时讲清楚每一步,requests + BeautifulSoup 足够覆盖大多数需求,等理解了生命周期再去迁移到 Scrapy 也不迟。

3.1 爬虫原理在二手房场景里的具体体现

浏览器输入网址到看到页面,中间发生的事可以简化成:客户端给服务器发 HTTP 请求,服务器返回 HTML 文本,浏览器把 HTML 渲染成页面。爬虫做的事情就是跳过“渲染”这一步,直接拿 HTML 文本,然后用解析库把需要的字段抠出来。二手房的列表页大多在服务端直接渲染,Chrome 开发者工具里按 F12 查看 Elements,能看到完整的房源标题、价格、户型,这意味着不需要 Selenium 这种重型工具去模拟浏览器,直接 GET 请求就能拿数据。

一个常见误判是“Requests 拿到的 HTML 和浏览器里看到的不一样”。原因通常在于列表页有一部分数据是懒加载的,比如缩略图、小区均价地图。解决办法不是立刻换工具,而是先在 Network 面板里看 XHR 请求,如果页面滚动时出现了额外的 JSON 接口,那就直接请求那个接口,解析 JSON 比解析 HTML 更稳定。

3.2 用 requests 和 BeautifulSoup 抓取列表页的最小实现

这里以贝壳/链家风格的房源列表页为例,写一个能直接改城市跑通的最小采集脚本。

import random import time import requests from bs4 import BeautifulSoup def fetch_page(city: str, page: int) -> str: url = f"https://{city}.lianjia.com/ershoufang/pg{page}/" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/124.0.0.0 Safari/537.36", "Referer": f"https://{city}.lianjia.com/ershoufang/", } resp = requests.get(url, headers=headers, timeout=10) resp.raise_for_status() resp.encoding = resp.apparent_encoding return resp.text def parse_list(html: str) -> list[dict]: soup = BeautifulSoup(html, "lxml") items = [] for li in soup.select("ul.sellListContent li"): title = li.select_one(".title a") house_info = li.select_one(".houseInfo") total_price = li.select_one(".totalPrice span") unit_price = li.select_one(".unitPrice span") if not all([title, house_info, total_price, unit_price]): continue items.append({ "title": title.get_text(strip=True), "info": house_info.get_text("|", strip=True), "total": total_price.get_text(strip=True), "unit": unit_price.get_text(strip=True), }) return items

代码里有三个关键参数值得解释:

headers里的 User-Agent 决定服务端把你当成浏览器还是爬虫。只写python-requests会遇到非常高的概率被返回验证页;Referer 表示请求是从哪个页面跳转来的,很多反爬规则会校验这个字段。timeout=10限制单个请求最大等待时间,防止某个 IP 被限速后线程卡死。raise_for_status()会在返回状态码为 4xx/5xx 时主动抛异常,比自己判断if resp.status_code != 200更简洁。

lxml解析器比默认的html.parser在复杂 HTML 上快得多,但需要单独 pip install 安装。.get_text("|", strip=True)里的竖线分隔符很重要——二手房列表页的houseInfo字段通常长这样:2室1厅 | 89.5平米 | 南 北 | 精装 | 中楼层(共18层) | 2020年建,用竖线分隔后方便后续劈列。

3.3 反爬应对策略:延迟、重试和字段丢失处理

爬虫写得快,不如写得稳。我在实际项目里给采集器加了三级处理逻辑:单页请求失败重试 3 次,每次间隔递增;连续失败 5 次就停 60 秒再继续;解析结果为空时记录日志而不是直接崩溃。这几个参数直接用 sleep 控制就行,不需要引入复杂的重试库。

def crawl_with_retry(city: str, max_pages: int = 20): data = [] for page in range(1, max_pages + 1): for attempt in range(3): try: html = fetch_page(city, page) items = parse_list(html) if not items: print(f"第{page}页解析为空,可能被重定向到验证页面") data.extend(items) break except requests.RequestException as e: print(f"第{page}页第{attempt + 1}次请求失败: {e}") time.sleep((attempt + 1) * 5) time.sleep(random.uniform(1, 3)) return data

random.uniform(1, 3)让每次请求间隔落在 1 到 3 秒的随机区间内,而不是固定 sleep 2 秒。固定间隔很容易被访问频率统计识别出来,随机间隔更接近人工操作。这里的max_pages在毕业设计中建议控制在 20 页以下,数据量已经足够支撑分析,同时减少对目标站点的压力。不要试图爬全站几十万条数据,论文里的分析结论靠抽样就能说明问题。

数据落地时注意编码:

import pandas as pd df = pd.DataFrame(data) df.to_csv("data/houses.csv", index=False, encoding="utf-8-sig")

utf-8-sig会在文件开头写入 BOM,Excel 打开时中文不会乱码。如果直接用普通utf-8,Windows 下 Excel 会按 ANSI 解析,出现“链家”变成“閾惧”这类问题。

4. 数据清洗与可视化分析:Pandas 预处理、房价分布和区域对比

爬下来的 CSV 是给电脑看的,不是给人看的。二手房数据的清洗主要集中在三类问题上:文本字段混在一起需要拆分、价格字段带单位需要转型、个别房源价格异常高或异常低需要过滤。直接拿原始数据画图,图里的坐标轴会出现 “300 万 500 万” 这种没法计算的文本,Matplotlib 也会因为字符串数据无法排序而报错。

4.1 用 Pandas 拆字段、类型转换和异常值过滤

先把原始数据按字段拆开。链家的houseInfo是竖线分隔的,用str.split拆成列表再取值:

import pandas as pd df = pd.read_csv("data/houses.csv") info_split = df["info"].str.split("|", expand=True) df["bedrooms"] = info_split[0].str.extract(r"(\d+)室").astype(float) df["hall"] = info_split[0].str.extract(r"(\d+)厅").astype(float) df["area"] = info_split[1].str.replace("平米", "").astype(float) df["orientation"] = info_split[2].str.strip() df["decoration"] = info_split[3].str.strip() df["floor"] = info_split[4].str.strip()

正则(\d+)室从 “2室1厅” 中提取数字 2,astype(float)把文本转数值。这里的核心坑是expand=True拆分出的空列:不是每套房源的房源信息字段段数都一样,老小区可能缺装修信息,这时对应列是None,后面算均值时 Pandas 会默认忽略 NaN,不会报错,但如果你想检查缺失率,可以跑一下df.isnull().mean()

价格字段的处理方式类似,把单位去掉再转 float:

df["total_price"] = df["total"].str.replace("万", "").astype(float) df["unit_price"] = df["unit"].str.replace("元/平", "").astype(float) # 过滤明显异常:单价低于1万或高于15万 df = df[(df["unit_price"] > 10000) & (df["unit_price"] < 150000)] df = df.dropna(subset=["area", "total_price", "unit_price"])

dropnasubset参数指定只看这三列,缺任何一列就删除整行,避免把不完整的记录画进图里。异常值的阈值取决于城市:一线城市中心区单价超过 15 万/平米是存在的,但出现在普通挂牌列表里大概率是别墅或商业地产误挂,直接过滤掉反而让分析更聚焦普通住宅。

4.2 区域均价对比:Matplotlib 柱状图与中文字体设置

清洗完就可以分组统计了。按区域算均价和挂牌量,是两个最基本的分析维度:

region_stats = df.groupby("region").agg( avg_unit_price=("unit_price", "mean"), avg_total_price=("total_price", "mean"), house_count=("title", "count") ).sort_values("avg_unit_price", ascending=False)

groupby后返回的 DataFrame 里,列名由(原列名, 聚合函数)组成,用agg传入命名元组可以让结果的列名直接叫avg_unit_pricesort_values按均价降序排列,方便画图时直接取前 10 个区域。

画图时中文字体是经典翻车点。Windows 自带 SimHei,macOS 没有;Linux 服务器上更可能只看到方框。标准做法是先用matplotlib.font_manager找系统里可用的中文字体:

import matplotlib import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["WenQuanYi Zen Hei", "Noto Sans CJK SC", "SimHei"] plt.rcParams["axes.unicode_minus"] = False top10 = region_stats.head(10) plt.figure(figsize=(12, 6)) plt.bar(top10.index, top10["avg_unit_price"]) plt.title("各区域二手房挂牌均价(前10)") plt.ylabel("单价(元/平米)") plt.xticks(rotation=45) plt.tight_layout() plt.savefig("output/region_avg_price.png", dpi=150)

axes.unicode_minus设为 False 是为了让坐标轴上的负号正常显示,虽然价格数据没有负数,但设置掉又不是坏事。tight_layout()自动调整子图参数,避免横坐标文字被截断。dpi=150是论文插图的最低标准,答辩屏幕投影时不会糊。

4.3 面积与总价关系:散点图和相关系数

二手房分析里面积和总价的关系几乎必问,适合用散点图呈现:

import numpy as np plt.figure(figsize=(10, 6)) plt.scatter(df["area"], df["total_price"], alpha=0.4, s=10) plt.xlabel("面积(平米)") plt.ylabel("总价(万)") plt.title("二手房面积-总价散点图") # 拟合线性趋势线 slope, intercept = np.polyfit(df["area"], df["total_price"], 1) x_line = np.linspace(df["area"].min(), df["area"].max(), 100) plt.plot(x_line, intercept + slope * x_line, color="red", linewidth=2) plt.savefig("output/area_total_scatter.png", dpi=150)

alpha=0.4降低散点透明度,数据重叠时能看出密度差异;s=10控制点的大小,点太大会把后面的点遮住。np.polyfit返回一次多项式系数,画出来的红色趋势线能直观回答“面积对总价的影响是不是线性的”。

实际操作中你会发现很多城市的面积-总价散点图有明显的拐弯:60 平米以下单价偏高,120 平米以上单价回落。这是因为学区房的面积通常不大,总价被学区溢价抬高;大面积改善房反而在非核心区。这个现象可以作为毕业设计里的一个分析亮点写进论文。

4.4 用 PyECharts 做可交互的 HTML 图表

Matplotlib 图适合放在论文 Word 文档里,但答辩现场展示时,交互式图表的观感会好很多。PyECharts 的用法和 Matplotlib 区别较大,它是链式调用配置项,输出一个独立的 HTML 文件,浏览器打开就能看,不需要启动本地服务。

from pyecharts.charts import Bar from pyecharts import options as opts bar = Bar() bar.add_xaxis(region_stats.head(10).index.tolist()) bar.add_yaxis( "挂牌均价(元/平米)", region_stats.head(10)["avg_unit_price"].round(0).astype(int).tolist(), itemstyle_opts=opts.ItemStyleOpts(color="#d48265"), ) bar.set_global_opts( title_opts=opts.TitleOpts(title="区域二手房均价对比"), yaxis_opts=opts.AxisOpts(name="元/平米"), tooltip_opts=opts.TooltipOpts(trigger="axis"), ) bar.render("output/region_bar.html")

PyECharts 的add_xaxis接受的是普通 list,不能直接传 Series;round(0).astype(int)是为了让 tooltip 里的数字不带小数点。trigger="axis"表示鼠标悬停在坐标轴上时显示该轴所有系列的数据,比默认的item更适合对比类图表。

如果你想把图表嵌入到 Flask 里做可视化系统,bar.render()会生成完整 HTML 页面,render_embed()可以返回带有依赖的 HTML 片段,直接作为模板字符串传回浏览器。这个技巧在答辩演示时很加分:老师以为你只画了静态图,结果你打开网页还能悬浮看每一根柱子的数值。

5. 毕业设计收尾:PPT 演示、答辩讲解和源码交付的细节

毕业设计评分通常由三部分构成:论文文本、系统演示、答辩问答。代码写得再好,如果 PPT 上满屏代码模糊不清,或者答辩时被问到“这个功能对应你论文里的哪一节”答不上来,分数都会受影响。以下是我自己带项目时常用的处理方式,可直接参用。

5.1 答辩演示时要避开的三个坑

第一,别在现场跑爬虫。网络状况、目标网站反爬策略、本地代理都会影响实时效果。正确做法是提前把爬虫结果存成 houses.csv,演示时打开数据文件展示字段,然后运行清洗和可视化脚本,这样既展示了“会写爬虫”,又不会因为一次超时让整个答辩尬住。可以在 PPT 里放一段爬虫运行时的截图,证明数据确实是从网站采集来的。

第二,图表要少而精。不要一口气放 20 张柱状图。我一般只留四类图:区域均价对比、面积-总价散点图、户型分布饼图、单价直方图。每张图配一两句结论,比如“从区域均价来看,核心城区比外围板块高约 2.1 倍”“面积-总价在对数坐标下呈线性关系,说明改善型住宅的溢价效应显著”。

第三,源代码交付时记得删掉无用文件和敏感信息。爬虫里如果写死了 Cookie 或者登录凭证,交之前务必清掉。config.py 里的请求头保留没问题,但任何带个人特征的 token 都不该出现在毕业设计里。__pycache__目录、venv 文件夹、爬取的大体积中间文件一并清理,打包体积小,老师也更容易找到入口。

5.2 论文中“爬虫模块”怎么写才不是流水账

很多论文写爬虫时把一整段 urllib 代码贴进去,然后写“本章实现了数据采集”。更合理的写法是先画数据流图,再写出关键函数,每个函数配固定格式说明:输入参数、输出格式、异常处理逻辑。比如fetch_page的输入是城市拼音和页码,输出是 UTF-8 编码的 HTML 字符串,异常时向调用方抛出 RequestException,由上层决定重试还是跳过。处理手段也用逻辑带的词“服务端渲染的列表页直取 HTML;接口渲染的页面则解析 JSON,不能一概用解析库等待页面加载”。

需要在论文中明确说明频率限制策略:time.sleep(random.uniform(1, 3))保证请求间隔不小于 1 秒,这类描述能从规范层面增加学术诚实度。

5.3 PPT 最后一页放什么信息量最大

不需要再来一页“谢谢”或 Q&A。把你在答辩中会被问到的问题和对应回答直接放在最后一页,能显著提高主动掌控感。常见问题包括:数据量多大,字段如何确认抓取正确;反爬机制做到什么程度,会不会触发封锁;为什么选择 Matplotlib 而不是 Seaborn;数据清洗时怎么判断异常值是错误数据还是真实高价。针对每个问题给出简洁回答,列在备注里,PPT 正页只留关键词。

演示时打开output/region_bar.html的交互图表,让鼠标悬停在柱子上展示具体数值,做一次现场演示,比任何口述都有说服力。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 4:25:43

Spring Boot智慧养老监护平台:多角色权限与数据库设计实战解析

简介&#xff1a;面向Java后端开发与毕业设计人群&#xff0c;这份材料是一套基于Spring Boot的社区智慧养老监护管理平台设计与实现源码及论文配套资源。平台围绕管理员、后勤人员、护工、体检员、用户五类角色构建闭环业务&#xff0c;覆盖房间信息与入住管理、老人健康状态档…

作者头像 李华
网站建设 2026/9/16 4:25:43

宿舍用电安全升级:离人断电系统原理、选型与部署实战

开学季刚过&#xff0c;后勤群里又有老师吐槽&#xff1a;学生宿舍忘拔充电器引发的小火情、电吹风过热跳闸、人走不关空调导致电费飙升。这些问题背后其实都指向同一个需求——离人断电。这些年我参与过不少高校学生公寓的用电安全改造&#xff0c;从最早的机械式定时器&#…

作者头像 李华
网站建设 2026/9/16 4:25:38

基于ADPD188BI和RA8D2K的高性能烟雾探测系统设计

这几年做消防报警相关的产品选型&#xff0c;我把主流的光学烟雾传感器方案都摸了一遍&#xff0c;最终定下来的组合是ADI的ADPD188BI配合瑞萨的R7KA8D2KFLCAC&#xff08;RA8D2K系列&#xff09;。这套方案要解决的核心问题很直接&#xff1a;比传统光电烟感更早发现阴燃火&am…

作者头像 李华
网站建设 2026/9/16 4:25:36

SCDUNet++与迁移学习在滑坡测绘中的技术解析

成都理工的滑坡测绘工作最近讨论度挺高&#xff0c;很多人看到 SCDUNet 这个模型名一头雾水——这到底是 UNet 的哪一代变种&#xff0c;凭什么跟迁移学习搭在一起就能提升滑坡识别精度&#xff1f;我本身做过几年遥感影像语义分割&#xff0c;也踩过滑坡样本不足的坑&#xff…

作者头像 李华
网站建设 2026/9/16 4:25:06

鸿蒙ArkTS @Styles装饰器:样式复用最佳实践与避坑指南

看到这个标题&#xff0c;估计不少刚开始接触鸿蒙 ArkTS 声明式开发的朋友都会有点懵——样式复用直接用公共类不就行了&#xff1f;为什么还要专门搞一个 Styles 装饰器&#xff1f;说实话&#xff0c;我刚开始也这么想。但真正在 HarmonyOS 应用开发里写多页面、多组件的时候…

作者头像 李华
网站建设 2026/9/16 4:24:02

OpenClaw:让大模型驱动具身机器人,从原理到实践

最近一两年&#xff0c;AI智能体&#xff08;Agent&#xff09;这个概念几乎被聊烂了&#xff0c;但绝大多数讨论都停留在“对话机器人”或者“自动写文案”的层面。直到我接触了 OpenClaw 这个开源项目&#xff0c;才真正感觉到智能体从“数字世界”走向“物理世界”的那条路&…

作者头像 李华