简介:这份资源是面向高校学生与Python初学者的二手房数据分析完整项目包,可直接用于毕业设计、期末大作业或课程设计场景。项目以真实二手房数据集为基础,覆盖数据读取、清洗、统计分析与可视化呈现的完整流程,代码注释详尽,新手也能快速理解与部署。压缩包共157个文件,约48.05MB,包含18个py源码文件、18个csv数据文件(含原始与清洗后的多版本数据)、15个html页面、11个js脚本,以及65张png图表、docx文档说明与pptx汇报资料,另附ini配置与字体文件,结构完整、开箱即用。目前已有127人学习下载。项目经严格调试,功能完善、界面美观、操作简便,配套文档与PPT可直接支撑答辩与报告撰写,帮助读者省去从零搭建的时间,快速掌握数据分析项目的实现思路与落地方法。
1. 二手房数据分析项目:从一堆 Excel 到能写进简历的完整交付物
很多人第一次接触“基于 Python 的二手房数据分析”这个题目,是在课程设计或毕业设计选题表上。标题看着简单——不就是爬数据、画图表吗?但真正动手才发现,数据源字段混乱、小区名字五花八门、总价和单价对不上、缺失值一删就少三成样本。更麻烦的是,做完分析还要交源码、文档说明和 PPT,三样东西缺一不可,而网上能找到的“免费 Python 源码大全”里,绝大多数只有一份跑不通的.ipynb,没有文档、没有数据字典、没有答辩能用的图表逻辑。
这篇文章面向三类人:正在做课程设计需要完整交付物的学生、想用二手房数据练手数据分析的 Python 入门者、以及需要一套可复现分析框架的从业者。我会按真实项目的推进顺序,把数据获取、清洗、分析建模、文档与 PPT 产出这条链路拆开讲,每个环节给出可抄的代码和参数说明,同时把我在这个方向上踩过的坑标出来。读完你拿到的不只是一个脚本,而是一套能直接改吧改吧就交差的工程结构。
2. 数据获取与工程目录:别把爬虫和分析混在一个文件里
2.1 为什么二手房数据建议“爬取 + 本地快照”双轨走
二手房数据的来源常见有两类:一类是公开的房产交易信息页面,另一类是链家、安居客等平台的历史成交记录。直接在线爬取有两个现实问题:第一,页面结构会变,今天能跑的xpath下周可能就失效;第二,分析过程中反复请求既慢又不稳定。我一般会采用“爬取一次、落地为 CSV、后续分析只读本地快照”的方式。
具体做法是:写一个独立的spider.py,只负责把原始字段抓下来存成raw_house.csv,不做任何清洗。分析脚本analysis.py只读这个 CSV。这样即使爬虫挂了,你手里的数据还在,分析进度不受影响。对于课程设计来说,这一步还能在文档里写成“数据采集与预处理分离”的架构说明,答辩时是个加分点。
注意:爬取时遵守目标站点的 robots.txt,控制请求频率,不要对目标服务器造成压力。课程设计场景下,用公开的少量样本数据做演示即可。
2.2 工程目录结构:让文档说明有东西可写
很多人文档写不出来,是因为代码全堆在一个文件里,没什么可描述的。下面这个目录结构是我反复用过的,每个目录都能在文档里对应一节说明:
second_hand_house/ ├── data/ │ ├── raw_house.csv # 爬取或下载的原始数据 │ └── clean_house.csv # 清洗后的分析用数据 ├── src/ │ ├── spider.py # 数据采集脚本 │ ├── clean.py # 数据清洗脚本 │ ├── analysis.py # 统计分析主脚本 │ └── visualize.py # 图表生成脚本 ├── output/ │ ├── figures/ # 生成的图表 PNG │ └── report_data.json # 分析结果汇总,供 PPT 引用 ├── docs/ │ └── 数据字典.md # 字段含义、取值范围、缺失情况 ├── requirements.txt └── README.md这个结构的好处是:docs/数据字典.md直接对应文档说明里的“数据说明”章节,output/report_data.json里的数字可以直接填进 PPT 的表格。你不需要在写文档时重新跑一遍代码去抄数字。
2.3 环境配置与依赖锁定
Python 环境这块,新手最容易翻车的地方是库版本冲突。我的习惯是每个项目建独立虚拟环境,用requirements.txt锁版本:
python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install pandas numpy matplotlib seaborn jieba wordcloud openpyxl pip freeze > requirements.txt参数说明:pandas负责表格处理,numpy做数值计算,matplotlib和seaborn出图,jieba和wordcloud用于小区名称或标题的词云,openpyxl让 pandas 能读写 Excel。如果你在vscode python 环境配置里遇到解释器选不对的问题,确认右下角选的是venv里的 python,而不是系统全局的。
依赖锁定后,文档里可以写“本项目基于 Python 3.10,依赖见 requirements.txt”,评审老师一看就知道你有工程意识。
3. 数据清洗:二手房字段的四个高频脏数据场景
3.1 总价、单价、面积三者的换算校验
二手房数据里最典型的脏数据是:总价、单价、面积三个字段对不上。比如总价 200 万、面积 89 平,单价却写着 18000 元/平——实际应该是 22472 元/平。这种数据如果不处理,后面做单价分布分析会严重失真。
我的处理逻辑是:以“总价 / 面积”重新计算单价,和原始单价对比,偏差超过 5% 的标记出来,用重算值覆盖。代码如下:
import pandas as pd import numpy as np df = pd.read_csv('data/raw_house.csv') # 统一面积单位为平方米,总价单位为万元 df['area'] = pd.to_numeric(df['area'], errors='coerce') df['total_price'] = pd.to_numeric(df['total_price'], errors='coerce') # 重算单价(元/平方米) df['unit_price_calc'] = df['total_price'] * 10000 / df['area'] # 原始单价与重算单价偏差超过 5% 的,用重算值覆盖 mask = (df['unit_price'].notna()) & (df['unit_price_calc'].notna()) deviation = np.abs(df.loc[mask, 'unit_price'] - df.loc[mask, 'unit_price_calc']) / df.loc[mask, 'unit_price_calc'] df.loc[mask & (deviation > 0.05), 'unit_price'] = df.loc[mask & (deviation > 0.05), 'unit_price_calc'] # 面积或总价缺失的行直接丢弃 df = df.dropna(subset=['area', 'total_price', 'unit_price'])逻辑说明:errors='coerce'把无法转换的值变成 NaN,避免字符串混入导致报错。偏差阈值 5% 是我试过比较合理的值,太严会误杀正常四舍五入,太松会放过明显错误。丢弃缺失行之前,先在文档里记录丢弃了多少条、占比多少,这是数据质量说明的一部分。
3.2 小区名称的标准化与区域字段拆分
小区名称是二手房分析里做词云和热度排名的核心字段,但原始数据里同一个小区可能有“万科城市花园”“万科城市花园一期”“万科城市花园(一期)”三种写法。不标准化,统计结果就是散的。
处理方式是:去掉括号及内容、去掉“一期/二期/三期”后缀、去掉首尾空格,然后统一转成简体。代码:
import re def normalize_community(name): if pd.isna(name): return name name = str(name).strip() name = re.sub(r'[((].*?[))]', '', name) # 去括号内容 name = re.sub(r'(一期|二期|三期|四期|五期)$', '', name) # 去期数后缀 name = re.sub(r'\s+', '', name) # 去所有空白 return name df['community_std'] = df['community'].apply(normalize_community)区域字段常见格式是“浦东-张江”“朝阳区-望京”,需要拆成“区域”和“板块”两列,方便后续做分组聚合。用str.split一次拆开:
df[['district', 'block']] = df['region'].str.split('-', n=1, expand=True)参数说明:n=1表示只拆第一个分隔符,防止板块名里本身带横杠。拆完后检查district的唯一值数量,如果超过 20 个,说明有别名没合并,需要手动维护一个映射字典。
3.3 楼层、朝向、装修程度的离散化编码
楼层字段常见写法有“中楼层/共 18 层”“低楼层/共 6 层”“高楼层”,朝向有“南”“南北”“东南”等。这些字段直接做统计没法用,需要离散化。
我的做法是:楼层拆成“楼层等级”和“总层数”两列,楼层等级映射为低(1)、中(2)、高(3);朝向按是否朝南、是否南北通透编码成两个布尔列;装修程度映射为毛坯(0)、简装(1)、精装(2)。
# 楼层拆分 df['floor_level'] = df['floor'].str.extract(r'(低|中|高)楼层') df['total_floors'] = df['floor'].str.extract(r'共(\d+)层').astype(float) floor_map = {'低': 1, '中': 2, '高': 3} df['floor_level_code'] = df['floor_level'].map(floor_map) # 朝向编码 df['is_south'] = df['orientation'].str.contains('南', na=False).astype(int) df['is_north_south'] = df['orientation'].str.contains('南北', na=False).astype(int) # 装修编码 decoration_map = {'毛坯': 0, '简装': 1, '精装': 2} df['decoration_code'] = df['decoration'].map(decoration_map)这些编码列在文档的数据字典里要写清楚映射关系,PPT 里做相关性分析时直接引用编码值,比放文字更直观。
3.4 异常值处理:面积 5 平米和 500 平米的房子
二手房数据里偶尔会出现面积 5 平米或 500 平米的记录,前者可能是车位或储藏室,后者可能是别墅或录入错误。我的处理策略是:面积小于 15 平米或大于 400 平米的记录,标记为异常,在分析时排除,但不从原始数据里删除。
df['is_outlier'] = ((df['area'] < 15) | (df['area'] > 400)).astype(int) df_analysis = df[df['is_outlier'] == 0].copy()这样做的好处是:文档里可以写“共识别异常记录 XX 条,占比 X%,分析时已排除”,体现数据质量控制的严谨性。如果直接删掉,原始数据就没了,后面想复查都找不到。
4. 分析建模与可视化:让 PPT 里的每张图都有数据来源
4.1 区域均价对比与性价比象限图
区域均价是二手房分析最基础的结论。用groupby按区域聚合,算均价、中位数、房源数量:
district_stats = df_analysis.groupby('district').agg( avg_unit_price=('unit_price', 'mean'), median_unit_price=('unit_price', 'median'), count=('unit_price', 'count'), avg_area=('area', 'mean') ).reset_index() district_stats = district_stats[district_stats['count'] >= 30] # 样本太少的区域不参与排名 district_stats = district_stats.sort_values('avg_unit_price', ascending=False)参数说明:count >= 30是为了保证统计显著性,样本太少的区域均价波动大,放进 PPT 容易被质疑。排序后取前 10 和后 10 做对比条形图。
性价比象限图是我在 PPT 里最常用的一张图:横轴是均价,纵轴是平均面积,气泡大小是房源数量。这样能一眼看出“均价低但面积大”的性价比区域,比单纯排名有说服力。用matplotlib的scatter实现,每个点标注区域名。
4.2 面积-总价散点与分段拟合
面积和总价的关系不是简单线性,小面积段单价高、大面积段单价低是常见规律。直接画散点图看不出趋势,我一般会按面积分段做拟合:
import numpy as np bins = [0, 60, 90, 120, 150, 400] labels = ['60平以下', '60-90平', '90-120平', '120-150平', '150平以上'] df_analysis['area_bin'] = pd.cut(df_analysis['area'], bins=bins, labels=labels) for label in labels: subset = df_analysis[df_analysis['area_bin'] == label] if len(subset) > 10: z = np.polyfit(subset['area'], subset['total_price'], 1) print(f'{label}: 总价 = {z[0]:.2f} * 面积 + {z[1]:.2f}')逻辑说明:np.polyfit做一次多项式拟合,返回斜率和截距。分段拟合后,每段的斜率就是该面积段的“边际单价”。PPT 里可以放一张分段拟合线叠加散点的图,说明“90-120 平是总价跳升最快的区间”。
4.3 小区热度词云与关键词提取
小区名称标准化之后,用jieba分词 +wordcloud出词云。但直接对小区名分词效果一般,因为小区名本身是专有名词。我的做法是:先统计小区出现频次,取 Top 50,再对这些名称做分词,提取“万科”“保利”“绿城”等开发商关键词和“花园”“公馆”“新城”等产品线关键词。
from wordcloud import WordCloud import jieba top_communities = df_analysis['community_std'].value_counts().head(50) text = ' '.join(top_communities.index) words = jieba.lcut(text) words = [w for w in words if len(w) >= 2] wc = WordCloud(font_path='SimHei.ttf', width=800, height=400, background_color='white') wc.generate(' '.join(words)) wc.to_file('output/figures/community_wordcloud.png')参数说明:font_path必须指定中文字体,否则词云全是方框。width和height按 PPT 版面调整,一般 800x400 够用。词云图在 PPT 里作为“市场热度概览”页,配合一段文字说明头部开发商的市场集中度。
4.4 把分析结果导出成 PPT 可直接引用的 JSON
PPT 制作最耗时的是从图表里抄数字。我的习惯是在analysis.py最后把所有关键指标写进一个 JSON:
import json report = { 'total_records': len(df_analysis), 'avg_unit_price': round(df_analysis['unit_price'].mean(), 2), 'median_unit_price': round(df_analysis['unit_price'].median(), 2), 'top_districts': district_stats.head(5).to_dict('records'), 'area_price_slope': {label: round(np.polyfit(subset['area'], subset['total_price'], 1)[0], 2) for label, subset in df_analysis.groupby('area_bin') if len(subset) > 10} } with open('output/report_data.json', 'w', encoding='utf-8') as f: json.dump(report, f, ensure_ascii=False, indent=2)这样写 PPT 时直接打开 JSON 抄数字,不用来回切窗口。文档说明里也可以附上这个 JSON 的字段说明,作为“分析结果输出”一节。
5. 避坑与常见问题:二手房分析项目里最容易翻车的五件事
5.1 爬虫字段和页面改版导致数据缺失
现象:爬下来的 CSV 里某一列全是空值,或者只有前几页有数据。原因:目标页面改版,原来的xpath或css selector失效,爬虫没报错但抓不到内容。解决:爬取时加字段非空校验,每抓一页打印一次字段填充率。发现填充率骤降就停下来检查页面结构。另外,爬虫脚本里把xpath写成配置项,改版时只改配置不改逻辑。
5.2 面积单位不统一导致单价计算错误
现象:单价分布图出现大量 10 万以上的异常值。原因:部分记录面积单位是“平方英尺”或“亩”,没有统一成平方米。解决:清洗时先检查面积字段的取值范围,正常住宅面积在 15-400 平米之间。超出这个范围的记录单独排查,确认单位后做换算。文档里写明“面积统一为平方米”。
5.3 中文乱码让词云和图表标题变方框
现象:matplotlib图表标题、词云里的中文全是方框。原因:默认字体不支持中文。解决:matplotlib里设置plt.rcParams['font.sans-serif'] = ['SimHei'],词云里指定font_path指向一个中文字体文件。Linux 环境下如果没有 SimHei,可以用WenQuanYi Micro Hei。这个坑几乎每个人都会踩一次,建议在visualize.py开头就统一设置。
5.4 虚拟环境没激活导致库装到全局
现象:pip install显示成功,但运行脚本时报ModuleNotFoundError。原因:终端没激活虚拟环境,包装到了系统 Python 里,而 IDE 用的是虚拟环境的解释器。解决:每次打开终端先执行激活命令,提示符前面出现(venv)再装包。vscode里按Ctrl+Shift+P选“Python: Select Interpreter”,选中venv里的 python。这个问题的血泪经验是:装完包先pip list确认一下,别急着跑代码。
5.5 文档和代码不同步,答辩时对不上
现象:文档里写的字段名和代码里的列名不一致,PPT 里的数字和最新跑出来的结果对不上。原因:改代码后没更新文档,或者文档是最后一天赶出来的。解决:把文档里的数据字典和分析结果都从代码里生成。数据字典可以用df.dtypes和df.describe()导出成 Markdown 表格,分析结果从report_data.json里抄。这样只要代码更新,文档跟着更新,不会出现对不上的情况。
6. 从能跑到能交:文档说明与 PPT 的产出技巧
6.1 文档说明的骨架:按代码模块写,不按论文格式写
课程设计的文档说明不需要写成学术论文,按代码模块组织反而更清晰。我的骨架是:项目概述(一段话)→ 环境与依赖 → 数据来源与字段说明 → 数据清洗流程 → 分析模块说明(每个脚本一节)→ 运行方式 → 结果展示。每一节对应src/下的一个文件,评审老师翻文档时能直接对应到代码。
数据字典用表格呈现,字段名、类型、含义、缺失率四列。缺失率从代码里算:
missing_rate = df.isnull().mean().round(4) * 100 data_dict = pd.DataFrame({ '字段名': df.columns, '类型': df.dtypes.astype(str).values, '缺失率(%)': missing_rate.values }) data_dict.to_markdown('docs/数据字典.md', index=False)这样生成的表格直接贴进文档,不用手写。
6.2 PPT 的图表复用:同一张图不要出现两次
PPT 常见问题是图表堆砌,同一张均价条形图在“数据概览”和“区域分析”两页各放一次。我的原则是:每张图只出现一次,每页 PPT 一个核心结论。图表从output/figures/里直接插入,标题用结论式写法,比如“浦东均价领跑,但 90-120 平段总价跳升最快”比“区域均价分析”更有信息量。
PPT 页数控制在 12-15 页:封面、项目背景、数据说明、清洗流程、分析框架、4-5 张核心图表、结论、致谢。每张图表页配一句话结论,数字从report_data.json里取。
6.3 一个让答辩加分的习惯:保留中间数据快照
我在data/目录下会额外保留clean_house.csv,这是清洗后但未做异常值排除的版本。答辩时如果老师问“为什么排除了某些数据”,可以直接打开这个文件展示原始记录和排除标记。这个习惯来自一次翻车经历:当时直接删了异常值,老师问起来拿不出证据,只能口头解释,说服力大打折扣。从那以后,所有中间数据都留快照,is_outlier标记列一直保留到最终输出。
希望帮到你。
本文还有配套的精品资源,点击获取