当前位置: 首页 > news >正文

Python二手房数据采集+清洗+可视化全流程实战包(含可运行代码与图表截图)

本文还有配套的精品资源,点击获取

简介:一套开箱即用的二手房数据自动化分析工具,基于Python实现从主流房产网站抓取房源标题、售价、面积、户型、楼层、区域等结构化字段;内置请求头轮换、随机延时、基础反反爬适配逻辑,兼顾稳定性与合规性;数据自动去重、空值处理、价格单位统一、面积格式标准化;支持一键导出清洗后数据为CSV或Excel文件;集成Matplotlib和Seaborn绘制10类实用图表:城市房价热力图、各行政区均价对比柱状图、户型分布饼图、价格与面积散点关系图、房龄分布直方图、挂牌天数趋势折线图等;项目包含完整源码、清晰README配置说明、运行日志示例及20余张真实截图(涵盖控制台执行过程、DataFrame表格预览、图表渲染效果),适用于本科毕业设计、数据分析入门练习或教学演示。

1. 项目概述:这不是一个“爬虫教程”,而是一套能直接跑通、能交差、能出图的二手房分析工作流

我带过三届毕业设计,每年都有至少七八个学生卡在“数据从哪来”这一步。有人花两周写了个漂亮爬虫,结果平台一改结构就全崩;有人好不容易存了5000条数据,发现价格字段混着“120万”“85.5w”“总价约320万元”三种格式;还有人Matplotlib画完图发群里,被导师一句“坐标轴没单位、标题没说明、颜色没区分度”打回重做。这套东西,就是我把自己踩过的所有坑、调过的所有参数、反复验证过的每一段逻辑,打包成一个“开箱即用”的闭环——它不教你Python语法,也不讲HTTP协议原理,它只解决一件事:从你双击run_analysis.py开始,到你把生成的price_heatmap.pngdistrict_avg_bar.png贴进论文第3章,全程不超过40分钟。核心关键词——Python爬虫、二手房数据采集、数据清洗、数据可视化、房价分析——不是标签,而是每个环节的真实落点。它适合谁?正在赶毕设 deadline 的本科生、想用真实数据练手的数据分析新手、需要快速产出教学案例的讲师,或者单纯想看看自己小区房价到底处在什么水平的普通人。它不承诺“100%适配所有网站”,但承诺“你按README操作,95%概率第一遍就能跑出带坐标轴的热力图”。下面所有内容,都是基于这个前提展开的:稳定、可复现、有结果、能解释。

2. 整体设计与思路拆解:为什么是“采集-清洗-可视化”三段式,而不是一步到位?

很多人拿到需求第一反应是:“写个大循环,一边爬一边画图不就行了?”我试过,也崩溃过。去年帮一个学生改毕设,他硬生生把爬虫、清洗、绘图全塞进一个main()函数里,代码2000行,调试时改一个正则表达式,整个流程得重跑两小时。后来我们把它彻底拆开,不是为了显得“架构高级”,而是因为这三个环节的失败模式、调试成本、迭代频率完全不同

2.1 采集层:稳字当头,宁慢勿错

采集的核心矛盾从来不是“快”,而是“活”。主流房产平台(比如链家、贝壳、安居客)的反反爬策略,本质是“识别非人类行为模式”。我们不用对抗,而是模拟。项目里内置的策略不是靠撞库或暴力请求,而是基于三个可量化、可配置的维度:

  • 请求头轮换:不是简单地随机换User-Agent。我收集了近半年Chrome、Edge、Safari主流版本的真实请求头样本,剔除掉明显异常的字段(比如Sec-Ch-Ua-Platform: "Windows"这种固定值),保留User-AgentAccept-LanguageAccept-Encoding三个最常被校验的字段,并做成一个JSON列表。每次请求前,从列表里随机选一组,再微调Referer为上一页URL(如果是首页,则设为平台主域名)。这样做的依据是:真实用户切换浏览器或刷新页面时,这些字段组合是自然变化的,而非完全随机。

  • 随机延时:很多人用time.sleep(random.uniform(1, 3)),这反而危险。真实用户浏览时,停留时间服从长尾分布——看标题可能0.5秒,读详情可能15秒。所以代码里用的是random.betavariate(2, 5)生成一个0~1之间的数,再映射到1~8秒区间。Beta分布的峰值在0.3左右,意味着大部分请求间隔在2~3秒,但偶尔会出现6秒以上的停顿,更贴近真实行为。这个参数我在测试时对比过:用均匀分布,连续爬取200页后触发验证码的概率是37%;用Beta分布,降到12%。

  • 基础反反爬适配:这里没有用任何第三方库(如Selenium),纯Requests+BeautifulSoup。关键在于对两个信号的响应:一是HTTP状态码非200时,不直接报错,而是记录URL和状态码,进入“降级重试队列”;二是解析时发现关键字段(如pricearea)为空,不跳过该条目,而是标记为status="parse_failed",后续清洗阶段统一处理。这样做的好处是,采集层只负责“尽力而为”,把“不确定”交给下游判断,避免因单条数据失败导致整页丢弃。

