尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Python爬取链家广州二手房数据:从采集清洗到可视化分析实战
简介本资源面向Python爬虫与数据分析初学者及进阶学习者提供一套完整的链家广州二手房数据采集与可视化分析项目源码帮助读者掌握从网页请求、数据解析到清洗、统计与图表呈现的全流程实践。压缩包共13个文件包含9个py脚本、2个md说明文档、1个csv数据文件及1个gitignore配置整体约146KB其中py脚本覆盖网页内容获取、数据清洗、均价与中位数计算、总价与面积散点关系、朝向饼图、装修类型分布及小区在售数量统计等模块csv文件为已采集的广州房价信息md文档提供项目说明与使用指引。目前已有187人学习下载。读者可据此理解requests与BeautifulSoup的爬取思路、pandas的数据预处理方法以及Matplotlib等工具绘制折线图、柱状图、散点图与饼图的具体实现并参考反爬策略与数据标准化处理快速搭建自己的房产数据分析流程。1. 链家广州二手房数据这条链路卡住新手的从来不是 Python很多人第一次做「基于 Python 实现爬取链家广州二手房数据并可视化分析」这类项目卡点往往不在 Python 语法而在数据链路列表页翻到第 3 页就返回空、详情页字段对不上、清洗完发现总价单位混着「万」和「元」、画图时横坐标挤成一团。我见过太多人把requests跑通就以为结束了结果拿到一份 300 行、字段缺失一半的 CSV分析结论自然站不住脚。这篇笔记按一条能复现的链路走先讲清链家广州二手房页面的结构特征和抓取边界再落到请求头、翻页、字段解析、反爬节奏控制接着做清洗与落库最后用 matplotlib 和 pyecharts 出图。适合已经会装 Python、写过几行requests的从业者也适合想拿一份真实二手房数据做分析练手的人。全程只讲公开页面数据的采集与本地分析不涉及任何绕过访问控制的手段。2. 抓链家广州二手房前先把页面结构和字段口径摸清2.1 列表页与详情页各拿什么字段链家广州二手房的入口是城市站列表页URL 形如https://gz.lianjia.com/ershoufang/pg{页码}/翻页靠pg后面的数字。列表页每张卡片能直接拿到标题、小区名、区域、总价、单价、户型、面积、朝向、楼层、建造年份、关注人数。这些字段足够做大部分分析但如果你要「满五唯一」「近地铁」「挂牌时间」这类信息就得进详情页。我的建议是分两层抓列表页负责批量拿基础字段详情页只对需要的样本补抓。原因是详情页请求量是列表页的 30 倍左右全量进详情页既慢又容易触发风控。下面这张表是我实际抓取时确认过的字段口径新手最容易在这里翻车。字段列表页位置常见坑总价.totalPrice下的span单位是「万」别当元单价.unitPrice带「元/平米」后缀要正则剥离小区.positionInfo第一个a部分卡片为空需兜底区域.positionInfo第二个a商圈和行政区混在一起户型.houseInfo第一段用 面积.houseInfo第二段带「平米」后缀朝向.houseInfo第三段有的是「暂无数据」楼层.positionInfo或.houseInfo不同模板位置会变2.2 请求头、翻页与节奏控制的最小实现链家对请求头比较敏感缺User-Agent和Referer基本第一页就返回 403。翻页不要用range(1, 100)硬怼先抓第一页拿到总页数再按总页数循环。节奏上我一般每页 sleep 1.5 到 3 秒随机连续抓 20 页就停一会儿。下面是最小可跑版本。import requests import time import random from parsel import Selector HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36, Referer: https://gz.lianjia.com/ershoufang/, Accept-Language: zh-CN,zh;q0.9, } def fetch_page(page: int) - str: url fhttps://gz.lianjia.com/ershoufang/pg{page}/ resp requests.get(url, headersHEADERS, timeout10) resp.raise_for_status() # 链家返回的是 utf-8但部分页面会声明 gbk统一按 apparent_encoding 处理 resp.encoding resp.apparent_encoding return resp.text def parse_list(html: str): sel Selector(texthtml) items [] for li in sel.css(ul.sellListContent li): title li.css(.title a::text).get(default).strip() total li.css(.totalPrice span::text).get(default).strip() unit li.css(.unitPrice span::text).get(default).strip() house_info li.css(.houseInfo::text).get(default).strip() pos li.css(.positionInfo a::text).getall() items.append({ title: title, total_price: total, unit_price: unit, house_info: house_info, community: pos[0] if pos else , region: pos[1] if len(pos) 1 else , }) return items if __name__ __main__: all_rows [] for pg in range(1, 6): html fetch_page(pg) rows parse_list(html) print(fpage {pg} - {len(rows)} rows) all_rows.extend(rows) time.sleep(random.uniform(1.5, 3.0)) print(total:, len(all_rows))这段代码里几个参数值得说清楚。timeout10是防止某个请求挂死拖垮整批任务apparent_encoding比硬写utf-8稳因为链家部分页面编码声明不一致random.uniform(1.5, 3.0)是节奏控制固定 sleep 反而更容易被识别成脚本。parsel的Selector比 BeautifulSoup 在 CSS 选择器上更顺手如果你只装了bs4把sel.css(...)换成soup.select(...)也能跑。2.3 详情页补抓与字段合并列表页拿不到的字段比如挂牌时间、房屋朝向细节、梯户比例需要进详情页。详情页 URL 在列表页的.title a::attr(href)里。补抓时建议用「先存列表页结果再按需补详情」的两段式避免中途失败导致整批重来。def parse_detail(html: str) - dict: sel Selector(texthtml) base sel.css(.base .content li::text).getall() # base 里通常是「户型2室1厅」「面积89平米」这种键值对 detail {} for line in base: if in line: k, v line.split(, 1) detail[k.strip()] v.strip() return detail合并时用title或详情页 URL 作为唯一键避免同一小区同名房源串行。这里有个血泪经验链家列表页的标题会带「满五」「近地铁」这类营销词同一套房源在不同时间抓到的标题可能不一样所以唯一键优先用详情页 URL 里的房源编号。3. 数据清洗与落库把「万」「元/平米」这些脏字段拆干净3.1 单位统一与缺失值处理抓下来的原始数据里总价是「350」这种数字加「万」单位单价是「42000元/平米」面积是「89.5平米」。直接astype(float)会报错必须先剥离单位。缺失值方面朝向、楼层、建造年份经常为空我的处理原则是数值字段缺失就置NaN并记录分类字段缺失填「未知」不要用均值硬填否则分析结论会失真。import re import pandas as pd def to_float(text: str): if not text: return None m re.search(r[\d.], str(text)) return float(m.group()) if m else None def clean(df: pd.DataFrame) - pd.DataFrame: df df.copy() df[total_price] df[total_price].apply(to_float) # 单位万 df[unit_price] df[unit_price].apply(to_float) # 单位元/平米 # house_info 形如「2室1厅 | 89.5平米 | 南 | 精装 | 中楼层」 parts df[house_info].str.split(|, expandTrue) df[layout] parts[0].str.strip() df[area] parts[1].apply(to_float) df[orientation] parts[2].str.strip() if parts.shape[1] 2 else 未知 df[decoration] parts[3].str.strip() if parts.shape[1] 3 else 未知 df[floor] parts[4].str.strip() if parts.shape[1] 4 else 未知 df[orientation] df[orientation].fillna(未知) df[decoration] df[decoration].fillna(未知) df[floor] df[floor].fillna(未知) # 面积和总价都有的才保留否则单价分析没意义 df df.dropna(subset[area, total_price]) df df[df[area] 0] return dfto_float用正则而不是strip(万元/平米)是因为实际文本里单位位置不固定有的写「350万」有的写「350 万」正则更稳。dropna只对面积和总价做因为这两个是后续单价计算和分布分析的基础缺了就没法用。3.2 落库与去重清洗完建议同时存 CSV 和 SQLite。CSV 方便快速看SQLite 方便后续按区域、户型做聚合查询也避免每次分析都重新读大文件。去重按详情页 URL 或房源编号不要按标题原因前面说过。import sqlite3 def save(df: pd.DataFrame, csv_pathgz_ershoufang.csv, db_pathgz.db): df.to_csv(csv_path, indexFalse, encodingutf-8-sig) conn sqlite3.connect(db_path) df.to_sql(ershoufang, conn, if_existsreplace, indexFalse) conn.close() def dedup(df: pd.DataFrame, keyurl) - pd.DataFrame: if key in df.columns: return df.drop_duplicates(subset[key]) return df.drop_duplicates(subset[title, area, total_price])utf-8-sig是为了 Excel 打开不乱码这个细节很多人踩过。if_existsreplace在调试阶段方便正式跑建议改成append并加抓取日期字段方便做时间序列对比。4. 可视化分析单价分布、区域对比和户型结构怎么画才不挤4.1 单价分布直方图与横坐标处理二手房单价分布是最直观的分析入口。用 matplotlib 画直方图时横坐标太密集是高频问题解决办法是控制bins数量并旋转刻度标签。广州二手房单价跨度大从 1 万多到 10 万以上都有直接画会被极端值拉扁建议先做 1% 和 99% 分位截断。import matplotlib.pyplot as plt import matplotlib matplotlib.rcParams[font.sans-serif] [SimHei] matplotlib.rcParams[axes.unicode_minus] False def plot_unit_price(df: pd.DataFrame): s df[unit_price].dropna() low, high s.quantile(0.01), s.quantile(0.99) s s[(s low) (s high)] plt.figure(figsize(10, 5)) plt.hist(s, bins40, color#4C72B0, edgecolorwhite) plt.xlabel(单价元/平米) plt.ylabel(房源数量) plt.title(广州二手房单价分布1%-99% 分位截断) plt.xticks(rotation45) plt.tight_layout() plt.savefig(unit_price_dist.png, dpi150) plt.close()SimHei是 Windows 自带中文字体Mac 上换成Arial Unicode MSLinux 上如果没有中文字体会显示方块需要提前装字体。bins40是经验值太少看不出分布形状太多柱子会糊成一片。rotation45解决横坐标标签重叠tight_layout防止标签被裁掉。4.2 区域均价对比与户型结构区域对比用横向条形图更清楚因为广州各区名字长短不一竖着放容易挤。户型结构用饼图或堆叠条形图我一般用堆叠条形图因为饼图超过 5 类就看不清了。def plot_region_price(df: pd.DataFrame): g df.groupby(region)[unit_price].median().sort_values(ascendingTrue) g g[g.index ! ] plt.figure(figsize(9, 6)) g.plot(kindbarh, color#55A868) plt.xlabel(单价中位数元/平米) plt.title(广州各区域二手房单价中位数) plt.tight_layout() plt.savefig(region_price.png, dpi150) plt.close() def plot_layout(df: pd.DataFrame): top df[layout].value_counts().head(8) plt.figure(figsize(9, 5)) top.plot(kindbar, color#C44E52) plt.ylabel(房源数量) plt.title(广州二手房户型分布 Top8) plt.xticks(rotation30) plt.tight_layout() plt.savefig(layout_dist.png, dpi150) plt.close()用中位数而不是均值是因为单价分布右偏均值会被高价房源拉高中位数更能代表区域真实水平。head(8)是控制类别数量超过 8 类图就不好看了。如果要做交互式图表pyecharts 的Bar和Pie可以直接输出 HTML适合放进报告里。5. 避坑与排查链家二手房抓取里最容易翻车的 5 个点5.1 翻页到第 3 页后返回空列表现象前两页正常第 3 页开始sellListContent里没有li或者直接返回验证页。原因通常是请求频率过高触发了风控或者pg参数被识别成异常。解决把 sleep 调到 2 到 4 秒随机每抓 10 到 20 页停 30 秒以上同时检查Referer是否和当前页匹配。如果持续为空先手动在浏览器打开对应页确认页面结构有没有变。5.2 总价和单价字段抓出来是空字符串现象totalPrice span::text拿到空值。原因是链家部分卡片用了懒加载或不同模板span层级不一样。解决先用li.css(.totalPrice).get()把整块 HTML 打出来看结构再调整选择器。兜底方案是用li.css(.totalPrice::text).get()配合正则提取数字。5.3 清洗后面积字段大量为 NaN现象house_info里有的用|分隔有的用空格或/分隔split 后索引错位。解决不要死守split(|)先用正则re.split(r[|/], house_info)统一分隔符再按「含『平米』的那段」定位面积而不是固定取第二段。5.4 画图时中文显示成方块现象matplotlib 图表里中文全是方框。原因是默认字体不含中文。解决Windows 设SimHeiMac 设Arial Unicode MSLinux 需要先安装中文字体并清缓存。设完记得加axes.unicode_minus False否则负号也会出问题。5.5 数据落库后重复行很多现象SQLite 里同一套房源出现多次。原因是列表页翻页时页面内容有重叠或者中途重跑没清库。解决用详情页 URL 或房源编号做唯一键去重重跑前先drop_duplicates正式任务加抓取日期字段区分批次。6. 把这份数据用起来从单次抓取到可复用的分析习惯抓到一份干净数据只是起点真正有价值的是把它变成可复用的分析流程。我自己的习惯是每次抓完先做三件事记录抓取日期和页数、对关键字段做一次分布检查、把清洗脚本和可视化脚本分开存。这样下次想对比不同时间点的价格变化直接换数据文件就行不用重写逻辑。进阶用法上可以把区域、户型、面积段做成组合维度比如「天河区 70 到 90 平米两房单价中位数」这种细分结论比整体均价更有参考价值。验证方法也简单随机抽 10 条记录手动在链家页面上核对总价、面积、小区如果对得上说明解析逻辑没问题对不上就回去查选择器和清洗规则。def segment_analysis(df: pd.DataFrame, region天河, area_min70, area_max90): sub df[(df[region] region) (df[area] area_min) (df[area] area_max)] if sub.empty: return None return { count: len(sub), median_unit_price: sub[unit_price].median(), median_total_price: sub[total_price].median(), }这个函数可以按区域和面积段快速切片输出中位数指标。参数region要和清洗后的区域字段口径一致area_min和area_max按你的分析目标调整。跑之前先确认region字段没有空值否则会漏掉一部分样本。最后说个我自己的教训早期我图快把 sleep 设成 0.5 秒结果抓到第 8 页就被限整批数据作废重来。后来固定成随机 2 到 4 秒虽然慢但一次跑完不用返工反而更省时间。数据采集这件事稳比快重要。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

