Python二手房数据分析实战:从爬虫到报告全流程
发布时间:2026/9/28 18:08:50来源:尧图网络
简介这是一份面向计算机相关专业学生与项目实战学习者的Python数据分析资源对应课程设计与期末大作业场景围绕二手房数据完成采集、清洗、统计分析与可视化全流程可作为高分作业参考模板。压缩包共190个文件约48.21MB包含18个py源码文件、18个csv数据文件含原始与清洗版本、15个html图表页面、65个png可视化结果以及docx分析报告、pptx展示文稿和说明文档覆盖代码、数据、报告与演示材料。已有65人学习下载。读者可直接获得可运行的完整项目源码与多版本数据集对照分析报告理解指标解读与结论撰写思路并借助图表与展示文件快速梳理汇报结构适合需要快速上手数据分析实战、查漏补缺的学习者。1. 二手房数据分析项目从爬虫到报告一套能跑通的 Python 全流程二手房市场有个特点同一套房源在不同平台挂的价格能差出十几万挂牌价和成交价之间还藏着大量水分。想搞清楚一个城市的真实行情靠人工刷页面不现实用 Excel 手动整理几千条数据也迟早翻车。这个项目要解决的就是这件事用 Python 把房源数据抓下来、清洗干净、算出各区域的真实均价和议价空间最后生成一份能直接看的分析报告。整套流程包含源码、数据集和报告模板适合会一点 Python 基础、想拿一个完整项目练手的人也适合做房产中介、投资分析的朋友拿来改改就能用。核心链路是四步采集、清洗、分析、出报告每一步都有具体的坑要绕。2. 数据采集用 requests BeautifulSoup 抓房源列表2.1 为什么选静态页面抓取而不是上 Selenium二手房数据采集最常见的做法有两种一种是直接请求页面 HTML 然后解析另一种是用浏览器自动化工具模拟点击翻页。我一般优先选前者原因很直接——速度快、资源占用低、代码好维护。Selenium 启动一个浏览器实例就要几百兆内存抓几千条数据下来光是等待页面渲染的时间就够你喝杯咖啡了。但这里有个前提目标页面的房源列表是服务端渲染的也就是你右键查看网页源代码能看到房源标题和价格。如果页面是前端 JS 动态加载的那 requests 拿到的 HTML 里就是空的这时候才需要考虑 Selenium 或者直接找后端 API。判断方法很简单用下面这段代码试一下import requests url https://example.com/ershoufang/pg1/ 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 } resp requests.get(url, headersheaders, timeout10) print(resp.status_code) print(len(resp.text)) # 在返回的文本里搜索一个你知道肯定存在的房源标题关键词 print(房源关键词 in resp.text)如果最后一行输出True说明数据在 HTML 里可以走静态解析路线。如果输出False要么换思路要么去找页面背后的数据接口。2.2 房源列表页的解析脚本与字段提取确认页面结构后用 BeautifulSoup 提取房源链接、标题、总价、单价、区域、楼层、朝向等字段。下面是一个可复用的解析函数from bs4 import BeautifulSoup import re def parse_list_page(html): soup BeautifulSoup(html, html.parser) items [] for li in soup.select(.sellListContent li): try: title li.select_one(.title a).get_text(stripTrue) link li.select_one(.title a)[href] total_price li.select_one(.totalPrice span).get_text(stripTrue) unit_price li.select_one(.unitPrice span).get_text(stripTrue) # 单价字段通常带元/平米后缀用正则去掉 unit_price re.sub(r[^\d], , unit_price) region li.select_one(.positionInfo a).get_text(stripTrue) items.append({ title: title, link: link, total_price: float(total_price), unit_price: int(unit_price), region: region }) except Exception as e: # 单条解析失败不影响整体记录后跳过 print(f解析跳过一条: {e}) continue return items这段代码的关键点有三个。第一select用的 CSS 选择器要跟目标页面实际结构对齐不同平台类名不一样需要自己打开开发者工具确认。第二单价字段里的“元/平米”必须去掉否则后面转 int 会报错。第三用 try-except 包住单条解析因为列表页里偶尔会混入广告位或格式异常的条目一条出错不能让整个采集中断。2.3 翻页策略与请求频率控制翻页本身不难把页码拼进 URL 循环请求就行。真正需要注意的是请求频率。我见过太多人写个 for 循环不加延时几十页请求发出去轻则被封 IP重则收到警告。稳妥的做法是每次请求之间随机等待 2 到 5 秒并且每抓完一页就把数据追加写入 CSV避免程序中途挂掉导致前面抓的全丢。import time import random import csv def crawl_pages(base_url, max_page50): all_items [] for page in range(1, max_page 1): url base_url.format(pagepage) try: resp requests.get(url, headersheaders, timeout10) if resp.status_code ! 200: print(f第{page}页返回{resp.status_code}停止) break items parse_list_page(resp.text) if not items: print(f第{page}页无数据可能已到末尾) break all_items.extend(items) # 每页抓完立即落盘 with open(houses.csv, a, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnamesitems[0].keys()) writer.writerows(items) print(f第{page}页完成累计{len(all_items)}条) except requests.RequestException as e: print(f第{page}页请求异常: {e}) time.sleep(random.uniform(2, 5)) return all_items参数方面max_page根据目标城市实际页数设一般二线城市在 50 到 100 页之间。timeout设 10 秒足够太短容易误判超时太长卡住浪费时间。随机延时范围 2 到 5 秒是经验值再短就有风险再长效率太低。3. 数据清洗把脏数据变成能算的表格3.1 二手房数据里最常见的五类脏数据抓下来的原始数据几乎不可能直接用来分析。我整理过十几个城市的房源数据以下五类问题几乎每次都会遇到第一面积字段缺失或格式混乱。有的写“89.5平米”有的写“89.5㎡”还有的干脆是空字符串。第二楼层信息五花八门“中楼层/共18层”“低楼层(共6层)”“地下室”混在一起需要拆出所在楼层和总楼层两个字段。第三朝向字段有“南”“南北”“东南”“东西”等多种组合分析时通常只关心是否朝南。第四同一套房源可能被重复抓取需要按链接或标题去重。第五总价和单价之间存在不一致比如总价 200 万、面积 89 平算出来单价应该是 22472 元/平但页面上写的单价可能是 22000这种偏差需要标记出来。3.2 用 pandas 做清洗的完整流程清洗环节用 pandas 最顺手。下面这段代码覆盖了去重、缺失值处理、字段拆分和类型转换import pandas as pd import numpy as np df pd.read_csv(houses.csv) # 1. 按链接去重 df df.drop_duplicates(subset[link]) # 2. 面积字段清洗提取数字 df[area] df[area].astype(str).str.extract(r(\d\.?\d*)) df[area] pd.to_numeric(df[area], errorscoerce) # 3. 楼层拆分 df[floor_info] df[floor].astype(str) df[total_floors] df[floor_info].str.extract(r共(\d)层) df[total_floors] pd.to_numeric(df[total_floors], errorscoerce) df[is_low] df[floor_info].str.contains(低楼层).astype(int) df[is_high] df[floor_info].str.contains(高楼层).astype(int) # 4. 朝向简化是否朝南 df[facing_south] df[orientation].astype(str).str.contains(南).astype(int) # 5. 删除关键字段缺失的行 df df.dropna(subset[area, total_price, unit_price]) df df[df[area] 10] # 面积小于10平的通常是车位或异常数据 # 6. 重新计算单价和页面单价对比 df[calc_unit_price] (df[total_price] * 10000 / df[area]).round(0) df[price_diff] abs(df[calc_unit_price] - df[unit_price]) df[price_abnormal] (df[price_diff] df[unit_price] * 0.1).astype(int) print(f清洗后剩余{len(df)}条异常单价{df[price_abnormal].sum()}条) df.to_csv(houses_clean.csv, indexFalse)几个参数需要说明。面积提取的正则(\d\.?\d*)能匹配整数和小数但遇到“暂无数据”这类文本会返回 NaN后面用dropna处理。单价偏差阈值设 10%超过这个比例就标记为异常分析时可以单独看这批数据是不是录入错误。面积下限设 10 平米是因为二手房列表里经常混入车位、储藏室这些数据会严重拉低区域均价。3.3 清洗后的数据质量校验清洗完不能直接开算得先做一轮校验。我一般会检查四个指标总记录数、各区域记录数分布、单价的中位数和四分位距、异常单价占比。如果某个区域只有个位数记录那这个区域的均价就没有代表性分析报告里要标注出来。单价四分位距过大说明数据里混入了豪宅或老破小需要分区域分价位段来看。# 区域分布检查 region_counts df[region].value_counts() print(region_counts[region_counts 10]) # 少于10条的区域 # 单价分布 print(df[unit_price].describe(percentiles[0.25, 0.5, 0.75])) # 按区域看均价 region_price df.groupby(region)[unit_price].agg([median, count]) region_price region_price[region_price[count] 10] print(region_price.sort_values(median, ascendingFalse))这段校验代码输出的结果直接决定了后面分析报告的可信度。记录数不足的区域要么合并到相邻区域要么在报告中注明“样本量不足仅供参考”。4. 分析建模区域均价、议价空间与可视化4.1 算清楚三个核心指标二手房分析最常被问到的三个问题是这个区域均价多少挂牌价和成交价差多少什么样的房子单价最高对应到数据上就是三个指标区域单价中位数、议价空间比例、单价影响因素排序。区域单价中位数比均值更靠谱因为均价容易被少数高价房源拉偏。议价空间需要成交价数据如果只有挂牌价可以用同一小区不同房源的挂牌价差异来近似。单价影响因素可以用简单的相关性分析看面积、楼层、朝向、区域分别和单价的相关程度。# 区域单价中位数 region_median df.groupby(region)[unit_price].median().sort_values(ascendingFalse) # 议价空间近似同一区域内的单价标准差除以中位数 region_cv df.groupby(region)[unit_price].agg( lambda x: x.std() / x.median() ).sort_values(ascendingFalse) # 相关性分析 corr_cols [area, total_floors, is_low, is_high, facing_south, unit_price] corr_matrix df[corr_cols].corr() print(corr_matrix[unit_price].sort_values(ascendingFalse))region_cv越大说明这个区域房源价格越分散议价空间可能越大。相关性分析里如果area和unit_price呈负相关说明小户型单价更高这在核心城区很常见。4.2 用 matplotlib 出三张关键图表报告里至少要有三张图区域均价柱状图、单价分布箱线图、面积与单价散点图。代码不复杂关键是图要能说明问题。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False # 图1区域均价 fig, ax plt.subplots(figsize(10, 6)) region_median.plot(kindbar, axax, color#4C72B0) ax.set_title(各区域二手房单价中位数元/平米) ax.set_ylabel(单价) plt.tight_layout() plt.savefig(region_price.png, dpi150) # 图2单价分布箱线图 fig, ax plt.subplots(figsize(10, 6)) df.boxplot(columnunit_price, byregion, axax, rot45) ax.set_title(各区域单价分布) ax.set_ylabel(单价) plt.suptitle() plt.tight_layout() plt.savefig(price_box.png, dpi150) # 图3面积与单价散点 fig, ax plt.subplots(figsize(8, 6)) ax.scatter(df[area], df[unit_price], alpha0.3, s10) ax.set_xlabel(面积平米) ax.set_ylabel(单价元/平米) ax.set_title(面积与单价关系) plt.tight_layout() plt.savefig(area_price.png, dpi150)中文字体那两行必须加否则图表里的中文会变成方块。alpha0.3让散点半透明数据量大时能看出密度分布。dpi150保证图片清晰度够放进报告。4.3 把分析结果落成一份可读的报告报告不需要花哨用 Markdown 或者 Word 都行结构建议固定为数据概览、区域对比、价格影响因素、异常数据说明、结论。数据概览写清楚采集时间、样本量、覆盖区域。区域对比放柱状图和表格。价格影响因素放相关性结果和散点图。异常数据说明列出被标记的异常单价条目数量和占比。结论部分用两三句话总结哪个区域性价比高、什么户型单价偏高。如果想让报告自动生成可以用jinja2模板把分析结果填进去再转成 HTML 或 PDF。这一步不是必须的但做一次之后以后换城市换数据改几个参数就能重新出一份报告省事很多。5. 避坑指南数据采集与分析中的五个血泪教训5.1 页面结构一变选择器全部失效现象昨天还能正常跑的爬虫今天返回的房源数量变成零或者解析出来的字段全是空值。原因目标网站调整了页面 HTML 结构类名或层级变了。这是最常见也最让人头疼的问题没有之一。解决不要把 CSS 选择器写死在代码里抽出来放到配置文件或常量区。每次运行前先手动请求一页检查关键选择器是否还能匹配到元素。更稳妥的做法是同时用多个备选选择器按优先级依次尝试。5.2 单价字段带单位导致类型转换报错现象ValueError: invalid literal for int() with base 10: 45000元/平米。原因页面上的单价文本包含“元/平米”后缀直接转 int 或 float 会失败。解决转换前先用正则去掉所有非数字字符。注意有些页面用“万”做单位比如“4.5万/平米”这种情况要先判断是否含“万”再乘以 10000。5.3 重复数据导致区域均价被拉偏现象某个小区的房源数量异常多算出来的区域均价明显偏离市场感知。原因同一套房源在列表页出现多次或者翻页时因为页面排序变化导致重复抓取。解决按房源链接去重是最可靠的方式。如果链接也重复可以组合标题加总价加面积做联合去重。去重后要重新检查各区域样本量样本太少的区域在报告中要标注。5.4 中文字体缺失导致图表全是方块现象matplotlib 生成的图表里标题和坐标轴的中文显示为一个个小方块。原因matplotlib 默认字体不支持中文需要手动指定系统中已有的中文字体。解决在绘图前设置plt.rcParams[font.sans-serif] [SimHei]Windows 系统一般都有 SimHei。如果是 Mac 或 Linux换成[Arial Unicode MS]或[WenQuanYi Micro Hei]。设置完记得加plt.rcParams[axes.unicode_minus] False否则负号也会显示异常。5.5 异常值不处理导致结论失真现象报告里某个区域的均价明显偏高点进去看发现是几套别墅或学区房拉高了均值。原因没有对极端值做处理少数高价房源对均值影响很大。解决用中位数代替均值作为区域均价的主要指标。同时可以按单价做分位数过滤比如只保留 5% 到 95% 分位之间的数据。如果确实要分析高端市场就单独分一个价位段来看不要和普通住宅混在一起。6. 进阶技巧把项目变成可复用的分析模板项目跑通一次不难难的是换一个城市、换一个平台还能快速复用。我的习惯是把整个流程拆成配置驱动城市名、目标 URL 模板、页面选择器、清洗规则、报告模板全部放到一个config.yaml里主程序只负责按配置执行。这样下次想分析另一个城市改配置文件就行不用动代码。import yaml with open(config.yaml, r, encodingutf-8) as f: config yaml.safe_load(f) city config[city] base_url config[base_url] selectors config[selectors] # 后续采集和解析都用 config 里的值对应的config.yaml大概长这样city: 成都 base_url: https://example.com/ershoufang/pg{page}/ selectors: list_item: .sellListContent li title: .title a total_price: .totalPrice span unit_price: .unitPrice span region: .positionInfo a另一个值得做的进阶方向是加一层简单的验证。每次分析完自动检查几个关键指标是否在合理范围内比如区域均价不能低于 3000 也不能高于 100000样本量不能少于 50 条。如果触发异常就发个提醒或者直接在报告里标红。这个习惯帮我省过好几次事有一次数据源改版导致抓到的全是车位信息均价算出来只有几千块验证层直接拦住了不然那份报告发出去就闹笑话了。最后说一个我自己的教训不要等到所有功能都写完再跑完整流程。我刚开始做这类项目时习惯先把采集、清洗、分析、报告全部写完再一次性运行结果每次都在某个环节卡住回头改代码又要重新跑一遍。后来改成每写完一个模块就用小批量数据跑通确认输出没问题再往下写整体效率反而高了很多。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网