提示:采集模块输出的原始CSV,第一列永远是crawl_timestamp(精确到毫秒),第二列是source_url。这是后续排查问题的黄金线索——当你发现某条数据价格异常,直接按时间戳去日志里搜,就能看到当时抓到的原始HTML片段,比对着正则表达式猜强十倍。

2.2 清洗层:数据不是“脏”或“干净”,而是“可解释”与“不可解释”

清洗不是把空值填上、把字符串转数字就完了。真正的难点在于:如何让清洗后的每一行数据,都能经得起一句“这个数字是怎么来的”追问。比如价格字段,原始数据可能是“¥120万/㎡”、“总价约320万元”、“单价8.5w”、“3.25亿(别墅)”。我们的清洗逻辑分四步走:

  1. 单位归一化:先用正则匹配所有货币符号(¥、$、¥)和单位(万、亿、w、W、元、yuan),提取数值部分。关键点在于“万”和“亿”的处理——不是简单乘以10000或100000000,而是结合上下文判断。如果字段名含unit_price(单价),则“120万/㎡”中的120直接作为数值;如果字段名含total_price(总价),则“320万元”转为3200000,“3.25亿”转为325000000。这个逻辑写在clean_price()函数里,有详细注释说明每种case的判定依据。

  2. 空值与异常值标注:清洗不盲目填充。area字段为空,不填0,而是标为area_raw="N/A",并在area_cleaned列写null_reason="missing"floor字段是“低楼层(共32层)”,不强行拆成数字,而是标为floor_raw="low"floor_cleaned="low"。所有清洗动作都生成_raw_cleaned两列,中间用clean_log列记录操作步骤(如"step1: extract number from '120万/㎡' -> 120; step2: unit '万/㎡' -> multiply by 10000 -> 1200000")。这样导出的Excel,打开就能看到数据血缘。

  3. 业务规则注入:清洗不是纯技术活。比如“挂牌天数”,原始数据可能是“已挂牌3天”、“3小时前”、“昨天”。我们定义了一套业务规则:<1小时记为0,1小时~24小时记为1,>24小时则按日期计算。又比如“房龄”,原始字段常是“2015年建”、“满五年”、“次新房”。代码里专门有个infer_age()函数,根据当前年份减去建成年份,若无建成年份,则按“满五年”=5年、“次新”=3年、“老破小”=25年等规则估算,并在age_source列注明是“parsed_from_text”还是“inferred_by_rule”。

  4. 去重逻辑的颗粒度:不是简单按title+price+area去重。真实场景中,同一套房可能被不同中介重复发布,标题微调(加“急售!”、“业主直卖”)、价格虚标(标价120万,实谈115万)、面积写“约89㎡”。所以去重用的是指纹哈希:对district(行政区)、community_name(小区名)、building_no(楼号)、unit_no(单元号)、room_no(房号)这五个强定位字段做MD5,只要指纹相同,即视为同一房源,保留crawl_timestamp最新的那条。这个逻辑在deduplicate_by_fingerprint.py里,比Pandas的drop_duplicates()多花0.3秒,但误删率从18%降到0.7%。

2.3 可视化层:图表不是“好看”,而是“能说话”

Matplotlib和Seaborn本身不难,难的是让图表承载业务洞察。项目里10+种图表,每一种都对应一个明确的分析问题:

  • 城市房价热力图:不是简单把经纬度打点。而是先用geopandas加载城市行政区划GeoJSON,再用scipy.interpolate.griddata对离散的房源点做插值,生成200×200的网格价格矩阵,最后叠加在地图底图上。这样做的好处是,即使某区域房源少,热力图也能反映趋势,而不是一片空白。

  • 区域均价柱状图:重点在排序和标注。横轴按均价从高到低排,但前三位用不同颜色突出;每个柱子顶部标出具体数值(如¥85,200),并用小号字体在下方标出该区房源总数(如n=1,247)。这样一眼能看出“贵在哪”和“量有多少”。

  • 户型占比饼图:拒绝默认的360度分割。把占比<5%的户型合并为“其他”,并用pctdistance=0.85把百分比标签拉到外圈,避免重叠;同时在图例里注明“数据来源:清洗后house_type字段,已标准化为‘一居室’‘二居室’‘三居室’‘四居室+’四类”。

  • 价格-面积散点图:核心是添加回归线和置信区间。用seaborn.regplot()scatter_kws={'alpha':0.6}降低点透明度防重叠,line_kws={'color':'red','linestyle':'--'}画出拟合线,并用ci=95显示95%置信带。这样不仅能看趋势,还能评估相关性强度。

注意:所有图表生成函数都接受一个output_dir参数,并自动按{chart_name}_{timestamp}.png命名。这意味着你跑十次,不会覆盖上次的图,方便对比迭代效果。

3. 核心细节解析与实操要点:从代码结构到关键参数,一个都不能少

项目目录不是随便堆的,每一层都有明确分工。打开压缩包,你会看到这样的结构:

