资讯动态

Python二手房数据采集与可视化分析:爬虫、Pandas与Flask实战

发布时间:2026/10/29 20:18:47 来源:尧图企业网站定制
简介面向Python毕业设计场景的二手房数据采集及可视化分析项目资源包适合需要完成期末大作业或毕设项目的本科生、Python初学者参考。项目完整覆盖从数据采集、清洗、存储到分析可视化的流程以Requests、BeautifulSoup编写爬虫抓取链家、房天下等平台的房源信息再通过Pandas完成去重、缺失值处理与数据类型转换清洗后的数据可写入SQLite、MySQL或MongoDB备查分析环节则提供Matplotlib、Seaborn、Plotly绘制柱状图、折线图等方案的示例帮助观察房价、面积、地理位置等字段的分布与趋势。zip压缩包大小约48.05MB内含可直接运行的项目源码和全套讲解PPT等资料便于对照学习与二次修改。资源已有691人学习适合用来自学爬虫与数据分析全流程、理解从数据采集到可视化展示的完整项目结构也可作为毕业设计开题、答辩环节的素材支撑。1. 二手房数据采集与可视化分析毕业设计为什么值得选这个方向答辩现场最容易出现的场景是老师问数据从哪来你说爬虫老师问有多少条你说几千条老师问你爬虫遇到反爬怎么处理你支支吾吾。基于 Python 的二手房数据采集及可视化分析正好把这三问都堵上——它既是一条完整的 Python 爬虫链路也是一套 Pandas 数据清洗和 Pyecharts 可视化分析的项目源码最后还能用 Flask 把图表串成可交互的看板答辩演示效果非常直观。加上所有资料做成 PPT 文件递上去导师一眼就能看出工作量。适合三类人基础薄弱想用套壳项目保底的学过爬虫和 Pandas 想系统串一遍的以及想往数据分析方向找实习作品的人。这个方向技术栈清晰、源码可复现、坑都可预期作为毕业设计性价比很高。2. 数据采集层用 Requests 和解析库把链家二手房列表页变成结构化数据2.1 先搞清楚数据长什么样再写爬虫写爬虫之前的第一件事不是上手写 requests.get而是打开浏览器按 F12 切到 Network 面板刷新一次列表页看 HTML 结构。链家和贝壳的二手房列表页房源信息通常在 class 为 house-title 或 sellListContent 的 li 标签里每套房子的标题、小区名、位置、总价、单价、关注人数都散落在不同的 div 和 span 中字段之间没有固定规律全靠 class 名区分。我一般会先用浏览器的“检查”功能选中某一套房子的标题确认它所在的标签层级再决定解析逻辑。这里有个常见做法是直接用 BeautifulSoup 配合 lxml 解析速度比正则快代码写起来也直观。另一个关键点是链家列表页对高频访问有明显限制连续翻页太快会弹验证页所以采集脚本里必须带请求间隔。建议把请求间隔写成可配置参数而不是写死在代码里。import time import requests from bs4 import BeautifulSoup session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Referer: https://sh.lianjia.com/ershoufang/ # 链家上海二手房列表页 }) url https://sh.lianjia.com/ershoufang/pg{page}/ house_list [] for page in range(1, 3): # 先爬两页验证结构 resp session.get(url.format(pagepage), timeout10) soup BeautifulSoup(resp.text, lxml) items soup.select(li. clear LOGCLICKDATA) if not items: items soup.select(div.info) # 不同城市 class 名可能不一样 for item in items: title_tag item.select_one(a.maidian-detail) if title_tag is None: continue title title_tag.get_text(stripTrue) total_price item.select_one(div.totalPrice span) unit_price item.select_one(div.unitPrice span) house_list.append({ 标题: title, 总价_万: total_price.get_text(stripTrue) if total_price else , 单价_元平: unit_price.get_text(stripTrue) if unit_price else }) time.sleep(1.5) # 请求间隔不能省这段代码的关键点有两个。第一selector 里 class 名含空格时直接用 select 会失效我用 lxml 解析后改用 div.info 兜底这种页面的 class 命名经常改版建议第一版先打印前 100 行 HTML 确认。第二总价和单价的文本是类似“560万”和“46251元/平米”的字符串后续必须在清洗阶段拆数字不要在爬虫阶段强行转 float因为缺失值会导致异常断掉。先把原始文本存下来清洗时统一处理是数据采集最常见的可靠做法。2.2 多字段抽取与详情页补数据列表页能拿到标题、总价、单价、小区名和基本位置但真正支撑可视化分析的字段比如建筑年代、面积、户型、朝向、楼层、装修情况都在详情页里。毕设如果想做楼龄和单价的关系分析详情页就是绕不开的一步。常见做法是把列表页解析出的房源链接队列化再串行进详情页采集。需要注意的是详情页的 URL 是数字编号格式比如 https://sh.lianjia.com/ershoufang/107123456789.html别把 URL 列存成字符串就以为完事了还要防重。我习惯在采集前先用一个 set 保存已访问过的房源编号重访直接跳过。import re from urllib.parse import urlparse def parse_house_from_detail(url, html): soup BeautifulSoup(html, lxml) # 基础信息在 introContent 里 intro soup.select_one(.introContent) if intro is None: text soup.get_text( , stripTrue) else: text intro.get_text( , stripTrue) # 用正则抽面积比定位元素更抗改版 area_match re.search(r建筑面积[:]\s*([\d.])㎡, text) year_match re.search(r建成年代[:]\s*(\d{4})年, text) toward_match re.search(r朝向[:]\s*([^\s]), text) house_id urlparse(url).path.split(/)[-1].replace(.html, ) return { 房源编号: house_id, 建筑面积_平: float(area_match.group(1)) if area_match else None, 建成年代: int(year_match.group(1)) if year_match else None, 朝向: toward_match.group(1) if toward_match else }正则在这里比 BeautifulSoup 的 select 更实用因为详情页的布局会在不同城市下浮动introContent 内部结构不统一而文字排版的规律相对稳定。加了一个 nums 参数来控制采样数量避免采集时间过长。2.3 采集策略翻页、多区域与请求间隔的边界整个列表往往是几十万条直接全量爬既慢又容易被封。合理的采集中间态是先按区域拆。比如上海二手房可以按浦东、徐汇、静安等行政区拼接 URL 参数每个区域只爬前五页这样数据量约数百到上千条对毕业设计完全够用又能展示多区域对比的图表效果。请求间隔的边界值我习惯设为 1 到 2 秒之间。太快会触发滑块验证太慢则几万条数据要爬大半天。另一个容易被忽略的坑是 Cookie 时效浏览器登录后 cookie 里的 SESSIONID 会在几小时后失效requests 复现不了浏览器环境所以不要在请求头里硬编码登录态。还有链家对同一个 IP 的访问频率有记录如果项目演示要保证现场稳定建议把采集好的数据落成 CSV 或 SQLite 存档演示时优先读本地文件爬虫只在更新数据时运行。3. 数据清洗与特征工程把爬回来的脏数据变成可分析的 DataFrame3.1 清洗流程类型转换、单位规整、缺失值处理爬虫返回的原始数据通常是一个 CSV长这样标题列混着“XX小区 2室1厅”总价是“560万”单价是“46251元/平米”建成年份可能有 1998 年也有“暂无”。直接拿这样的数据进 Pandas 做聚合结果全是字符串拼出来的假数字。清洗第一步是把价格和面积拆出来。第二步是统一单位总价字段里去掉“万”字单价去掉“元/平米”字样并把可能出现的“暂无”“--”替换成 NaN。第三步是筛选采样数据把面积明显异常的大于 500 平的记录剔除因为这些要么是别墅搞的栋数不对要么就是爬到了车位。还要注意层高、朝向、装修等枚举字段的空值删掉不是好选择填充成“未知”可视化时才能如实展示。import pandas as pd import re df pd.read_csv(lianjia_sh_raw.csv, encodingutf-8-sig) def clean_price(value): 把 560万 - 560.0, 1.2万/平米 - 1.2的文本清洗 if pd.isna(value) or str(value) in (暂无, --, ): return None match re.search(r([\d.]), str(value)) return float(match.group(1)) if match else None df[总价_万元] df[总价_万].apply(clean_price) df[单价_元平] df[单价_元平].apply(clean_price) def area_from_text(text): match re.search(r([\d.])平米, str(text)) return float(match.group(1)) if match else None df[建筑面积_平] df[建筑面积_平].apply(area_from_text) df df.dropna(subset[总价_万元, 单价_元平, 建筑面积_平]) df df[df[建筑面积_平] 500] # 排除异常超大户型参数说明encodingutf-8-sig 是为了让 Excel 打开 CSV 时不出现中文乱码这类细节在毕设答辩时经常被问到。正则提取正则表达式采取“匹配第一个数字”策略好处是总价里出现“1.2万”这类带小数的文本也能兼容坏处是“非住宅”这类字样可能污染单价列所以我只在文本如“46251元/平米”时提取保证列语义一致。整个清洗过程要和后续可视化分开写。Pandas 的 apply 是逐行处理数据量大时速度一般好在毕业设计的数据规模通常在五千条以内性能不是问题。真正要注意的是清洗后要做一次 describe看看列的最大最小值合不合理单价 10 万一平米的豪宅明显会让图表失真这类数据是否保留要在答辩时给出理由。3.2 特征工程单价计算、楼龄分段、总价区间数据清洗完剩下来的是原始字段可视化之前还要做特征加工。常用的是四个衍生字段单价如果爬虫没直接给就用总价除以面积、楼龄当前年份减建成年代、总价区间比如 200 万以下、200-400 万等 5 档、面积段60 平以下、60-90 平、90-140 平、140 平以上。原始数据里单价可能存在 1 楼带花园的折价房总价和面积都属实但单价看起来异常低。要不要单独剔除我建议不要因为这类房源本身是真实存在在做均价计算时可以被拉低数值处理办法是使用中位数而不是均值。import numpy as np df[单价_计算] df[总价_万元] * 10000 / df[建筑面积_平] df[楼龄] 2025 - df[建成年代] bins_price [0, 200, 400, 600, 800, np.inf] labels_price [200万以下, 200-400万, 400-600万, 600-800万, 800万以上] df[总价区间] pd.cut(df[总价_万元], binsbins_price, labelslabels_price) bins_area [0, 60, 90, 140, np.inf] labels_area [60平以下, 60-90平, 90-140平, 140平以上] df[面积段] pd.cut(df[建筑面积_平], binsbins_area, labelslabels_area)pd.cut 的关键是区间左闭右开200万以下这一档的口径是 0 到 200 万含 200 万如果你希望 200 万整归入下一档就把 bins 改成 [0, 199.99, 400, ...]但这样代码就变丑了。我一般直接用默认答辩时讲清楚口径即可。特征工程这一步不只是为了可视化好看它还是模型类分析的输入基础如果毕设内容里有房价预测或特征相关性分析这些区间字段可以直接做 one-hot。4. 可视化分析从 Pandas 聚合到交互式看板的落地路径4.1 用 Pandas 做聚合统计产出一张基础分析表可视化分析的核心不是画图而是“分析”。画图前要先想清楚要说明什么观点比如“总价在 400-600 万区间是主力户型”或者“90 平以下房源集中在哪些商圈”。用 Pandas 做 groupby 聚合得到一张二维表再交给图表库。summary df.groupby([区域, 面积段], observedFalse)[总价_万元].agg( 挂牌均价mean, 房源数量count, min_pricemin, max_pricemax ).reset_index() summary[挂牌均价_万] summary[挂牌均价].round(1) summary.sort_values(房源数量, ascendingFalse, inplaceTrue) print(summary.head(20))这段代码的意图是每个区域在不同面积段内的挂牌均价、房源数量和价格区间。直接打印出 20 行数据是最原始的“用数据说话”的证据。注意 groupby 里用了 observedFalse当分组列是分类类型时它会保留空组避免某些面积段没有房源时被忽略这种细节到了答辩时能被评审老师一眼看到。4.2 用 Pyecharts 做交互图表的配置注意点Pyecharts 是基于 ECharts 的 Python 封装优势是生成 HTML浏览器直接打开就能交互缩放、悬停显示数值。很多新手上来就画饼图堆了一堆五颜六色的扇区答辩时老师一问“这个图想说明什么”就答不上来。合理做法是先做三个图区域均价柱状图、总价区间分布环形图、朝向与楼龄散点图。from pyecharts.charts import Bar, Pie, Scatter from pyecharts import options as opts bar Bar() bar.add_xaxis(summary[区域].unique().tolist()) bar.add_yaxis(挂牌均价_万, summary.groupby(区域, observedFalse)[挂牌均价_万].mean().tolist()) bar.set_global_opts( title_optsopts.TitleOpts(title各区域二手房挂牌均价对比), yaxis_optsopts.AxisOpts(name均价万元), ) bar.render(区域均价对比.html) scatter Scatter() x_data df[建筑面积_平].tolist() y_data df[总价_万元].tolist() scatter.add_xaxis(x_data) scatter.add_yaxis(总价/面积, y_data, symbol_size8) scatter.set_global_opts( title_optsopts.TitleOpts(title面积与总价的散点分布), xaxis_optsopts.AxisOpts(name建筑面积平), yaxis_optsopts.AxisOpts(name总价万元) ) scatter.render(面积总价散点.html)参数说明x 轴数据用列表y 轴数据也要用列表如果直接把 Series 传进去Pyecharts 内部会转成奇怪的对象导致图表空白。add_yaxis 里 symbol_size 控制散点大小数据量超过 1000 条时可以调到 5否则点太密。render 方法生成独立 HTML 文件图片上传到 PPT 或现场打开浏览器演示都可以。这个环节我习惯把每个图表单独存一个 html最后用 Flask 统一集成或直接用 iframe 放进页面。4.3 用 Flask 把图表整合成 Dashboard 的可选路径如果不想在答辩时开五个 HTML 页来回切换可以在项目里加一个 Flask 应用把多个图表的 HTML 文件通过 iframe 嵌入一个总览页面。这是毕设里最常见的“可视化分析平台”形态数据存储用 CSV 或 SQLite 都可以Flask 只负责提供静态文件。from flask import Flask, render_template import os app Flask(__name__) app.route(/) def index(): chart_files [区域均价对比.html, 面积总价散点.html, 总价区间分布.html] chart_files [f for f in chart_files if os.path.exists(f)] return render_template(dashboard.html, chartschart_files) if __name__ __main__: app.run(host127.0.0.1, port5000, debugFalse)这里有个经验开发时 debugFalse 不是让你看不到报错而是 Flask 的调试器会暴露本机端口答辩环境里跑外部设备演示时容易造成安全隐患。真正调 bug 时我用 print 加日志不用 debug 模式。还有iframe 引用本地 HTML 直接用相对路径是可行的Flask 默认的 static 路径不好管时把图表的 html 放到 templates 同级目录下再用 url_for 动态生成地址会更规范不过毕设规模下用 os.path.exists 判断文件存在与否已经能应付答辩现场。联动组件不是必须的但拖一个区域下拉框联动图表会让整个项目看起来有增量价值。常见实现是用 Flask 路由传参数后端重新筛选 DataFrame再 render 对应图表这样做成本可控也适合回答“你做了哪些功能”的问题。5. 二手房采集可视化避坑反爬、乱码与浏览器兼容问题排查5.1 现象列表页爬下来为空连服务器返回的 HTML 都不对劲抓包时直接打印 resp.text发现内容变成了几个 base64 的图片或“访问过于频繁”提示。原因很简单访问频率超过对方阈值触发了人机验证。解决是靠“跨间隔 User-Agent 轮换”来模拟真人浏览模式另外把抓取时间调到凌晨或工作日白天。还有一个比较隐蔽的坑我踩过requests 的 Session 持有一个连接池长时间不关闭会占用端口当爬取上千个页面时偶发 connection reset 会导致中断。应对是把连接池上限调小、遇到异常就 sleep 后重试一次from requests.adapters import HTTPAdapter session.mount(https://, HTTPAdapter(pool_connections5, pool_maxsize10)) for retry in range(2): try: resp session.get(url, timeout10) break except requests.ConnectionError: time.sleep(5)5.2 现象数据写入 CSV 后 Excel 打开中文乱码现象是 Excel 里所有中文变成一堆类似“浜”的乱码。原因是 Python 默认 utf-8 而 Excel 默认以 GBK 读取两者编码体系不一致。解决是 df.to_csv 时指定 encodingutf-8-sig这个编码会带 BOM 头Excel 能正确识别。这是所有用 Python 导出中文 Excel 数据的人必踩的一处细节答辩时主动说出来反而是加分项。df.to_csv(clean_lianjia.csv, indexFalse, encodingutf-8-sig)5.3 现象图表 x 轴标签挤成一条黑线这类现象出现在 Pyecharts 柱状图 x 轴区域名太多时默认每个标签都旋转 0 度几十个区域名叠加在一起。解决方法是增设 axis_label 的 interval 和 rotate 参数或者直接限定只展示 Top15 区域。我个人更倾向于先排序筛选再画图既保证图清晰又避免图表库卡顿。bar.set_global_opts( xaxis_optsopts.AxisOpts( axis_label_optsopts.LabelOpts(rotate45, interval0) ) )5.4 现象把“建成年代”当整数用计算楼龄出错清洗时建成年代可能是“1998年”或“暂无”直接用 int 会报错。更隐蔽的是把解析后的年份当成字符串和 2025 相减得到一串乱码。解决方式是统一先清理到干净的数值列再参与计算。建议在清洗阶段用 pd.to_numeric 强制转换coerce 参数把异常置为 NaNdf[建成年代_clean] pd.to_numeric(df[建成年代], errorscoerce) df[楼龄] 2025 - df[建成年代_clean]这里还有个玄学如果数据源给的是“建成年代1998年”这种含中文的字符串to_numeric 直接返回 NaN必须先正则抽出数字这就是前面清洗函数里先 re.search 再转 float 的原因。6. 进阶与答辩技巧用最小代价验证分析与数据可信度评审最常问的一句话是“你的数据怎么证明是对的”。如果只是把爬来的数据画成图不具备可证伪性等于没做分析。我会额外加一个“数据抽样核验”脚本随机抽 20 条记录把房源编号重新请求一次链家网页断言标题和总价一致。这个脚本不用跑全量只需要作为答辩演示的一部分现场抽 3 条即可。另外源码包里最好留一个 data_quality_report.xlsx 或 CSV记录每个字段的空值率、去重数、异常值数这些都是可量化的可信度证据。关于交付资料我建议源码按三个目录组织crawler、cleaning、dashboard。crawler 放采集脚本和原始 CSVcleaning 放清洗脚本和最终 DataFrame 结果dashboard 放 Flask 应用和图表 HTML。全部资料 PPT 文件里放三页最关键的内容系统架构图、数据采集流程图、可视化效果截图。架构图不追求花哨用文字框画清楚“Requests 采集 → Pandas 清洗 → Pyecharts 可视化 → Flask 展示”这条链路即可。PPT 页数控制在 12 到 15 页功能讲清楚不要贴满代码。我在讲单个图表时有一个习惯先展示结论文字再切图。比如“上海二手房的主力总价段集中在 300 到 600 万之间占样本的 54%”然后再显示对应环形图图片只是佐证不是主角。最后写代码时建议给每个文件加注释特别是解释“为什么 sleep 1.5 秒”这类决策因为导师看源码时会顺带看注释是否成体系。如果你准备把这个项目放进简历把数据和可视化复现一遍能跑通比会讲概念重要得多。希望这个方向能帮你在毕业设计里少走弯路做出一个真正能用、能解释、能演示的作品。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