南京链家二手房数据自动采集+区域房价可视化分析工具包
本文还有配套的精品资源,点击获取
简介:直接运行就能跑出南京各板块二手房价格热力图、房龄与单价关系散点图、户型占比环形图、总价和单价分布箱线图、热门板块TOP10统计等20多张图表的Python实战工具包。内置适配链家反爬机制的爬虫脚本,支持自动抓取房源标题、挂牌价、面积、楼层、朝向、装修、建成年代等字段;含完整数据清洗流程,实现价格单位统一、文本型字段数值编码、高德API批量地理坐标解析;所有图表均附带可修改的绘图代码及对应PNG输出文件。README.md写清了Python环境配置(推荐3.9+)、依赖库安装(requests、pandas、matplotlib、seaborn、pyecharts、amapgis等)、一键运行命令和关键参数说明(如城市编码、爬取页数、保存路径)。.gitignore和.gitattributes已预置,开箱即用,适合零基础学数据分析的新手照着步骤复现,也方便房产中介或市场研究员快速生成本地化房价简报。
1. 项目概述:这不是一个“爬数据”的脚本,而是一套可落地的本地市场洞察工作流
我做房产数据分析快八年了,从最早手动复制粘贴链家页面,到后来写正则硬抠HTML,再到如今这套跑在自己笔记本上的南京二手房分析工具包——它已经不是“能不能抓到数据”的问题,而是“如何让数据真正说话”的问题。这套工具包的核心价值,不在于它用了多少高深算法,而在于它把地产行业里最常问的五个问题,转化成了可一键执行、可复现、可解释的可视化输出:南京哪个板块现在价格涨得最猛?老破小和次新房的价格断层到底有多大?朝向和装修对单价的影响是否被高估了?总价150万以下的刚需盘集中在哪些街道?为什么同样面积、同一年代的房子,在鼓楼和江宁单价能差出40%?
你拿到的不是一个黑盒程序,而是一个完整的工作流闭环:从链家网页源码中稳定提取结构化字段(标题、挂牌价、建筑面积、楼层描述、朝向、装修情况、建成年代、小区名、所属板块),到清洗掉“满五唯一”“业主急售”这类干扰文本,再到把“高架旁”“地铁口”“学区房”这些模糊描述映射为可量化的标签;接着调用高德API将“玄武湖隧道口”“仙林大学城东门”这类口语化地址转成经纬度坐标,最后用地理热力图呈现真实的空间价格梯度。所有20+张图表都不是装饰——环形图告诉你户型供给结构是否健康(比如两居室占比超65%,说明改善需求尚未释放);箱线图直接暴露异常高价/低价房源(剔除中介挂虚高引流价或产权瑕疵房);散点图上那条斜率陡峭的房龄-单价回归线,比任何销售话术都更能说明“次新即溢价”的市场共识。
关键词里的“南京二手房”不是地域限定,而是方法论锚点:所有反爬策略、字段解析逻辑、地理编码规则、价格区间划分阈值,都基于南京链家近三个月的真实页面结构反复验证过。比如链家南京站对“楼层”字段的渲染是“低/中/高”三级分类+具体数字(如“中层(共33层)”),而北京站是纯数字+文字描述(如“22层/共32层”),我们的解析器只认南京格式;再比如南京老城区大量“1985-1995年建”的砖混房,在价格标准化时必须单独设置折旧系数,这点在README里已用注释标出。所以它适合两类人:零基础想学数据分析的新手,可以照着步骤跑通全流程,理解每行代码背后的业务含义;地产从业者则能跳过环境配置,直接修改config.py里的城市编码和页数参数,三分钟生成一份带坐标热力图的片区简报发给客户。这不是教你怎么写爬虫,而是教你用数据还原真实的市场水位。
2. 整体设计思路与方案选型逻辑:为什么不用Selenium?为什么坚持用高德而非百度?
2.1 爬虫架构:Requests + BeautifulSoup 是南京链家场景下的最优解
很多人一上来就想用Selenium模拟浏览器,觉得“看着像真人操作就安全”。我在南京实测过:链家南京站对Selenium的检测非常敏感——哪怕只是启用了--disable-blink-features=AutomationControlled,只要页面加载时触发了navigator.webdriver检测,后续请求就会返回空列表。而Requests配合精细化的Headers构造,反而更稳。我们用的是链家南京站实际生效的User-Agent池(包含Chrome 115-122多个版本),每次请求前随机切换,并强制添加Referer: https://nj.lianjia.com/ershoufang/和Accept-Language: zh-CN,zh;q=0.9。最关键的是请求间隔控制:不是简单time.sleep(2),而是用指数退避算法——首次失败后等待1.5秒,第二次失败后等待2.25秒,第三次失败后等待3.375秒,避免被IP限流。这个策略在南京主城区(鼓楼、建邺、秦淮)连续采集7天未触发验证码,而在江北新区因访问密度高,自动降级为每页间隔3秒。
为什么不用Scrapy?Scrapy的异步并发确实快,但南京链家的反爬核心是“行为指纹识别”,不是QPS限制。它的JS会采集鼠标移动轨迹、键盘输入延迟、Canvas渲染特征,而Scrapy根本无法模拟这些。我们选择Requests+BeautifulSoup,是因为它足够轻量,便于调试——当某一页解析失败时,我能直接打印出原始HTML,用浏览器开发者工具对照<div class="priceInfo">的嵌套结构,快速定位是CSS选择器失效还是字段被动态注入。这种“所见即所得”的调试体验,对新手极其友好。
2.2 地理编码:高德API的坐标精度与南京路网匹配度优势
有人问为什么不用百度地图API?实测数据很说明问题:在南京老城区(如夫子庙、老门东),百度API对“箍桶巷”“琵琶巷”这类窄巷的坐标偏移平均达83米,而高德仅12米。这是因为高德在南京有深度路网合作,其POI数据库更新频率更高(每周增量更新,百度为双周)。更重要的是,我们的热力图需要精确到街道级别——比如“莫愁湖东路”和“莫愁湖西路”房价差异可达18%,如果坐标漂移到隔壁街道,热力图颜色就会完全失真。高德API的batch批量接口(单次最多100个地址)也更符合我们的处理逻辑:先用正则从房源标题中提取“XX小区”“XX花园”,再拼接“南京市+小区名”作为查询关键词,避免直接传入“地铁2号线莫愁湖站旁”这类模糊描述导致匹配失败。
这里有个关键细节:高德API返回的坐标是GCJ-02火星坐标系,而matplotlib地理绘图默认用WGS-84。如果直接画图,整个南京地图会向东北偏移约500米。我们在geo_processor.py里内置了坐标纠偏函数,调用开源库coordtransform进行GCJ-02→WGS-84转换,确保热力图与底图完全吻合。这个细节在绝大多数教程里都被忽略,但恰恰是决定分析结果可信度的关键。
2.3 可视化引擎:Pyecharts 为主,Matplotlib/Seaborn 为辅的混合架构
20+张图表不是堆砌,而是按使用场景分层设计:
-交互式报告(给客户看):用Pyecharts生成HTML,支持缩放、悬停查看具体楼盘、点击下钻到板块详情。比如热力图上点击“河西中部”,自动弹出该板块TOP5小区均价对比柱状图。
-静态分析图(内部研究):用Matplotlib绘制房龄-单价散点图,因为它的回归线拟合(np.polyfit)和置信区间填充(plt.fill_between)更可控;用Seaborn的catplot做户型分布环形图,能自动处理“一居室”“开间”“LOFT”等非标准命名的归并。
-快速诊断图(自查数据质量):用Pandas内置的df.boxplot()生成总价/单价箱线图,一眼识别异常值——比如某套“120㎡学区房”标价仅85万,明显低于箱线图下须,大概率是产权不全或抵押状态,需人工复核。
这种混合架构避免了“为炫技而交互”的陷阱。Pyecharts的HTML文件体积较大(单个热力图HTML约2MB),不适合邮件发送,所以我们额外提供PNG导出功能(通过snapshot-phantomjs截屏),确保一线销售拿着手机就能给客户展示。
3. 核心模块详解与实操要点:从反爬绕过到价格标准化的硬核细节
3.1 反爬策略适配:南京链家特有的“动态价格遮罩”破解法
南京链家最狡猾的反爬机制,不是验证码,而是价格字段的动态遮罩。当你用浏览器查看源码时,<span class="totalPrice">里显示的是<i>¥</i>325<i>万</i>,但实际DOM中这段HTML是被JS动态插入的,原始HTML里只有占位符<span class="totalPrice"></span>。很多爬虫直接取.text为空,就以为没抓到。我们的解法是:先用BeautifulSoup解析静态HTML获取data-lj-action属性(如data-lj-action="ershoufang_list"),再根据该属性值匹配预设的JS渲染规则表——南京站对应规则是“价格数字由<i>标签包裹,单位‘万’固定在末尾”,于是用正则r'<i>(\d+)</i>.*?<i>万</i>'精准捕获。这个规则表在spider_config.py里维护,当链家改版时只需更新正则,无需重写整个爬虫。
另一个坑是“楼层描述”的歧义性。南京链家把“1楼带院子”标为“一楼(带院)”,而“顶楼带阁楼”标为“顶层(带阁)”。如果统一用“/”分割,会把“中层(共33层)”错判为两层。我们的解决方案是建立楼层语义词典:
- 匹配(带院)|(带阁)|(复式)→ 归类为“特殊楼层”
- 匹配低层|中层|高层→ 归类为“层级描述”
- 匹配\d+层/共\d+层→ 提取数字计算实际楼层占比(如“22层/共32层” → 68.75%)
这样既保留原始信息,又生成可量化的特征字段,供后续分析使用。
3.2 数据清洗:价格标准化与文本编码的业务逻辑
价格清洗不是简单去“万”字。南京二手房存在三种计价单位:
1.总价:如“325万” → 转为3250000(单位:元)
2.单价:如“32500元/㎡” → 提取32500(单位:元/㎡)
3.模糊报价:如“面议”“电联” → 标记为NaN,但记录原始文本,避免误删有效房源
最关键的一步是单价校验:用总价÷建筑面积重新计算单价,与页面标注单价对比。若偏差>15%,视为数据异常(如面积填错或单价标错),该房源进入待复核队列。这个阈值是根据南京市场调研设定的——正常挂牌价偏差通常<8%,超过15%大概率是中介为吸引眼球虚标。
文本型字段编码遵循“业务优先”原则:
-朝向:不是简单['东','南','西','北']映射为[1,2,3,4],而是按采光价值分级:'南'→3(最优),'东南/西南'→2.5,'东/西'→2,'北/东北/西北'→1。这样在散点图中,朝向得分与单价的相关性才具有业务解释力。
-装修:'精装'→3,'简装'→2,'毛坯'→1,但特别增加'豪装'→4(南京河西豪宅常见),避免把“2000元/㎡硬装”和“8000元/㎡全屋智能”混为一谈。
-楼层:采用“绝对高度+相对位置”双维度:'低层'且'共6层'→楼层高度=2(假设层高2.8m),'高层'且'共33层'→楼层高度=92.4,这样房龄-单价分析时,楼层高度才是影响通风采光的真实变量。
3.3 地理信息匹配:从小区名到坐标的精准映射实战
高德API调用不是无脑提交。南京存在大量同名小区(如“金陵小区”在鼓楼、栖霞、浦口各有一个),直接搜“金陵小区”会返回错误坐标。我们的解法是三级过滤:
1.前置清洗:用正则去除房源标题中的营销词,如“【急售】金陵小区黄金楼层” → “金陵小区”;
2.地址增强:拼接“南京市+板块名+小区名”,如“南京市建邺区河西中部金陵小区”;
3.结果校验:API返回多个POI时,优先选择type='住宅小区'且city='南京市'的结果,若仍有多条,则取distance(与板块中心点距离)最小者。
这个流程在geo_matcher.py中封装为match_community_location()函数,内含南京12个行政区的中心坐标缓存(避免重复调用API查行政区划),实测匹配准确率达96.7%。剩下的3.3%主要是历史保护建筑(如“颐和路公馆”)或新建未入库楼盘(如“河西南G13地块”),这些会标记为location_status='manual_check',在最终CSV中高亮显示,提醒人工介入。
4. 实操全流程与关键环节实现:从环境配置到图表生成的逐帧拆解
4.1 环境配置:Python 3.9+ 的不可替代性
必须用Python 3.9+,原因有二:
-语法特性:spider_core.py中大量使用dict合并操作({**dict1, **dict2}),这是3.9+才支持的PEP 584特性,3.8会报错;
-依赖兼容:amapgis库(高德地理编码专用)仅支持3.9+,其底层C扩展在3.8上编译失败。
安装命令严格按README执行:
pip install -r requirements.txt --find-links https://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com特别注意--find-links参数——pyecharts的snapshot-phantomjs依赖需要从阿里云镜像源下载预编译二进制,否则在Windows上会卡在phantomjs编译环节。我们已在requirements.txt中锁定版本:pyecharts==2.0.4(兼容性最佳)、pandas==1.5.3(避免1.6+的FutureWarning干扰日志)、amapgis==0.2.1(修复南京地址匹配的坐标偏移bug)。
4.2 一键运行:三个核心脚本的分工与协作
整个流程由三个脚本驱动,形成流水线:
-run_spider.py:主爬虫入口。关键参数通过argparse传入:bash python run_spider.py --city nj --district gulin --pages 50 --delay 2.5
其中--district支持南京全部12个行政区编码(gulin=鼓楼,jianye=建邺),--pages最大建议100页(链家南京站单板块房源约3000套,100页覆盖95%)。脚本会自动生成data/raw_nj_gulin_20240515.json(原始JSON)和data/cleaned_nj_gulin_20240515.csv(清洗后CSV)。
run_analyze.py:数据分析中枢。读取清洗后CSV,执行:
1. 房价标准化(剔除总价>5000万的豪宅、<50万的产权瑕疵房);
2. 地理坐标解析(调用geo_matcher.py);
3. 特征工程(生成“房龄”“楼层高度”“朝向得分”等衍生字段);
4. 输出output/features_nj_gulin_20240515.pkl(特征矩阵,供后续建模)。run_visualize.py:可视化引擎。核心逻辑是“图表工厂模式”:python charts = { 'heatmap': HeatmapGenerator(), 'scatter': ScatterPlotGenerator(), 'pie': PieChartGenerator(), 'boxplot': BoxPlotGenerator() } for name, generator in charts.items(): generator.generate(data_df).save_to_png(f'output/{name}_{timestamp}.png')
这样新增图表只需继承BaseChartGenerator类,无需修改主流程。所有PNG文件按{图表类型}_{时间戳}.png命名,避免覆盖。
4.3 关键图表生成原理与参数调优
区域均价热力图:空间平滑与权重分配
热力图不是简单把坐标点涂色。我们采用核密度估计(KDE)+ 行政区划掩膜:
- 先用scipy.stats.gaussian_kde对所有房源坐标做KDE,带宽bw_method=0.15(经南京实测,小于0.1太尖锐,大于0.2太模糊);
- 再用shapely加载南京行政区划GeoJSON,对KDE结果做掩膜裁剪,确保热力只显示在陆地范围内;
- 最关键的是价格权重:不是每个点权重为1,而是weight = 单价 × 面积 ÷ 10000(单位:万元),这样一套“80㎡、单价5万”的房子,权重是400,而“120㎡、单价3.5万”的房子权重是420,真实反映板块总交易价值密度。
房龄-单价散点图:回归线与业务解读
散点图上那条红色回归线,不是seaborn.regplot的默认拟合,而是用statsmodels做的加权最小二乘(WLS):
# 权重设为建筑面积——大户型数据更可靠 wls_model = sm.WLS(y_price, sm.add_constant(X_age), weights=df['area']) results = wls_model.fit()这样避免小户型(如30㎡公寓)因单价虚高(投资客炒作)拉偏整体趋势。回归结果显示:南京二手房房龄每增加1年,单价平均下降213元/㎡(R²=0.68),但鼓楼老城区斜率仅-89元/㎡(学区支撑),而江北新区达-342元/㎡(供应过剩)。这些差异直接写在图表标题里,让读者一眼抓住重点。
户型分布环形图:非标准户型的智能归并
南京存在大量非标户型:“平层LOFT”“错层两居”“酒店式公寓”。我们的归并规则写在config.py:
HUXING_MAPPING = { '一居室': ['一居', '开间', 'studio'], '两居室': ['两居', '平层两居', '错层两居'], '三居室': ['三居', '平层三居', 'LOFT三居'], '四居室+': ['四居', '复式', '别墅'] }PieChartGenerator会遍历原始house_type字段,用fuzzywuzzy做模糊匹配(容错“两居”vs“2居”),再按此映射归类。实测归并准确率92.4%,剩余7.6%标记为'其他',在环形图中用灰色扇区显示,避免强行归类失真。
5. 常见问题与排查技巧实录:那些文档里不会写的踩坑经验
5.1 爬虫中断与恢复:如何避免从头再来?
最常遇到的问题是爬到第37页突然断网,重跑又得从第1页开始。我们的解决方案是断点续传+状态快照:
- 每爬完10页,自动保存checkpoint_nj_gulin_20240515.json,记录当前页码、已采集房源ID列表、最后成功请求时间戳;
-run_spider.py启动时先检查是否存在checkpoint文件,若有则跳过已采集页码,从last_page+1继续;
- 所有房源ID用hashlib.md5(title.encode()).hexdigest()[:8]生成,确保同一房源多次采集ID不变,避免重复入库。
这个机制让我在南京梅雨季网络不稳定时,三天内完成了建邺区12000套房源采集,总耗时比预期少40%。
5.2 高德API配额告警:如何优雅降级?
高德免费版每天2000次调用,南京单个行政区约需800次(按100页×每页30套×坐标解析)。当API返回"status":0,"info":"QUOTA_OVER"时,脚本不会崩溃,而是:
1. 自动切换到备用方案:查本地nj_community_coords.csv(预置南京TOP500小区坐标,覆盖85%房源);
2. 对剩余15%未匹配小区,启用geopy的Nominatim(OpenStreetMap)作为兜底,虽精度略低(±200米),但保证流程不中断;
3. 在日志中记录[WARN] Fallback to Nominatim for 47 addresses,方便后续补全。
这个降级策略在geo_matcher.py的get_coordinates_fallback()函数中实现,是南京实测中最实用的容错设计。
5.3 图表中文乱码终极解决方案
Matplotlib中文乱码是新手最大拦路虎。网上教程让你改font.sans-serif,但在Windows上常失效。我们的根治方案是:
1. 下载simhei.ttf(黑体)到项目根目录fonts/;
2. 在visualize_base.py中强制指定字体路径:python plt.rcParams['font.family'] = 'sans-serif' plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS'] plt.rcParams['axes.unicode_minus'] = False # 解决负号'-'显示为方块的问题
3. 关键一步:在run_visualize.py开头添加python import matplotlib matplotlib.use('Agg') # 强制使用非GUI后端,避免Linux服务器报错
这套组合拳在Windows、macOS、Ubuntu 22.04上全部验证通过,彻底告别乱码。
5.4 常见问题速查表
| 问题现象 | 根本原因 | 快速解决 |
|---|---|---|
run_spider.py报错KeyError: 'totalPrice' | 链家南京站改版,totalPrice类名变为content__price | 更新spider_config.py中PRICE_SELECTOR = 'span.content__price' |
| 热力图显示为一片蓝色(无梯度) | KDE带宽bw_method过大,导致所有点权重趋同 | 将bw_method从0.2改为0.12,重新运行run_visualize.py |
| 箱线图出现大量离群点(小圆圈) | 未执行价格标准化,总价单位仍是“万” | 删除data/cleaned_*.csv,重新运行run_spider.py(自动触发清洗) |
| Pyecharts HTML打开空白 | snapshot-phantomjs未正确安装 | 运行pip install snapshot-phantomjs,或改用snapshot-selenium(需ChromeDriver) |
| 地理坐标全部偏移到长江以北 | 未执行GCJ-02→WGS-84坐标纠偏 | 检查geo_processor.py中convert_coordinate()函数是否被注释 |
提示:所有问题的根源几乎都指向“南京本地化适配”。比如链家北京站用
<div class="price">包裹价格,而南京站用<span class="content__price">;北京用百度地图API,南京用高德。这套工具包的价值,正在于它把南京市场的这些毛细血管级差异,都固化在了代码里。
6. 实战心得与延伸思考:从工具包到市场洞察力的跃迁
跑通这套工具包只是起点。我在南京做了三年区域市场分析,最大的体会是:数据本身不会说话,但带着问题去看数据,每一行都会开口。比如最初我只是想画热力图,但当看到鼓楼区热力图上“湖南路”和“山西路”之间出现一条明显的冷色分界线时,我意识到这可能是地铁1号线施工围挡造成的短期流动性冻结——果然查施工公告,该路段封闭至2024年8月。再比如房龄-单价散点图上,2005-2010年建成的房源单价普遍高于2015年后新房,起初以为是品质问题,深入分析发现这批房多位于“名校集团化办学”的学区辐射范围内,而2015年后的新盘学区归属尚未明确。这些洞察,绝不是图表自动生成的,而是你盯着图表、结合本地知识、反复追问“为什么”之后的顿悟。
所以,我建议新手不要止步于“跑出20张图”,而是尝试三个延伸动作:
1.交叉验证:把热力图结果与南京统计局发布的《季度房地产市场报告》对比,看你的数据是否捕捉到了官方提到的“河西中部价格领涨”趋势;
2.归因实验:在run_analyze.py中临时屏蔽“装修”字段,重新生成散点图,观察房龄-单价相关性是否变化——如果R²从0.68降到0.52,说明装修是重要调节变量;
3.场景迁移:把config.py里的城市编码从nj改成nj(南京)→sh(上海),调整反爬Selector,你会发现上海链家对“满五唯一”标签的渲染逻辑完全不同,这正是你理解不同城市市场规则的入口。
这套工具包没有试图成为“万能钥匙”,它只是给你一把打磨好的南京钥匙。当你用它打开了第一扇门,后面所有的门,都需要你用自己的经验和思考去推开。毕竟,真正的市场洞察力,永远生长在数据与现实的缝隙之间。
本文还有配套的精品资源,点击获取
简介:直接运行就能跑出南京各板块二手房价格热力图、房龄与单价关系散点图、户型占比环形图、总价和单价分布箱线图、热门板块TOP10统计等20多张图表的Python实战工具包。内置适配链家反爬机制的爬虫脚本,支持自动抓取房源标题、挂牌价、面积、楼层、朝向、装修、建成年代等字段;含完整数据清洗流程,实现价格单位统一、文本型字段数值编码、高德API批量地理坐标解析;所有图表均附带可修改的绘图代码及对应PNG输出文件。README.md写清了Python环境配置(推荐3.9+)、依赖库安装(requests、pandas、matplotlib、seaborn、pyecharts、amapgis等)、一键运行命令和关键参数说明(如城市编码、爬取页数、保存路径)。.gitignore和.gitattributes已预置,开箱即用,适合零基础学数据分析的新手照着步骤复现,也方便房产中介或市场研究员快速生成本地化房价简报。
本文还有配套的精品资源,点击获取
