首页 / 资讯中心 / 文章详情

链家二手房数据实战:Python爬虫到Jupyter分析全流程

链家二手房数据实战:Python爬虫到Jupyter分析全流程 ★ FEATURED ARTICLE
简介这份资源是面向数据分析初学者与Python爬虫实践者的链家二手房分析项目源码围绕真实房源数据的采集、清洗、可视化与报告输出展开适合用作课程设计、期末大作业或数据分析入门练手。压缩包共20个文件、约30.68MB包含1个py爬虫脚本、1个ipynb分析笔记、1个csv原始数据表以及13张png可视化图表、1份pptx演示文稿、1份docx报告和若干txt说明文件覆盖从抓取到成稿的完整链路。图表涉及词云、聚类、区域单价热力图、户型与装修分布、面积总价散点等可直接参考其分析维度与呈现方式。目前已有559人学习下载读者可据此掌握requests与解析库的爬虫写法、Jupyter中的数据处理流程并借鉴报告与演示文稿的组织结构快速复现一套可交付的二手房分析成果。1. 链家二手房数据分析从爬虫到 Jupyter 的完整落地路径二手房挂牌数据是很多人做数据分析练手时绕不开的一类真实数据集而链家作为房源信息相对规范的平台常被拿来当第一个实战目标。这个标题讲的事情很具体用 Python 爬虫把链家二手房房源抓下来存成结构化数据再在 Jupyter Notebook 里做清洗、统计和可视化。它解决的是「有真实数据可分析、但不知道数据从哪来、怎么变成能画图的表」这个断点。适合已经会一点 Python 基础语法、想跑通一个完整数据分析项目的人也适合做数据分析项目作业、想找一个有业务含义的数据集练手的人。整条链路里爬虫负责取数Jupyter 负责分析和呈现中间的数据清洗才是真正花时间的地方。2. 抓链家二手房数据请求构造与字段解析2.1 为什么选 requests 静态解析而不是一上来就上框架链家的二手房列表页在多数城市是服务端渲染的房源标题、总价、单价、小区、区域、户型、面积、楼层、朝向这些字段直接出现在返回的 HTML 里。这意味着用 requests 拿到页面源码再用解析库提取节点就能拿到大部分字段不需要一开始就上 Selenium 或 Playwright 这类浏览器自动化工具。常见做法是先用 requests 跑通单页确认字段能稳定取到再考虑翻页和批量。选 requests 的理由很直接依赖少、调试快、单页请求耗时低。选解析库时lxml 的 xpath 写法对嵌套结构友好BeautifulSoup 的容错性更好。我一般用 lxml xpath因为链家的 DOM 层级比较规整xpath 能精确定位到列表项。这里要提醒一句链家不同城市站点的 DOM 结构可能有差异写解析规则前先手动打开一个城市站点用浏览器开发者工具确认列表容器的 class 或结构特征别直接抄网上的选择器。2.2 单页抓取的最小可运行代码下面这段代码是抓取链家某个城市二手房列表页第一页的最小示例重点看请求头构造和字段提取逻辑。import requests from lxml import etree import csv import time # 请求头里 User-Agent 是必须的缺失会被直接拒绝 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 ), Accept-Language: zh-CN,zh;q0.9, } def parse_list_page(url): resp requests.get(url, headersHEADERS, timeout10) resp.encoding utf-8 # 链家页面多为 utf-8显式指定避免乱码 if resp.status_code ! 200: print(状态码异常:, resp.status_code) return [] html etree.HTML(resp.text) items html.xpath(//ul[classsellListContent]/li) rows [] for li in items: title li.xpath(.//div[classtitle]/a/text()) total li.xpath(.//div[classtotalPrice]/span/text()) unit li.xpath(.//div[classunitPrice]/span/text()) house li.xpath(.//div[classhouseInfo]/text()) position li.xpath(.//div[classpositionInfo]/a/text()) rows.append({ title: title[0].strip() if title else , total_price: total[0].strip() if total else , unit_price: unit[0].strip() if unit else , house_info: house[0].strip() if house else , position: position[0].strip() if position else , }) return rows if __name__ __main__: base https://bj.lianjia.com/ershoufang/pg1/ data parse_list_page(base) print(抓到条数:, len(data)) for d in data[:3]: print(d)逻辑说明requests.get 负责取回页面文本timeout 防止请求卡死resp.encoding 显式设为 utf-8否则中文可能变成乱码etree.HTML 把文本转成可 xpath 的树xpath 里的.//表示在当前 li 节点下查找避免匹配到其他列表项。参数说明HEADERS 里的 User-Agent 建议换成你自己浏览器的真实值timeout 一般设 10 秒网络差可放宽到 15pg1 是页码翻页时替换数字即可。2.3 翻页与字段落库单页跑通后翻页就是把 pg1 到 pgN 循环起来。链家二手房列表通常一页 30 条翻页 URL 规律是pg{页码}。批量抓取时必须在每次请求之间加 sleep常见做法是 1 到 3 秒随机间隔降低被限流的概率。抓到的字段建议先落成 CSV方便后面在 Jupyter 里直接读。import random def crawl_pages(city_base, max_page5): all_rows [] for page in range(1, max_page 1): url f{city_base}pg{page}/ rows parse_list_page(url) all_rows.extend(rows) print(f第 {page} 页完成累计 {len(all_rows)} 条) time.sleep(random.uniform(1, 3)) # 随机间隔别用固定值 return all_rows def save_csv(rows, pathlianjia_ershoufang.csv): if not rows: print(无数据可写) return keys rows[0].keys() with open(path, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnameskeys) writer.writeheader() writer.writerows(rows) print(已写入:, path)逻辑说明crawl_pages 把翻页和解析串起来max_page 控制抓取深度save_csv 用 utf-8-sig 编码这样用 Excel 打开不会中文乱码。参数说明max_page 建议先设 5 页试跑确认字段完整再放大sleep 区间不要低于 1 秒固定间隔容易被识别为脚本行为。3. 在 Jupyter 里把原始数据变成可分析的表3.1 Jupyter Notebook 环境准备与数据读取Jupyter 的价值在于边写边看结果适合做数据清洗这种需要反复试的活。安装方式常见的是通过 pip 装 notebook或者用 Anaconda 自带。启动后在浏览器里新建 notebook第一件事是确认 pandas 能正常导入。如果启动时提示需要 password or token按终端里打印的带 token 的地址访问即可这是 Jupyter 的访问校验机制不是报错。import pandas as pd import numpy as np # 读取爬虫落地的 CSV注意编码要和写入时一致 df pd.read_csv(lianjia_ershoufang.csv, encodingutf-8-sig) print(df.shape) df.head()逻辑说明read_csv 的 encoding 必须和写入时一致否则会抛 UnicodeDecodeErrordf.shape 先看行列数确认数据量是否符合预期。参数说明如果 CSV 里有中文列名保持默认即可如果字段里有千分位逗号读取后需要额外处理成数值。3.2 字段清洗把「89.5平米」变成 89.5爬下来的字段大多是带单位的字符串比如总价是「350万」面积混在 house_info 里是「2室1厅 | 89.5平米 | 南 | 精装」。要分析就得把这些拆成数值列。常见做法是用正则提取数字再转成 float。import re def extract_area(info): # 从 houseInfo 里提取面积数字 m re.search(r(\d\.?\d*)平米, str(info)) return float(m.group(1)) if m else np.nan def extract_total(text): m re.search(r(\d\.?\d*), str(text)) return float(m.group(1)) if m else np.nan df[area] df[house_info].apply(extract_area) df[total_price_num] df[total_price].apply(extract_total) df[unit_price_num] df[unit_price].apply(extract_total) # 丢弃关键字段缺失的行 df df.dropna(subset[area, total_price_num]) print(df[[area, total_price_num, unit_price_num]].describe())逻辑说明extract_area 用正则匹配「数字平米」匹配不到返回 NaNextract_total 抓第一个数字适配「350万」这类格式dropna 把关键字段缺失的行去掉避免后续统计被污染。参数说明正则里的\d\.?\d*能匹配整数和小数如果面积出现「89平米」和「89.5平米」混排也能覆盖describe 用来快速看数值分布是否合理。3.3 分组统计与可视化清洗完就能做业务统计了。二手房分析里最常看的是各区域均价、面积段分布、总价段分布。Jupyter 里配合 matplotlib 直接出图横坐标太密集时用 rotation 旋转标签。import matplotlib.pyplot as plt # 按区域看均价position 字段里含区域信息 df[region] df[position].astype(str).str.split(-).str[0] region_price df.groupby(region)[unit_price_num].mean().sort_values(ascendingFalse) plt.figure(figsize(10, 5)) region_price.head(10).plot(kindbar) plt.title(各区域二手房平均单价) plt.ylabel(单价) plt.xticks(rotation45) # 横坐标标签旋转避免重叠 plt.tight_layout() plt.show()逻辑说明groupby 按区域聚合单价均值sort_values 降序排列plot(kindbar) 出柱状图rotation45 解决横坐标标签挤在一起的问题。参数说明head(10) 只画前 10 个区域区域多时可调整figsize 控制图幅太小标签会重叠。4. 避坑与排查链家爬虫和 Jupyter 分析里最容易翻车的地方4.1 请求被拒或返回空列表现象requests 返回 403或者状态码 200 但 xpath 取到 0 条。原因缺少 User-Agent 或请求头过于简单被服务端识别为脚本也可能是页面结构变了原来的 xpath 失效。解决补全 User-Agent 和 Accept-Language先用浏览器访问同一 URL 确认页面正常再把返回的 HTML 存下来用 xpath 工具单独验证选择器。4.2 中文乱码现象CSV 里中文变成问号或乱码。原因requests 没显式设 encoding或者写 CSV 时用了默认编码。解决请求后设 resp.encoding utf-8写 CSV 用 utf-8-sig这样 Excel 也能正常打开。4.3 翻页抓到重复数据现象抓了 5 页去重后发现只有 2 页的量。原因页码参数拼错或者某些页返回的是推荐位而非列表。解决打印每页 URL 和返回条数确认 pg 参数生效对 title 字段做 drop_duplicates把重复项去掉再分析。4.4 Jupyter 里改了代码但结果没变现象改了清洗逻辑重新运行单元格df 还是旧数据。原因notebook 单元格执行顺序乱了后面的单元格引用了旧的变量。解决养成从上到下顺序执行的习惯或者改完关键单元格后用 Restart Run All 整体重跑。4.5 数值字段转 float 报错现象apply 转数值时抛 ValueError。原因字段里混入了「暂无数据」「暂无报价」这类非数字文本。解决在提取函数里用 try 或正则先判断匹配不到返回 np.nan再统一 dropna别让异常中断整个流程。5. 让分析更稳的几个进阶习惯爬到数据只是起点真正决定这份分析能不能拿出手的是字段口径是否统一。我一般会在 Jupyter 里单独开一个单元格把清洗后的关键字段做一次 describe 和缺失率统计确认没有异常值再往下画图。比如单价字段如果出现个位数多半是单位没对齐得回头检查提取逻辑。另一个习惯是把爬虫和分析拆成两个 notebook。爬虫那个只负责取数和落 CSV分析那个只读 CSV不重复发请求。这样调试分析逻辑时不会反复触发爬取也避免因为改代码把已经抓好的数据覆盖掉。数据量大的时候可以在分析 notebook 开头加一个缓存判断CSV 存在就直接读不存在再提示去跑爬虫。验证分析结果是否可信有个简单办法挑一个你熟悉的区域手动在链家页面上看几套房源的单价和 groupby 出来的均值对一下。如果偏差很大大概率是清洗环节把某些字段串了。这个交叉验证花不了几分钟但能省掉后面返工的血泪经验。最后说个我自己的教训别一上来就追求抓全城所有房源。先把一个区域、五页数据跑通字段清洗干净图能出来再逐步放大范围。很多人卡在第一步就是因为想一次抓几万条结果被限流、字段错位、编码乱码一起爆发最后连一份能看的表都没留下。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站