随机变量:从抽象定义到工程实践,避开建模中的常见陷阱

随机变量:从抽象定义到工程实践,避开建模中的常见陷阱

随机变量这个概念,很多人第一次接触时都会觉得抽象——明明叫“变量”,怎么又跟“随机”扯上关系了?我在带新人和做项目复盘时反复遇到同一个现象:大家能背出定义,却说不清它到底解决什么问题,更别提在实际…

📅 2026/10/31 14:08:27
搭建个人技能管理体系:把能力变成可管理的资产

搭建个人技能管理体系:把能力变成可管理的资产

做了这么多年技术和管理,我越来越发现一个残酷的事实:决定一个人能走多远的,往往不是他某一项技能有多强,而是他能不能把自己的技能“管理”起来。很多人明明学了一堆东西,真到用的时候却想不起来;有些人工…

📅 2026/10/31 14:08:27
电阻标准阻值体系:E系列优选数系与选型实战指南

电阻标准阻值体系:E系列优选数系与选型实战指南

电阻选型的时候,你有没有想过一个问题:为什么手头能买到的电阻,阻值总是那几个数?1k、2.2k、4.7k、10k,翻来覆去就这些。你想找个3.3k的,有;想找个3.5k的,翻遍货架也找不到。刚入行那…

📅 2026/10/31 14:08:27
MORE NEWS

