简介:这份资源是面向计算机、电子信息工程、数学等专业大学生的毕业设计与课程设计参考资料,围绕Python二手房数据采集与可视化分析展开,适合作为期末大作业或毕业设计的完整方案模板。压缩包共155个文件,约35.13MB,包含18个py脚本、18个csv数据集、15个html页面、11个js文件以及65张png图表,另有pptx报告、md说明与ini配置等,覆盖数据采集、清洗、存储、可视化到报告输出的完整链路。数据集提供原始与清洗后的多版本csv,便于对比处理效果。目前已有841人学习下载,说明该选题在同类项目中具有较高参考价值。读者可据此快速理解二手房数据的采集思路、字段清洗流程与可视化图表实现方式,并参考报告PPT结构组织自己的论文与答辩材料,适合具备一定Python基础、能自行调试与修改代码的学习者使用。
1. 从一份二手房数据到能讲清的报告:这套毕业设计到底在做什么
二手房数据采集与可视化分析这个题目,几乎每年毕业季都会被翻出来做一遍。原因很直接:数据源相对好找,业务逻辑人人都懂一点,图表做出来也好看。但真正动手就会发现,采集端要处理反爬和字段缺失,分析端要面对重复房源、面积口径不一致、挂牌价与成交价混在一起这些脏活。这份毕业设计源码加数据集加报告PPT的组合,核心价值不在于代码有多复杂,而在于它把一条完整的链路跑通了:从目标网站抓取房源字段,清洗成结构化表格,再用可视化把价格分布、区域热度、户型结构讲清楚。适合两类人:一是时间紧、需要一套能跑通并改得动的计算机毕业设计同学;二是想练手 Python 数据采集与 pandas 分析的入门者。下面按我实际做这类项目的顺序,把每个环节拆开讲。
2. 采集端怎么搭:requests 加解析库的最小可用方案
2.1 为什么选 requests + BeautifulSoup 而不是上框架
二手房列表页通常是服务端渲染的 HTML,字段直接嵌在标签里,用 requests 拿页面、BeautifulSoup 抽字段就够了。Scrapy 更适合大规模、多层级、需要去重调度的场景,但对一个毕业设计来说,引入框架反而增加调试成本。我的判断标准很简单:如果目标站点单页就能拿到全部字段,且总量在几千条以内,requests 方案两三百行就能收工。真正要花心思的是请求头、翻页逻辑和字段容错,而不是框架选型。
2.2 最小采集脚本与字段抽取
import requests from bs4 import BeautifulSoup import csv, time, random HEADERS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36", "Accept-Language": "zh-CN,zh;q=0.9", } def parse_page(html): soup = BeautifulSoup(html, "html.parser") rows = [] for item in soup.select("div.house-list-item"): # 列表项容器,按实际站点改 title = item.select_one("h3 a") price = item.select_one("span.total-price") area = item.select_one("span.area") rows.append({ "title": title.get_text(strip=True) if title else "", "price": price.get_text(strip=True) if price else "", "area": area.get_text(strip=True) if area else "", }) return rows def crawl(pages=5): all_rows = [] for p in range(1, pages + 1): url = f"https://example.com/sale/pg{p}/" # 替换为目标站点分页规则 resp = requests.get(url, headers=HEADERS, timeout=10) resp.encoding = resp.apparent_encoding all_rows.extend(parse_page(resp.text)) time.sleep(random.uniform(1.5, 3.5)) # 随机间隔,降低被封风险 return all_rows if __name__ == "__main__": data = crawl(pages=5) with open("houses.csv", "w", newline="", encoding="utf-8-sig") as f: writer = csv.DictWriter(f, fieldnames=["title", "price", "area"]) writer.writeheader() writer.writerows(data)这段代码的逻辑是:先定义请求头伪装成正常浏览器,再对每一页做 GET 请求,用 BeautifulSoup 按 CSS 选择器定位列表项,逐个抽取标题、总价、面积三个字段,最后写入 CSV。参数上,timeout=10防止某个请求卡死拖垮整个脚本;resp.apparent_encoding比写死 utf-8 更稳,因为不同站点编码不统一;random.uniform(1.5, 3.5)的随机间隔是血泪经验,固定间隔的请求节奏太规律,很容易触发风控。选择器div.house-list-item这类必须按目标站点实际 DOM 改,不要照抄。
2.3 翻页与字段容错的两个关键处理
翻页有两种常见形态:URL 里带页码参数,或者下一页按钮的链接。前者直接拼 URL 最省事,后者需要先解析出下一页 href 再请求。字段容错更重要——二手房列表里经常出现「暂无面积」「价格面议」这类文本,如果直接float()转换会抛异常。稳妥做法是抽取阶段保留原始字符串,清洗阶段再用正则提取数字,提取不到就置为 NaN,而不是在采集时就崩掉。另外,标题里常混着「满五唯一」「近地铁」这类标签,分析前要决定是保留还是剥离,这直接影响后续户型字段的解析。
3. 数据清洗:把脏字符串变成能算的表格
3.1 价格、面积、户型的标准化规则
采集下来的字段几乎全是带单位的字符串,比如「320万」「89.5平米」「3室2厅」。要参与统计就必须转成数值或结构化字段。价格统一转成万元,面积统一转成平方米,户型拆成室、厅两个整数列。这一步没有捷径,只能针对实际数据写规则,但规则要写得可复用,别硬编码某一条记录。
import pandas as pd import re df = pd.read_csv("houses.csv") def to_price(s): if not isinstance(s, str): return None m = re.search(r"(\d+(?:\.\d+)?)", s) return float(m.group(1)) if m else None def to_area(s): if not isinstance(s, str): return None m = re.search(r"(\d+(?:\.\d+)?)", s) return float(m.group(1)) if m else None def split_layout(s): if not isinstance(s, str): return None, None m = re.search(r"(\d+)室(\d+)厅", s) return (int(m.group(1)), int(m.group(2))) if m else (None, None) df["price"] = df["title"].apply(to_price) if "price" not in df else df["price"].apply(to_price) df["area"] = df["area"].apply(to_area) df["rooms"], df["halls"] = zip(*df["title"].apply(split_layout)) df["unit_price"] = (df["price"] * 10000 / df["area"]).round(0) # 元/平米 df = df.dropna(subset=["price", "area"]) df = df[(df["area"] > 10) & (df["area"] < 1000)] # 剔除异常面积 df.to_csv("houses_clean.csv", index=False, encoding="utf-8-sig")逻辑说明:三个转换函数都用正则提取第一个数字,兼容「320万」「320.5万」这类写法。split_layout用捕获组同时拿到室和厅。unit_price是单价,单位换算成元每平米,这是后续分析最有价值的字段之一。参数上,面积过滤区间(10, 1000)是经验值,能滤掉明显录入错误;dropna放在转换之后,保证不会因为个别脏数据丢掉整行有效信息。
3.2 去重与异常值:两个最容易翻车的地方
同一套房源可能因为重新挂牌出现多次,标题和价格几乎一样。简单按标题去重会误杀,稳妥做法是组合「小区名 + 面积 + 户型」做近似去重,或者按标题相似度加价格接近度判断。异常值方面,单价明显偏离区域均值的记录要单独看,可能是别墅、商住两用或数据错误,直接删会丢信息,保留又会拉偏均值。我一般会先算分位数,把超过 99 分位和低于 1 分位的记录单独导出人工核对,而不是一刀切。
4. 可视化分析:让图表真正回答业务问题
4.1 价格分布与区域对比怎么画才有信息量
可视化最容易犯的错是「为了好看而画」。二手房分析真正要回答的是:价格集中在哪个区间、哪些区域单价高、户型结构和总价是什么关系。价格分布用直方图加核密度曲线,区域对比用箱线图比用柱状图更能看出离散程度。下面用 matplotlib 画两张核心图。
import matplotlib.pyplot as plt import matplotlib matplotlib.rcParams["font.sans-serif"] = ["SimHei"] # 中文字体 matplotlib.rcParams["axes.unicode_minus"] = False df = pd.read_csv("houses_clean.csv") fig, axes = plt.subplots(1, 2, figsize=(14, 5)) axes[0].hist(df["unit_price"], bins=40, color="#4C72B0", edgecolor="white") axes[0].set_title("单价分布") axes[0].set_xlabel("元/平米") axes[0].set_ylabel("房源数") df.boxplot(column="unit_price", by="district", ax=axes[1], rot=45) axes[1].set_title("各区域单价对比") axes[1].set_xlabel("区域") axes[1].set_ylabel("元/平米") plt.tight_layout() plt.savefig("price_analysis.png", dpi=150)逻辑说明:左图直方图看整体分布形态,bins=40是经验值,太少看不出细节,太多噪声大。右图箱线图按区域分组,能同时看到中位数、四分位和离群点,比柱状图信息密度高。参数上,dpi=150保证导出图片在报告里清晰;中文字体必须设置,否则标题全是方块,这是新手最常见的翻车点。district字段需要在清洗阶段从标题或地址里解析出来,如果原始数据没有,就得补一步区域提取。
4.2 户型与总价的关系:用散点图找规律
户型和总价的关系适合用散点图,颜色区分区域,能一眼看出哪些区域大户型集中。如果要做交互式图表,pyecharts 是常见选择,导出 HTML 嵌进报告 PPT 也方便。但要注意,交互图在静态报告里展示效果有限,答辩时现场演示更合适。静态图用 matplotlib 或 seaborn 足够,别为了炫技堆一堆用不上的图表。
5. 避坑与常见问题:采集分析路上绕不开的五个坑
5.1 请求被拒或返回空页面
现象:脚本跑几页后返回 403 或空 HTML。原因:请求频率过高或请求头不完整,被服务端识别为异常流量。解决:补全 User-Agent、Referer、Accept 等头部,加大随机间隔,必要时降低单次采集页数,分批次跑。
5.2 中文字段乱码
现象:CSV 打开后中文全是乱码。原因:写入编码和读取编码不一致。解决:写 CSV 统一用utf-8-sig,Excel 能正确识别;读取时显式指定encoding="utf-8-sig",不要依赖默认值。
5.3 价格转换后大量为空
现象:to_price返回一堆 None。原因:部分记录价格写成「面议」「暂无」或藏在其他标签里。解决:先统计空值比例,如果超过两成,说明选择器或字段定位有问题,回去检查 DOM;如果只是少量,直接 dropna 即可,别为个别记录改坏整体规则。
5.4 图表中文显示成方块
现象:matplotlib 图上标题和标签全是方框。原因:默认字体不含中文。解决:设置font.sans-serif为 SimHei 或系统已有中文字体,同时把axes.unicode_minus设为 False,否则负号也会异常。
5.5 分析结论和常识矛盾
现象:算出来的区域均价和实际感受差很远。原因:样本偏差,比如某区域只采到几套高价房源,或者商住和住宅混在一起。解决:按房源类型分层统计,样本量过少的区域标注说明,不要直接下结论。
6. 从能跑到能讲:报告与答辩的进阶处理
代码跑通只是及格线,毕业设计真正拉开差距的是能不能把数据讲成一个有逻辑的故事。我的习惯是先把清洗后的数据做几组交叉统计,比如「区域 × 户型」的均价透视表,再挑出三到四个反直觉的发现作为报告主线。比如某个远郊区域单价低但总价中位数不低,原因是户型偏大,这种结论比单纯说「A区比B区贵」有说服力得多。
pivot = df.pivot_table( values="unit_price", index="district", columns="rooms", aggfunc="median" ).round(0) print(pivot)这段透视表按区域和室数算单价中位数,能快速定位「哪个区域哪种户型最贵」。用中位数而不是均值,是为了避免少数高价房源拉偏结果。参数上,aggfunc可以换成count看样本量,样本太少的格子要谨慎解读。
报告 PPT 的结构建议按「问题—数据—方法—发现—结论」走,图表只放支撑结论的那几张,不要把脚本里所有图都塞进去。答辩时最常被问的是数据来源合法性、样本量和清洗规则,提前把这三块准备好。数据集和源码要保证能在他人的环境里跑通,依赖版本写进 requirements,路径用相对路径,别写死自己电脑的绝对路径。
我自己做这类项目最大的教训是:一开始总想采集越多越好,结果清洗阶段被脏数据拖垮,后来改成先小批量跑通全流程,再逐步扩大采集量,效率反而高得多。数据采集与分析这件事,跑通闭环比堆量重要。希望帮到你。
本文还有配套的精品资源,点击获取