WGmXUawRCExeTUpZSYbx-master-413f72e956072fefffd0cb494fe4c886feae2e71/ ├── config/ │ ├── settings.py # 全局配置:超时时间、重试次数、并发数 │ └── headers.json # 请求头轮换池(50组真实样本) ├── crawler/ │ ├── __init__.py │ ├── base_crawler.py # 抽象基类,定义crawl_page()等接口 │ ├── lianjia_crawler.py # 链家专用爬虫(已适配其Ajax接口) │ └── utils.py # 公共工具:URL拼接、HTML清理、日志记录 ├── data_cleaning/ │ ├── __init__.py │ ├── clean_pipeline.py # 清洗主流程:调用各clean_*函数 │ ├── clean_price.py # 价格清洗(含单位识别、异常值过滤) │ └── deduplicate_by_fingerprint.py # 指纹去重核心逻辑 ├── visualization/ │ ├── __init__.py │ ├── chart_generator.py # 图表工厂:generate_heatmap(), generate_bar_chart()等 │ └── style_config.py # 统一图表样式:字体、颜色、尺寸 ├── data_analysis/ # 输出目录(自动生成) │ ├── raw/ # 原始采集CSV │ ├── cleaned/ # 清洗后CSV/Excel │ └── figures/ # 所有图表PNG ├── scripts/ │ └── run_analysis.py # 入口脚本:一键执行全流程 ├── README.md # 配置说明、运行步骤、截图预览 └── requirements.txt

3.1settings.py:那些决定成败的数字

别小看这个配置文件,它控制着整个流程的“脾气”。里面几个关键参数,我挨个说清楚为什么这么设:

  • REQUEST_TIMEOUT = 15:不是越短越好。设太短(如5秒),网络抖动时大量请求被判定为失败,重试增多反而拖慢整体;设太长(如30秒),某个页面卡死会阻塞整个线程。15秒是经过2000次实测的平衡点——98.7%的有效页面能在12秒内返回,留3秒余量应对边缘情况。

  • MAX_RETRIES = 3:重试不是越多越好。第一次失败可能是网络瞬断,第二次可能是服务器忙,第三次还失败,大概率是目标页面结构已变或IP被限。此时再重试只是浪费资源,不如记下URL,人工检查。

  • CONCURRENCY = 5:并发数。本地测试用笔记本(i5-8250U, 8GB RAM),开10个线程,内存占用飙升到95%,系统卡顿;开3个,速度慢一半。5是个甜点——CPU占用稳定在60%~70%,内存占用<65%,吞吐量最优。如果你是台式机(16GB+),可以调到8,但务必在crawler/utils.py里加上threading.Semaphore控制全局并发,否则容易被平台封IP。

  • PRICE_OUTLIER_THRESHOLD = 5:价格异常值过滤阈值。不是固定值,而是“标准差倍数”。清洗时,先算出所有有效价格的均值μ和标准差σ,然后过滤掉|price - μ| > 5σ的数据。为什么是5?因为房价分布是右偏的(有少量天价豪宅拉高均值),用3σ会误删高端盘,用10σ又放过太多水分。5σ在我们测试的12个城市数据集上,异常值检出率82%,误删率仅0.3%。

3.2lianjia_crawler.py:如何绕过链家的Ajax“迷宫”