更多资讯

📰

2026大模型学习路线:从RAG到Agent的全景指南

1. 大模型学习生态的底层逻辑与全局认知 1.1 为什么2026年学大模型不能再走“单点突破”的老路 我在过去两年带过不少想转大模型方向的朋友,发现一个特别普遍的现象:很多人一上来就问“PyTorch怎么学”“Transformer源码怎么读”,然后埋头啃…

📰

机器人产业十年发展:从自动化到AI赋能的演进

1. 机器人产业十年发展背景2008年全球金融危机后,制造业开始重新审视自动化转型的必要性。当时我在深圳一家电子代工厂负责产线优化,亲眼见证了人工成本上涨带来的压力。一台六轴机械臂的价格相当于3名工人一年的薪资,但稳定性问题让很多企业…

📰

RAG从Demo到生产:六道分水岭与Agentic RAG、GraphRAG实战

1. 为什么“RAG 烂大街”是个伪命题这两年但凡跟大模型沾点边的项目,十有八九会挂上 RAG 三个字母。招聘 JD 里写“熟悉 RAG 检索增强”,技术分享里讲“我们基于 RAG 搭建了企业知识库”,开源社区里各种 RAG 框架层出不穷。于是很多人得出一个…

📰

Flink窗口机制解析与实时流处理实践

1. Flink窗口机制概述在实时流处理领域,窗口(Window)是将无限数据流切分为有限数据块进行计算的核心理念。作为Apache Flink的核心特性之一,窗口机制允许我们对持续到达的数据流进行分段聚合处理。想象一下高速公路上的车流量监测…

📰

Flink窗口机制详解:类型、原理与实战优化

1. Flink窗口机制概述 在实时数据处理领域,窗口计算是最核心的概念之一。作为Apache Flink的资深使用者,我经常需要处理各种时间维度的聚合分析需求。Flink的窗口机制提供了灵活的时间边界定义方式,让我们能够对无界数据流进行有意义的切片计…

📰

Google广告原生对话:零跳转咨询实战指南

1. 这不是科幻片,是正在发生的销售现场重构最近刷到一条消息:“Google开始测试‘广告里的AI销售员’”,标题里那个引号用得特别有意思——它既像在强调这个新角色的特殊性,又像在提醒我们:这玩意儿还没正式挂牌上岗&am…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