ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

二手房数据采集与可视化分析:Python毕业设计实战指南

二手房数据采集与可视化分析:Python毕业设计实战指南 简介这份资源是面向计算机、通信、人工智能、自动化等专业学生与教师的Python毕业设计完整项目包围绕二手房数据采集与可视化分析展开可用于毕业设计、期末课程设计或课程大作业也适合作为小白入门与进阶练习的实战案例。压缩包共157个文件约40.01MB包含18个py源码文件、18个csv数据集、15个html页面、11个js脚本及65个png图表另有txt说明、ini配置、ttf字体与pptx答辩材料覆盖数据采集、清洗、存储、可视化到论文资料的全流程。项目代码经过调试测试答辩评审分达98分可直接运行。已有177人学习下载读者能获得完整赛题方案、原始与清洗后的二手房数据、可视化图表、论文配套资料及清晰的目录结构基础较好的同学还可在此基础上修改调整实现不同功能。1. 二手房数据采集与可视化一个能写进简历的毕业设计选题二手房数据采集及可视化分析说白了就是三件事把房源信息从公开页面上抓下来、清洗成结构化表格、再用图表把价格分布和区域差异讲清楚。这个选题在计算机毕业设计里经久不衰原因很实在——数据源公开、技术栈成熟、可视化效果直观答辩时老师一眼就能看懂你在做什么。适合的人群也很明确Python 刚入门、想做一个完整项目练手、同时需要一份能写进简历的本科或专科毕业生。我见过太多人卡在第一步环境没配好、请求被拒、数据存不进数据库。这篇笔记就按我实际带学生做项目的路径从环境搭建一路讲到可视化出图把每个环节的参数和坑都摊开说。2. 环境搭建与数据采集从零把房源数据抓下来2.1 Python 环境与依赖库的版本选择很多人第一步就翻车python安装教程看了一堆结果装完发现 pip 用不了或者 vscode python环境配置对不上。我的建议是直接用 Python 3.10 或 3.11这两个版本对爬虫和可视化库的兼容性最稳。不要用 3.12 以上的最新版部分可视化库的 wheel 包还没跟上装的时候会报编译错误新手根本处理不了。安装时务必勾选“Add Python to PATH”这一步漏了后面所有命令行操作都会提示“python 不是内部或外部命令”。装完之后在终端里跑一遍确认python --version pip --version两条命令都能正常输出版本号才算环境通了。如果 pip 版本太旧先升级python -m pip install --upgrade pip接下来装核心依赖。二手房数据采集和可视化分析通常需要这几类库请求库、解析库、数据处理库、存储库、可视化库。我一般用一条命令批量装pip install requests beautifulsoup4 lxml pandas matplotlib seaborn jieba wordcloud pymysql sqlalchemy这里解释一下每个库的角色。requests 负责发 HTTP 请求拿页面内容beautifulsoup4 配合 lxml 解析 HTML 提取房源字段pandas 做数据清洗和表格操作matplotlib 和 seaborn 出统计图jieba 加分词wordcloud 做词云pymysql 和 sqlalchemy 负责把数据写进 MySQL。版本方面不用刻意锁死pip 默认拉最新稳定版即可但如果 pandas 装到了 2.x后面读 CSV 时encoding参数要留意默认 utf-8 对中文兼容没问题但遇到 GBK 编码的文件要显式指定。提示如果你用的是 conda 环境把 pip 换成 conda install 也可以但部分库在 conda 源里更新慢建议混合使用优先 pip。2.2 目标页面分析与请求头构造二手房数据采集的第一步不是写代码是打开浏览器开发者工具F12切到 Network 面板刷新目标页面找到返回房源列表的那个请求。你要确认三件事请求方式是 GET 还是 POST、返回的是 HTML 还是 JSON、有没有分页参数。大部分二手房平台的列表页是服务端渲染的 HTML直接请求就能拿到房源卡片。但有些平台改成了 AJAX 加载返回的是 JSON 数据这时候解析方式完全不同。我一般先看 Response 的 Content-Type如果是text/html用 BeautifulSoup 解析如果是application/json直接用response.json()处理。请求头是第二个关键点。不加 User-Agent 的请求大概率被拒返回 403。我通常构造一个完整的 headersimport requests import time import random headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9, Referer: https://example.com/, } def fetch_page(url, retry3): for i in range(retry): try: resp requests.get(url, headersheaders, timeout10) if resp.status_code 200: return resp.text print(f状态码 {resp.status_code}第 {i1} 次重试) except requests.RequestException as e: print(f请求异常{e}第 {i1} 次重试) time.sleep(random.uniform(2, 5)) return None这段代码的逻辑很直白带 headers 发 GET 请求超时设 10 秒失败后随机等 2 到 5 秒再重试最多三次。timeout参数必须设不设的话遇到慢响应会一直挂着。random.uniform做随机延迟是为了降低请求频率特征比固定 sleep 更自然。Referer字段有些平台会校验加上更保险。参数说明retry3是经验值超过三次还失败说明要么 IP 被限了要么页面结构变了继续重试没意义。timeout10对大多数房源页面够用如果目标服务器响应慢可以调到 15。2.3 房源字段提取与分页采集拿到 HTML 之后用 BeautifulSoup 定位房源卡片。假设每个房源是一个div带 classhouse-item里面包含标题、总价、单价、面积、户型、楼层、小区名、区域等字段。提取逻辑如下from bs4 import BeautifulSoup def parse_list(html): soup BeautifulSoup(html, lxml) items soup.select(div.house-item) records [] for item in items: try: record { title: item.select_one(h2.title).get_text(stripTrue), total_price: item.select_one(span.total).get_text(stripTrue), unit_price: item.select_one(span.unit).get_text(stripTrue), area: item.select_one(span.area).get_text(stripTrue), layout: item.select_one(span.layout).get_text(stripTrue), floor: item.select_one(span.floor).get_text(stripTrue), community: item.select_one(a.community).get_text(stripTrue), district: item.select_one(a.district).get_text(stripTrue), } records.append(record) except AttributeError: continue return records这里用select_one而不是find因为 CSS 选择器写起来更直观。每个字段单独 try 包裹遇到某个卡片结构不完整就跳过不让一条脏数据打断整批采集。get_text(stripTrue)去掉首尾空白和换行后面清洗时省事。分页采集用一个循环拼接 URLall_records [] for page in range(1, 51): url fhttps://example.com/sale/pg{page}/ html fetch_page(url) if html is None: print(f第 {page} 页采集失败跳过) continue records parse_list(html) if not records: print(f第 {page} 页无数据可能已到末页) break all_records.extend(records) print(f第 {page} 页采集 {len(records)} 条) time.sleep(random.uniform(3, 6))50 页是常见二手房列表的深度上限实际跑的时候如果某页返回空列表就提前结束。每页之间等 3 到 6 秒一天采几百页完全够用不会给目标服务器造成压力。3. 数据清洗与存储把脏数据变成能分析的表格3.1 价格、面积字段的数值化处理采集下来的原始数据全是字符串比如“总价 320 万”“单价 28500 元/平米”“面积 112.3 平米”。直接拿去做统计会报错必须转成数值类型。这一步是数据清洗里最琐碎但最不能省的环节。import pandas as pd import re df pd.DataFrame(all_records) def parse_total_price(text): match re.search(r(\d\.?\d*), text) return float(match.group(1)) if match else None def parse_unit_price(text): match re.search(r(\d\.?\d*), text) return float(match.group(1)) if match else None def parse_area(text): match re.search(r(\d\.?\d*), text) return float(match.group(1)) if match else None df[total_price] df[total_price].apply(parse_total_price) df[unit_price] df[unit_price].apply(parse_unit_price) df[area] df[area].apply(parse_area) df.dropna(subset[total_price, unit_price, area], inplaceTrue) df df[(df[total_price] 10) (df[total_price] 5000)] df df[(df[area] 15) (df[area] 1000)]正则(\d\.?\d*)匹配整数或小数兼容“320”和“112.3”两种格式。dropna去掉关键字段缺失的行然后做业务过滤总价低于 10 万或高于 5000 万的极可能是录入错误或异常房源面积低于 15 平米或高于 1000 平米的同理。这两步过滤能去掉大部分噪声让后面的分布图更干净。注意过滤阈值要根据目标城市的实际房价水平调整。一线城市总价上限可以放到 8000 万三四线城市下限可以降到 5 万。3.2 去重、缺失值填充与 MySQL 入库二手房数据采集最容易出现的问题是重复同一房源在不同页面出现、或者翻页时边界重叠。去重按标题加小区名加面积组合判断df.drop_duplicates(subset[title, community, area], keepfirst, inplaceTrue) df.reset_index(dropTrue, inplaceTrue)keepfirst保留第一次出现的记录后面的丢弃。reset_index重建索引不然后面入库时索引跳号。缺失值处理分两种数值字段用中位数填充文本字段用“未知”填充。但我的习惯是关键字段缺失直接删行非关键字段才填充。比如楼层缺失可以填“未知”但面积缺失这条记录就没分析价值了。入库用 sqlalchemy 加 pymysqlfrom sqlalchemy import create_engine engine create_engine( mysqlpymysql://root:passwordlocalhost:3306/house_db?charsetutf8mb4 ) df.to_sql(second_hand_house, engine, if_existsreplace, indexFalse)连接串里charsetutf8mb4必须加不然小区名里的生僻字会报错。if_existsreplace每次全量覆盖适合毕业设计这种一次性采集场景。如果是增量采集改成append并加唯一索引去重。参数说明root:password换成你自己的 MySQL 账号密码localhost:3306是默认地址端口house_db是数据库名需要提前建好。4. 可视化分析用图表把房价规律讲清楚4.1 价格分布与区域对比图可视化分析的核心不是图多好看是图能回答什么问题。二手房数据最常回答的三个问题价格整体分布什么样、哪个区域最贵、面积和总价是什么关系。先看价格分布用直方图加核密度曲线import matplotlib.pyplot as plt import seaborn as sns plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False fig, ax plt.subplots(figsize(10, 6)) sns.histplot(df[total_price], bins40, kdeTrue, axax, color#4C72B0) ax.set_xlabel(总价万元) ax.set_ylabel(房源数量) ax.set_title(二手房总价分布) plt.tight_layout() plt.savefig(price_dist.png, dpi150) plt.show()font.sans-serif设成 SimHei 是为了正常显示中文不设的话标题全是方框。axes.unicode_minus设 False 解决负号显示问题。bins40是经验值数据量在几千条时 40 个柱子粒度合适太少看不出分布形态太多柱子太碎。kdeTrue叠加核密度曲线能直观看出分布是单峰还是双峰。区域对比用箱线图fig, ax plt.subplots(figsize(12, 6)) order df.groupby(district)[unit_price].median().sort_values(ascendingFalse).index sns.boxplot(datadf, xdistrict, yunit_price, orderorder, axax) ax.set_xlabel(区域) ax.set_ylabel(单价元/平米) ax.set_title(各区域二手房单价对比) plt.xticks(rotation45) plt.tight_layout() plt.savefig(district_price.png, dpi150) plt.show()按中位数排序让图表更有可读性贵的区域排左边。箱线图能同时看到中位数、四分位距和异常值比柱状图信息量大。rotation45防止区域名重叠。4.2 户型词云与面积-总价散点图词云适合展示户型、小区名、卖点标签的分布。用 jieba 分词后喂给 wordcloudimport jieba from wordcloud import WordCloud text .join(df[layout].dropna().tolist()) words .join(jieba.cut(text)) wc WordCloud( font_pathC:/Windows/Fonts/simhei.ttf, width800, height400, background_colorwhite, max_words100 ) wc.generate(words) wc.to_file(layout_wordcloud.png)font_path必须指向系统中文字体文件不指定的话中文全是方块。Windows 下 simhei.ttf 一般在C:/Windows/Fonts/目录Linux 下换成/usr/share/fonts/里的中文字体路径。max_words100控制显示词数太多会挤在一起。面积和总价的关系用散点图加回归线fig, ax plt.subplots(figsize(10, 6)) sns.regplot(datadf, xarea, ytotal_price, axax, scatter_kws{alpha: 0.4, s: 15}, line_kws{color: red}) ax.set_xlabel(面积平米) ax.set_ylabel(总价万元) ax.set_title(面积与总价关系) plt.tight_layout() plt.savefig(area_price.png, dpi150) plt.show()alpha0.4让散点半透明重叠区域颜色更深能看出密度。s15控制点大小数据量大时点太大会糊成一片。regplot 自动拟合线性回归线斜率能反映该城市每平米均价的粗略水平。5. 避坑与排查采集和可视化中最容易翻车的五个点5.1 请求返回 403 或空内容现象代码跑起来不报错但resp.text是空字符串或者状态码直接 403。原因通常是请求头不完整或 IP 被临时限制。解决先补全 User-Agent、Accept、Accept-Language、Referer 四个字段再降低请求频率把 sleep 调到 5 秒以上。如果还不行换一个时间段再试不要连续硬刚。5.2 中文显示为方块现象matplotlib 出的图里标题和轴标签全是方框。原因是默认字体不支持中文。解决在绘图前设置plt.rcParams[font.sans-serif] [SimHei]Linux 环境下如果没有 SimHei换成[WenQuanYi Micro Hei]或系统里已有的中文字体名。词云图单独设font_path参数。5.3 入库时报编码错误现象to_sql执行到一半报UnicodeEncodeError或乱码。原因是连接串没指定 utf8mb4或者数据库建库时字符集不对。解决连接串加?charsetutf8mb4建库语句用CREATE DATABASE house_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。两个地方都对了才不会出问题。5.4 分页采集提前中断现象跑到第 10 页突然返回空列表但手动打开第 10 页明明有数据。原因是目标页面做了频率检测短时间请求太多被临时拦截。解决在循环里加随机延迟每页间隔 3 到 6 秒每采 10 页额外多等 10 秒。如果已经被拦停 10 分钟再跑。5.5 可视化图表保存后空白现象plt.savefig保存的图片打开是白板。原因是savefig写在了plt.show()后面show 之后画布被清空了。解决先savefig再show或者用fig.savefig代替plt.savefig。这个坑很隐蔽因为代码不报错只是图没了。6. 从毕业设计到可展示项目三个让答辩加分的技巧第一个技巧是加一个简单的交互式筛选。用 Streamlit 把 DataFrame 包一层几行代码就能做出区域下拉框和价格区间滑块答辩时现场演示比静态图有说服力import streamlit as st st.title(二手房数据可视化看板) district st.selectbox(选择区域, [全部] sorted(df[district].unique().tolist())) price_range st.slider(总价范围万元, 0, 2000, (100, 800)) filtered df.copy() if district ! 全部: filtered filtered[filtered[district] district] filtered filtered[(filtered[total_price] price_range[0]) (filtered[total_price] price_range[1])] st.metric(房源数量, len(filtered)) st.metric(平均单价元/平米, f{filtered[unit_price].mean():.0f}) st.bar_chart(filtered.groupby(district)[unit_price].median())跑起来只需要streamlit run app.py浏览器自动打开。st.metric显示关键指标st.bar_chart直接出图不用写 matplotlib 那套配置。第二个技巧是导出分析报告。用 pandas 的to_excel把清洗后的数据和统计结果写成一个多 sheet 的 Excel答辩时可以作为附件提交with pd.ExcelWriter(house_analysis.xlsx) as writer: df.to_excel(writer, sheet_name原始数据, indexFalse) df.groupby(district)[unit_price].agg([mean, median, count]).to_excel( writer, sheet_name区域统计 ) df.groupby(layout)[total_price].mean().sort_values(ascendingFalse).head(10).to_excel( writer, sheet_name户型均价TOP10 )第三个技巧是给采集脚本加日志。不要用 print用 logging 写到文件里答辩时如果老师问“你采了多少条、失败了多少”直接翻日志比回忆靠谱import logging logging.basicConfig( filenamecrawl.log, levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s ) logging.info(f第 {page} 页采集 {len(records)} 条) logging.warning(f第 {page} 页采集失败)这三个技巧花不了多少时间但能让你的毕业设计从“跑通了”变成“像个正经项目”。我带学生做的时候凡是加了 Streamlit 看板和日志的答辩分数都明显高一截。血泪经验是不要等到答辩前一天才想起来整理代码采集脚本、清洗脚本、可视化脚本分三个文件放好每个文件开头写清楚用途和运行顺序老师翻代码的时候一眼就能看懂。希望帮到你。本文还有配套的精品资源点击获取
返回列表