链家现在基本不用静态HTML了,房源列表走Ajax接口,参数藏在JavaScript里。很多人卡在这儿,以为要逆向JS。其实有更轻量的办法:抓包+参数复用。项目里已经完成了这一步,你只需要知道关键逻辑:

  • 首页URL(如https://sh.lianjia.com/ershoufang/)返回的HTML里,有一段<script>标签,包含类似window.__INITIAL_STATE__={"topList":...}的JSON。我们用正则r'window\.__INITIAL_STATE__ = (.*?);'提取这段JSON,里面就有cityIdregionId等关键参数。

  • 真正的房源列表接口是https://sh.lianjia.com/ershoufang/pg{page}/,但必须带headers里的X-Requested-With: XMLHttpRequest,否则返回404。这个Header在headers.json里已预置。

  • 分页参数不是简单的pg2pg3。链家会校验_src参数,它是当前页面URL的Base64编码。所以代码里有encode_src_url()函数,每次请求前动态生成。

实操心得:第一次运行时,建议先把CONCURRENCY设为1,打开config/settings.py里的DEBUG_MODE = True。这样爬虫会在data_analysis/raw/下生成debug_html/目录,存下每一页的原始HTML和Ajax响应,方便你对照检查XPath是否写对。等确认无误,再关掉DEBUG,调高并发。

3.3clean_price.py:一行代码,解决“万”“亿”“w”“元”的混乱

价格清洗是清洗层最复杂的模块。核心函数clean_price(raw_price: str) -> dict返回一个字典,包含value(清洗后数值)、unit(单位,如“元/㎡”)、confidence(置信度,0~1)。关键逻辑如下:

def clean_price(raw_price: str) -> dict: if not raw_price or pd.isna(raw_price): return {"value": None, "unit": "N/A", "confidence": 0.0} # 步骤1:移除所有空格和常见干扰字符 clean_str = re.sub(r'[\s\u3000\xa0]', '', str(raw_price)) # 步骤2:匹配货币符号和数量级单位 # 支持:¥120万/㎡、总价约320万元、8.5w、3.25亿(别墅) pattern = r'([¥$¥])?(\d+(?:\.\d+)?)\s*(万|亿|w|W|元|yuan)?(?:[/㎡]|/m²|/平方米)?' match = re.search(pattern, clean_str) if not match: # 尝试匹配无符号纯数字(如"1200000") num_match = re.search(r'(\d+(?:\.\d+)?)', clean_str) if num_match: value = float(num_match.group(1)) # 如果原始字符串含"万"但没被上面pattern捕获,可能是"120万"没空格 if '万' in clean_str or 'w' in clean_str.lower(): value *= 10000 elif '亿' in clean_str: value *= 100000000 return {"value": value, "unit": "元", "confidence": 0.7} else: return {"value": None, "unit": "unknown", "confidence": 0.2} # 步骤3:解析匹配结果 _, num_str, unit_str = match.groups() value = float(num_str) # 步骤4:单位转换(核心!) if unit_str in ['万', 'w', 'W']: value *= 10000 unit_out = "元" elif unit_str in ['亿']: value *= 100000000 unit_out = "元" elif unit_str in ['元', 'yuan']: unit_out = "元" else: unit_out = "元/㎡" if '/㎡' in clean_str or '/m²' in clean_str else "元" # 步骤5:置信度打分 confidence = 0.9 if '约' in clean_str or '左右' in clean_str: confidence = 0.6 if '总价' in clean_str or '单价' in clean_str: confidence *= 1.1 # 有明确标识,更可信 return {"value": value, "unit": unit_out, "confidence": min(confidence, 1.0)}

这段代码的精妙之处在于:它不追求100%覆盖所有奇葩格式(那会无限膨胀),而是聚焦于高频、高价值场景。测试数据显示,它能正确处理92.4%的原始价格字符串,剩下7.6%被标为低置信度,进入人工审核队列——这才是工程思维:用80%的代码解决90%的问题,把精力留给真正需要判断的10%。

3.4chart_generator.py:让图表“开口说话”的5个细节

可视化不是调API,而是设计信息传达。chart_generator.py里每个函数都藏着让图表更专业的细节:

  • 字体嵌入:中文图表最大的坑是字体缺失。代码里强制指定plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans'],并设置plt.rcParams['axes.unicode_minus'] = False解决负号显示为方块的问题。所有图表保存时用bbox_inches='tight',确保标题不被截断。

  • 坐标轴精度:价格、面积这类大数值,Y轴标签默认是1000000,看着累。我们在format_yaxis()函数里,自动转为100万320万,并用FuncFormatter实现。代码只有三行,但效果立竿见影。

  • 图例位置智能适配:散点图图例放右边会挤占绘图区。代码里用plt.legend(loc='upper left', bbox_to_anchor=(1, 1)),把图例锚定在右上角外部,再用plt.tight_layout(rect=[0, 0, 0.85, 1])给图例留出空间。

  • 颜色语义化:不是随便选色。热力图用'YlOrRd'(黄-橙-红),符合“温度越高越贵”的直觉;区域均价柱状图,前三位用深蓝、中蓝、浅蓝,体现梯度;户型饼图,用plt.cm.Set3色板,保证相邻颜色区分度>70%(经ColorBrewer验证)。

  • 导出DPI与尺寸:论文要求图表清晰,plt.savefig(..., dpi=300, bbox_inches='tight')是底线。但更重要的是尺寸——plt.figure(figsize=(10, 6))是通用尺寸,热力图用(12, 8),散点图用(8, 6),确保在A4纸打印时,关键信息不缩水。

注意:所有图表函数都返回fig, ax对象。这意味着你可以拿到ax后,继续调用ax.set_title("上海浦东新区房价热力图(2024Q2)")ax.text()添加自定义标注,无缝接入你的定制需求。

4. 实操过程与核心环节实现:从零开始,一步步跑通全流程

现在,我们把所有理论落地。假设你刚下载完压缩包,解压到D:\house_analysis,接下来怎么做?我按真实操作顺序,把每一步的命令、预期输出、常见卡点都写清楚。

4.1 环境准备:3分钟搞定,不装虚拟环境也能跑

项目对环境要求极低,Python 3.8+即可,不需要conda或docker。打开命令行(Windows用CMD或PowerShell,Mac/Linux用Terminal),依次执行:

# 1. 进入项目根目录 cd D:\house_analysis\WGmXUawRCExeTUpZSYbx-master-413f72e956072fefffd0cb494fe4c886feae2e71 # 2. 创建并激活虚拟环境(推荐,但非必须) python -m venv venv venv\Scripts\activate # Windows # venv/bin/activate # Mac/Linux # 3. 安装依赖(requirements.txt已锁定版本,避免兼容问题) pip install -r requirements.txt # 4. 验证安装(应看到pandas、requests、matplotlib等版本) pip list | findstr "pandas requests matplotlib seaborn"

requirements.txt里关键依赖版本:
-pandas==1.5.3(兼容旧版Excel引擎)
-requests==2.31.0(最新稳定版,修复SSL漏洞)
-matplotlib==3.7.1(支持中文渲染的成熟版本)
-seaborn==0.12.2(与Matplotlib 3.7.x完美兼容)

提示:如果你用的是公司电脑,pip install被禁,可以把requirements.txt里的包名复制出来,用pip download -d ./packages --no-deps下载.whl文件,再离线安装。项目里scripts/offline_install.py已写好离线安装脚本,只需把下载的包放./packages目录下运行即可。

4.2 配置采集目标:改3个地方,指定你要爬的城市和区域

打开config/settings.py,找到以下三处修改:

  1. CITY_CODE = "sh":城市代码。链家城市代码是拼音缩写:sh=上海,bj=北京,gz=广州,sz=深圳,hz=杭州。完整列表在config/city_codes.json里。

  2. DISTRICTS = ["pudong", "xuhui", "changning"]:行政区列表。同样用链家URL里的英文名:pudong=浦东,xuhui=徐汇,changning=长宁。不要写中文!可以在链家网页上点进某个区,看URL里/ershoufang/pudong/那段就是。

  3. MAX_PAGES_PER_DISTRICT = 5:每个区爬多少页。链家每页30条,5页=150条。新手建议从1开始,确认流程跑通再调高。注意:页数不是越多越好,链家对高频访问会限速,MAX_PAGES_PER_DISTRICT > 10时,建议把CONCURRENCY降到3。

改完保存。现在,你已经指定了“爬上海浦东、徐汇、长宁三个区,各5页”。

4.3 运行全流程:一条命令,见证数据从网页到图表的蜕变

回到命令行,确保在项目根目录,执行:

python scripts/run_analysis.py

你会看到类似这样的滚动日志:

[2024-06-15 14:22:03] INFO: 开始执行二手房数据分析全流程... [2024-06-15 14:22:03] INFO: 正在初始化链家爬虫(城市:sh,区域:pudong)... [2024-06-15 14:22:05] INFO: 已获取浦东新区第1页(30条房源)... [2024-06-15 14:22:08] INFO: 已获取浦东新区第2页(30条房源)... ... [2024-06-15 14:25:12] INFO: 采集完成!共获取原始数据:278条。 [2024-06-15 14:25:13] INFO: 开始数据清洗... [2024-06-15 14:25:15] INFO: 价格清洗完成,有效价格率:94.2% [2024-06-15 14:25:16] INFO: 面积清洗完成,标准化率:98.6% [2024-06-15 14:25:17] INFO: 指纹去重完成,去重率:12.3% [2024-06-15 14:25:18] INFO: 清洗完成!有效数据:244条,已导出至 data_analysis/cleaned/house_data_cleaned_20240615.csv [2024-06-15 14:25:19] INFO: 开始生成可视化图表... [2024-06-15 14:25:22] INFO: 已生成:price_heatmap_sh_20240615.png [2024-06-15 14:25:24] INFO: 已生成:district_avg_bar_sh_20240615.png ... [2024-06-15 14:26:01] INFO: 图表生成完成!共12张,已存至 data_analysis/figures/ [2024-06-15 14:26:01] INFO: 全流程执行完毕!耗时:3分58秒。

关键观察点
- 日志里有精确到秒的时间戳,方便你定位哪个环节耗时最长。
- “有效价格率”“标准化率”这些指标,是清洗质量的直接反馈。如果低于90%,说明原始数据质量差,需要检查DISTRICTS是否选了维护不勤的冷门区。
- 最后一行明确告诉你总耗时。我的测试机(i7-10750H, 16GB)跑上海3个区各5页,平均耗时4分12秒。

4.4 查看成果:3个目录,10秒定位你需要的一切

运行结束后,打开data_analysis/目录,你会看到三个子目录:

  • raw/:原始采集数据。打开house_data_raw_20240615.csv,用Excel或VS Code查看。你会看到crawl_timestampsource_urltitle_rawprice_raw等列,全是未加工的“毛坯数据”。这是你溯源的起点。

  • cleaned/:清洗后数据。打开house_data_cleaned_20240615.csv,重点看这几列:

  • price_cleaned:统一为“元”的数值,可直接计算。
  • area_cleaned:统一为“㎡”的数值。
  • district_cleaned:行政区标准化为“浦东新区”“徐汇区”。
  • clean_log:每一行的清洗步骤,白纸黑字。

  • figures/:所有图表。打开price_heatmap_sh_20240615.png,你应该看到一张上海地图,上面覆盖着由浅黄到深红的热力区块,图例显示“¥50,000 ~ ¥120,000/㎡”。这就是你的第一个成果——无需PS,无需配色,一键生成。

实操心得:第一次运行,建议在scripts/run_analysis.py里找到if __name__ == "__main__":下面的代码,把run_full_pipeline()换成run_crawler_only(),先单独跑通采集,确认能拿到数据,再跑清洗,最后跑可视化。分段验证,比一次全跑更容易定位问题。

5. 常见问题与排查技巧实录:那些文档里不会写的“血泪经验”

再完美的流程,也会遇到意外。我把过去两年帮学生和学员解决的高频问题,整理成这张表。每一个问题,都来自真实现场,每一个答案,都是试错后的最优解。

问题现象可能原因排查步骤解决方案我的血泪经验
爬虫卡在“正在获取第1页”不动,10分钟后报timeout目标城市/区域URL失效,或链家临时调整了首页结构1. 手动打开浏览器,访问https://sh.lianjia.com/ershoufang/pudong/
2. 查看网页源码,搜索window.__INITIAL_STATE__是否存在
3. 检查config/city_codes.jsonsh对应的base_url是否仍是https://sh.lianjia.com
修改config/settings.py里的CITY_CODE为其他城市(如hz),或更新base_url。项目里crawler/utils.pyget_city_base_url()函数已预留扩展点。别死磕!链家每月都有小改版。我维护了一个“城市健康度”表,每周自动检测各城市首页是否返回200。上海、北京、深圳常年健康;成都、武汉偶尔抽风。遇到卡住,先切到健康城市跑通流程,再回头研究问题城市。
清洗后price_cleaned全是NaN,clean_log显示"step1: no match"原始价格字段格式剧变(如链家突然把“¥120万/㎡”改成“1200000元/㎡”)1. 打开data_analysis/raw/下的最新CSV,筛选几行price_raw非空的记录
2. 复制一条price_raw值(如"1200000元/㎡"
3. 在Python交互环境里运行clean_price("1200000元/㎡"),看返回什么
打开data_cleaning/clean_price.py,找到正则pattern,在末尾加上\s*(元|yuan)?,并调整unit_str判断逻辑。改完重新运行清洗。正则不是一劳永逸。我在clean_price.py开头加了注释:“此正则适配链家2024Q2格式。若失效,请检查data_analysis/raw/样本,更新pattern”。把版本意识刻进代码。
热力图一片空白,或全是同一个颜色插值网格分辨率太低,或价格数据量太少(<50条)1. 打开data_analysis/cleaned/下的CSV,用Excel的COUNTA()统计price_cleaned非空行数
2. 检查visualization/chart_generator.py里的GRID_SIZE = 200是否被误改为50
如果数据量<50,改用seaborn.scatterplot()画散点图替代热力图;如果数据量充足,检查GRID_SIZE是否仍为200,并确认geopandas加载的GeoJSON是最新版(项目里data/目录下有shanghai_districts.geojson)。热力图不是万能的。我最初坚持用它,直到一个学生用50条数据硬生成,图上全是噪点。后来加了自动判断:if len(df) < 100: use_scatterplot() else: use_heatmap()
图表中文显示为方块,或负号是□系统缺少中文字体,或Matplotlib配置未生效1. 运行python -c "import matplotlib; print(matplotlib.matplotlib_fname())",找到matplotlibrc文件
2. 用文本编辑器打开,搜索font.sans-serif,确认是否包含SimHei
visualization/style_config.py里,set_chinese_font()函数已强制指定字体路径。如果还不行,在代码开头加import matplotlib; matplotlib.use('Agg'),并确保系统已安装微软雅黑或思源黑体。字体问题90%是环境问题。我干脆在scripts/run_analysis.py开头加了字体检测:if not os.path.exists("C:/Windows/Fonts/msyh.ttc"): print("警告:未检测到微软雅黑,将使用备用字体")
导出的Excel打开提示“文件损坏”,但CSV正常openpyxl版本冲突,或Excel文件被其他程序占用1. 运行pip show openpyxl,确认版本是3.1.2(项目锁定版本)
2. 检查data_analysis/cleaned/目录下是否有同名文件正被Excel程序打开
关闭所有Excel进程,删除data_analysis/cleaned/下所有.xlsx文件,重新运行。如果仍失败,临时把config/settings.py里的EXPORT_TO_EXCEL = False设为False,只导出CSV。Excel导出是锦上添花,不是雪中送炭。我测试过,CSV在任何环境都能100%打开,而Excel导出在某些国产办公软件上有兼容问题。所以项目默认EXPORT_TO_EXCEL = True,但加了try...except,失败时自动降级为CSV,并在日志里提醒。

5.1 一个真实案例:从“爬不到数据”到“论文第三章”的48小时

去年12月,一个叫李明的学生找我,他的毕设题目是《基于网络爬虫的上海市二手房价格影响因素分析》,但卡在第一步:用网上教程的爬虫,爬链家,两天没拿到一条有效数据。我让他把项目包发给我,打开data_analysis/raw/,发现CSV里price_raw列全是空的。按上表排查:

  1. 手动访问https://sh.lianjia.com/ershoufang/pudong/能打开,但源码里搜window.__INITIAL_STATE__,找不到。原来链家把初始化数据挪到了另一个Ajax接口https://sh.lianjia.com/ershoufang/chengjiao/ajax里。

  2. 更新爬虫:我让他打开crawler/lianjia_crawler.py,找到fetch_initial_state()函数,把原来的正则提取,改成用requests.get()调用那个新接口,并把返回的JSON里的cityIdregionId提取出来。改了12行代码。

  3. 测试清洗:运行后,price_raw有了,但全是“1200000”这种格式。我让他在clean_price.py的正则里,把(\d+(?:\.\d+)?)后面加上\s*(元|yuan)?,并把unit_str的判断逻辑简化为if '元' in clean_str: unit_out = "元"

48小时后,他微信发来截图:district_avg_bar_sh_20231215.png,标题是“上海市各行政区二手房均价对比(2023年Q4)”,柱状图清晰显示浦东最高(¥85,200),崇明最低(¥28,600),图例标注了数据量。他告诉我,导师看了说:“这个图,第三章可以直接用了。”

这就是这套东西的价值:它不承诺教会你所有,但它承诺,当你遇到问题,解决方案就藏在你打开的那个文件里,就在你看到的那行日志旁。

6. 扩展与定制:让这套工具,真正长在你的项目里

这套流程不是终点,而是起点。它的模块化设计,就是为了让你轻松嫁接自己的需求。下面三个方向,是我最常被问到的,也是实战中提升价值最快的。

6.1 增加新数据源:不只是链家,还能接贝壳、安居客

项目目前只内置了链家爬虫,但架构是开放的。要加贝壳(ke.com),只需三步:

  1. 新建爬虫类:在crawler/目录下创建ke_crawler.py,继承base_crawler.BaseCrawler,实现crawl_page()方法。贝壳的页面结构更简单,主要是静态HTML,XPath路径是//div[@class='property-content']//span[@class='price-value']

  2. 注册到入口:打开scripts/run_analysis.py,找到run_crawler_only()函数,在if city_code == "sh":分支下,加上elif source == "ke": crawler = KeCrawler(city_code, districts)

  3. 配置开关:在config/settings.py里加一个DATA_SOURCE = "lianjia"(可选"ke""anjuke"),并在run_analysis.py里读取它,决定实例化哪个爬虫。

提示:贝壳的反反爬更松,CONCURRENCY可以提到8,REQUEST_TIMEOUT降到10秒。但要注意,贝壳的“总价”字段常是“面议”,清洗时需在clean_price.py里加一条规则:if "面议" in clean_str: return {"value": None, "unit": "negotiable", "confidence": 0.1}

6.2 深化分析维度:从“均价”到“价格洼地”识别

项目自带的图表是描述性分析,但你可以轻松升级为诊断性分析。比如,识别“价格洼地”(同区域、同户型、同面积段,价格显著低于均值的房源),只需在data_cleaning/clean_pipeline.py的末尾,加一段逻辑:

def identify_price_gaps(df: pd.DataFrame) -> pd.DataFrame: """识别价格洼地:同区域、同户型、同面积段(±5㎡)内,价格低于均值2个标准差的房源""" # 先按区域、户型分组 grouped = df.groupby(['district_cleaned', 'house_type_cleaned']) def mark_gap(group): if len(group) < 10: # 样本太少,不计算 group['is_price_gap'] = False return group mean_price = group['price_cleaned'].mean() std_price = group['price_cleaned'].std() # 计算面积段中心(取整到5的倍数) group['area_bin'] = (group['area_cleaned'] // 5) * 5 # 按面积段再分组,标记洼地 area_grouped = group.groupby('area_bin') for _, area_subgroup in area_grouped: if len(area_subgroup) >= 5: area_mean = area_subgroup['price_cleaned'].mean() area_std = area_subgroup['price_cleaned'].std() gap_mask = area_subgroup['price_cleaned'] < (area_mean - 2 * area_std) group.loc[area_subgroup.index, 'is_price_gap'] = gap_mask return group return grouped.apply(mark_gap).reset_index(drop=True) # 在clean_pipeline.py的主函数里调用 df = identify_price_gaps(df)

运行后,cleaned数据里会多一列is_price_gap(True/False),你就可以用seaborn.scatterplot(x='area_cleaned', y='price_cleaned', hue='is_price_gap')画出洼地分布图。这比单纯看均价,更能指导买房决策。

6.3 集成到Web服务:用Flask搭个简易房价查询站

如果你学过一点Web开发,可以把这套分析变成一个网页。在项目根目录新建web_app/,用Flask:

# web_app/app.py from flask import Flask, render_template, request import pandas as pd from data_cleaning.clean_pipeline import run_cleaning_pipeline from visualization.chart_generator import generate_district_bar_chart app = Flask(__name__) @app.route('/') def index(): return render_template('index.html') @app.route('/analyze', methods=['POST']) def analyze(): city = request.form['city'] district = request.form['district'] # 调用爬虫和清洗(此处简化,实际需异步或缓存) raw_df = crawl_for_district(city, district) # 你的爬虫函数 cleaned_df = run_cleaning_pipeline(raw_df) # 生成图表 fig_path = generate_district_bar_chart(cleaned_df, district) return render_template('result.html', chart_path=fig_path, count=len(cleaned_df)) if __name__ == '__main__': app.run(debug=True)

前端templates/index.html放一个表单,输入城市和区域,提交后调用后端。这样,你的毕设就从“本地脚本”升级为“Web应用”,答辩时演示起来,效果翻倍。

最后分享一个小技巧:所有图表生成函数,都支持save_fig=False参数。这意味着你可以不保存文件,而是直接返回fig对象,用io.BytesIO()转成字节流,通过Flask的send_file()直接返回给前端。这样就不用管文件路径和权限问题了。

我在实际使用中发现,这套工具的生命力,不在于它有多“全”,而在于它有多“顺”。当你想加一个字段、换一个网站、改一种图表,改动都在一个文件里,改完立刻见效。它不强迫你理解整个生态,它只给你一把趁手的刀,让你专注切自己的菜。

本文还有配套的精品资源,点击获取

简介:一套开箱即用的二手房数据自动化分析工具,基于Python实现从主流房产网站抓取房源标题、售价、面积、户型、楼层、区域等结构化字段;内置请求头轮换、随机延时、基础反反爬适配逻辑,兼顾稳定性与合规性;数据自动去重、空值处理、价格单位统一、面积格式标准化;支持一键导出清洗后数据为CSV或Excel文件;集成Matplotlib和Seaborn绘制10类实用图表:城市房价热力图、各行政区均价对比柱状图、户型分布饼图、价格与面积散点关系图、房龄分布直方图、挂牌天数趋势折线图等;项目包含完整源码、清晰README配置说明、运行日志示例及20余张真实截图(涵盖控制台执行过程、DataFrame表格预览、图表渲染效果),适用于本科毕业设计、数据分析入门练习或教学演示。


本文还有配套的精品资源,点击获取

http://www.jsqmd.com/news/910559/

相关文章:

  • 保姆级避坑指南:在CentOS 8.5上用JDK 17搞定Hadoop 3.3.5 + Spark 3.3.2集群(附虚拟机克隆技巧)
  • 在智能客服场景中利用Taotoken多模型能力优化对话流程与成本
  • 三步解锁手机音频无线传输:sndcpy让电脑成为你的手机音响
  • 2026年6月亲历深度评测现场记录|百达翡丽官方售后网点2026年实地验证报告(含迁址与新开) - 百达翡丽服务中心
  • Go语言WASM:WebAssembly支持
  • 终极跨平台资源下载神器:3分钟快速上手全攻略
  • Orange Pi上RetroPie前端优化:ES-X增强模块部署与配置指南
  • 绵阳游仙区一环路东段149号附近,宠物生病去哪看?本地人常去的3家口碑医院 - 品牌日记
  • 雷电冲击发生器,现场用着心里踏实
  • 智造未来:四大品牌如何赋能制造业数字化转型?
  • 高校生最爱的AI论文工具是哪款?
  • 告别Cloud Sync?试试用Rclone在群晖上挂载阿里云盘,实现更灵活的同步与备份
  • 2026年国内五大辣椒油品牌推荐!2026最新排名出炉,椒上飞实力领先 - 十大品牌榜
  • pom-xml-flattened 这是什么文件?可以删除吗?
  • 如何快速掌握Raw Accel鼠标加速:面向游戏玩家的7种曲线终极指南
  • 新手避坑指南:用Jellyfish和GenomeScope2.0搞定基因组Survey(附R语言绘图代码)
  • 基于Arduino与ESP8266的水质监测系统:传感器信号稳定与校准实战
  • 盐城GEO优化公司哪家靠谱?四大维度实测避坑指南(2026年5月最新) - 商业新知
  • AI统一分析:打破数据孤岛,从暗数据到智能决策的实战指南
  • 深度解析:AI智能体的“记忆”(Memory)与“知识库”(RAG)如何协同进化?
  • 终极指南:如何使用stl-thumb快速预览3D打印文件
  • 别再手动敲字了!用Python的EasyOCR库,5分钟搞定图片文字批量提取(附中文识别实战代码)
  • 上海职场西装定制哪家好?2026年商务精英高口碑店铺推荐 - 西装爱好者
  • 2026国产在线浊度计品牌综合实力测评:技术参数与真实案例深度分析 - 液体流量液位品牌推荐
  • 谷歌投资回报周期解析:从业务拆解到实战策略
  • 走访京城字画回收市场,听听藏家口中的靠谱公司 - 品牌排行榜
  • Arduino电容触摸调光小夜灯:Visuino可视化编程实战
  • 2026年如何选低价苹果二手手机平台?实测推荐更安心 - 速递信息
  • AAnthropic 团队都改用 HTML 写文档了!HTML Anything这个开源项目让 AI gent 一键生成 75 种精美排版——微信/X/知乎一键导出
  • 2026 高性价比离子色谱仪 热裂解仪厂家推荐:广州金谷科学仪器有限公司 - 新闻快传