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

📅 发布时间:2026/9/16 4:25:47
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 里按CtrlShiftP打开命令面板输入 “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 fhttps://{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: fhttps://{city}.lianjia.com/ershoufang/, } resp requests.get(url, headersheaders, timeout10) 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(stripTrue), info: house_info.get_text(|, stripTrue), total: total_price.get_text(stripTrue), unit: unit_price.get_text(stripTrue), }) return items代码里有三个关键参数值得解释headers里的 User-Agent 决定服务端把你当成浏览器还是爬虫。只写python-requests会遇到非常高的概率被返回验证页Referer 表示请求是从哪个页面跳转来的很多反爬规则会校验这个字段。timeout10限制单个请求最大等待时间防止某个 IP 被限速后线程卡死。raise_for_status()会在返回状态码为 4xx/5xx 时主动抛异常比自己判断if resp.status_code ! 200更简洁。lxml解析器比默认的html.parser在复杂 HTML 上快得多但需要单独 pip install 安装。.get_text(|, stripTrue)里的竖线分隔符很重要——二手房列表页的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 datarandom.uniform(1, 3)让每次请求间隔落在 1 到 3 秒的随机区间内而不是固定 sleep 2 秒。固定间隔很容易被访问频率统计识别出来随机间隔更接近人工操作。这里的max_pages在毕业设计中建议控制在 20 页以下数据量已经足够支撑分析同时减少对目标站点的压力。不要试图爬全站几十万条数据论文里的分析结论靠抽样就能说明问题。数据落地时注意编码import pandas as pd df pd.DataFrame(data) df.to_csv(data/houses.csv, indexFalse, encodingutf-8-sig)utf-8-sig会在文件开头写入 BOMExcel 打开时中文不会乱码。如果直接用普通utf-8Windows 下 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(|, expandTrue) 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厅” 中提取数字 2astype(float)把文本转数值。这里的核心坑是expandTrue拆分出的空列不是每套房源的房源信息字段段数都一样老小区可能缺装修信息这时对应列是None后面算均值时 Pandas 会默认忽略 NaN不会报错但如果你想检查缺失率可以跑一下df.isnull().mean()。价格字段的处理方式类似把单位去掉再转 floatdf[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])dropna的subset参数指定只看这三列缺任何一列就删除整行避免把不完整的记录画进图里。异常值的阈值取决于城市一线城市中心区单价超过 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, ascendingFalse)groupby后返回的 DataFrame 里列名由(原列名, 聚合函数)组成用agg传入命名元组可以让结果的列名直接叫avg_unit_price。sort_values按均价降序排列方便画图时直接取前 10 个区域。画图时中文字体是经典翻车点。Windows 自带 SimHeimacOS 没有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(rotation45) plt.tight_layout() plt.savefig(output/region_avg_price.png, dpi150)axes.unicode_minus设为 False 是为了让坐标轴上的负号正常显示虽然价格数据没有负数但设置掉又不是坏事。tight_layout()自动调整子图参数避免横坐标文字被截断。dpi150是论文插图的最低标准答辩屏幕投影时不会糊。4.3 面积与总价关系散点图和相关系数二手房分析里面积和总价的关系几乎必问适合用散点图呈现import numpy as np plt.figure(figsize(10, 6)) plt.scatter(df[area], df[total_price], alpha0.4, s10) 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, colorred, linewidth2) plt.savefig(output/area_total_scatter.png, dpi150)alpha0.4降低散点透明度数据重叠时能看出密度差异s10控制点的大小点太大会把后面的点遮住。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_optsopts.ItemStyleOpts(color#d48265), ) bar.set_global_opts( title_optsopts.TitleOpts(title区域二手房均价对比), yaxis_optsopts.AxisOpts(name元/平米), tooltip_optsopts.TooltipOpts(triggeraxis), ) bar.render(output/region_bar.html)PyECharts 的add_xaxis接受的是普通 list不能直接传 Seriesround(0).astype(int)是为了让 tooltip 里的数字不带小数点。triggeraxis表示鼠标悬停在坐标轴上时显示该轴所有系列的数据比默认的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 最后一页放什么信息量最大不需要再来一页“谢谢”或 QA。把你在答辩中会被问到的问题和对应回答直接放在最后一页能显著提高主动掌控感。常见问题包括数据量多大字段如何确认抓取正确反爬机制做到什么程度会不会触发封锁为什么选择 Matplotlib 而不是 Seaborn数据清洗时怎么判断异常值是错误数据还是真实高价。针对每个问题给出简洁回答列在备注里PPT 正页只留关键词。演示时打开output/region_bar.html的交互图表让鼠标悬停在柱子上展示具体数值做一次现场演示比任何口述都有说服力。本文还有配套的精品资源点击获取