Python爬取链家租房数据:从请求伪装到数据分析全流程 简介面向Python爬虫入门者的实战资源以链家网租房数据为目标演示从构造请求、解析HTML到清洗存储的完整流程适合学习Requests、BeautifulSoup以及Pandas数据处理技能的读者。压缩包共8个文件包含5个Python脚本和3个pyc编译文件整体仅4KB脚本覆盖爬虫主程序、异常处理、登录模拟、数据保存等模块pyc文件则用于快速加载或发布场景结构紧凑便于逐模块研读。目前已有6663人学习下载足见该实例的受欢迎程度和参考价值。通过这份资源可具体看到如何处理分页、设置请求头伪装、利用延时规避反爬并使用pandas和csv将租金、面积、位置等字段整理为结构化数据登录模拟和异常处理部分也提供了可复用的抓取思路。资源中的Python脚本各有分工从发起请求到保存结果形成了清晰模块便于按需修改尽管包体小巧但请求、解析、存储链路完整非常适合作为爬虫入门练习与二次开发的基底。 每隔一段时间我都会用python爬取链家网租房数据给城市租房市场做个快照。这个习惯坚持了很久起因很朴素——不是想批量抓全站而是想搞清楚真实挂牌价、户型分布和区域热度这比听中介口述或者看新闻稿靠谱。如果你正在研究租房市场、写毕业设计或者单纯想拿爬虫练手这篇能让你少走不少弯路。我会把完整链路过一遍页面分析、请求伪装、解析清洗、数据存储、第一轮统计以及路上踩过的反爬坑。需要先说明链家的页面结构和风控策略经常调整下面所有选择器和地址都以你实际抓取时的页面为准我的代码提供的是思路不是万年不变的“神器”。1. 先拆需求租房数据到底要哪些字段1.1 为什么不是上来就写代码很多人拿到标题第一反应是打开编辑器写requests.get我劝你先花十分钟把字段定下来。抓链家租房最常用的字段大概是标题、价格、户型、面积、朝向、所在区域、商圈、小区、楼层、标签比如近地铁、随时入住、发布时间、房源详情链接。如果只抓标题和价格后面做区域热度分析会发现自己少了一堆关键维度只能重头再爬一遍。这个教训我自己就踩过第一版脚本只抓了标题、价格、链接结果想按商圈排序时发现没有区域字段只能返工。我的建议是先想清楚拿到数据后用来做什么如果做租金统计价格、区域、面积是核心如果做户型偏好分析户型字段必须单独拆出来而不能和描述混在一起。字段清单定下来代码结构也就清晰了。1.2 链家租房页面的URL规律链家租房分城市、分页。以北京为例租房列表页是https://bj.lianjia.com/zufang/第二页是https://bj.lianjia.com/zufang/pg2/以此类推。gz、sh、sz这些城市代码就是拼音首字母换城市只需要替换前缀。建议你第一站先手动访问列表页按 CtrlU 查看网页源代码在源码里搜“content__list”或者“租金”这类关键字确认数据是直接渲染在 HTML 里的还是通过异步接口加载的。不同时期链家的做法有变化根据我的观察租房列表页大体上是服务端渲染但结构改过好几轮网上很多老教程里的选择器拿过来直接跑是会扑空的。拿到 HTML 之后先保存到本地或 print 前几百个字符确认里面有房源标题再做下一步。这一段分析看起来琐碎却能帮你省掉后续几十次无效请求。1.3 用开发者工具确认数据是HTML还是接口返回另一个判断方法是用浏览器开发者工具。打开 Network 面板刷新页面过滤 XHR/Fetch看有没有返回 JSON 的接口。如果找到返回房源数据的接口那解析效率会高很多直接请求接口拿 JSON 即可。如果没找到就说明列表数据在 HTML 里。这里说个不太顺的体验有些异步接口在第一次请求时能通但连续翻几页之后会返回异常接口可能带签名或者频率限制。遇到“接口被封”的情况不要死磕退回去解析 HTML 反而更稳。不少做爬虫的朋友都遇到过类似尴尬我的经验是永远留一条 HTML 解析的后备路径。另外链家页面结构偶尔会在某个城市单独改版北京能用的选择器换到成都可能就不一样所以每次换城市都要重新验证一次。2. 请求环节的细节处理从Headers到重试2.1 环境准备与依赖安装Python 环境建议 3.8 以上依赖装四个包就够了requests、beautifulsoup4、lxml、pandas。安装命令是pip install requests beautifulsoup4 lxml pandaslxml 是解析器比 Python 自带的 html.parser 快解析大页面时能明显感觉到差距。Windows 下 lxml 一般有预编译的 wheel 包直接 pip 装基本不会出问题真要碰到编译类报错换用 Python 官方最新版大概率也能绕过。我一般在项目目录下先建一个虚拟环境避免把全局环境搞乱。等你装了四五个项目以后就会发现虚拟环境是最省心的习惯。如果只想快速跑通不装 pandas 也可以但后面做分析会麻烦建议一次装齐。依赖安装这一步本身没有太多花样真正的功夫在请求头的细节里。2.2 请求头的伪装与编码处理requests 默认的 User-Agent 是python-requests/2.x这种特征太明显目标站点识别起来毫无压力。至少要换成一个真实浏览器的 UA最好再把 Referer、Accept-Language 带上让请求看起来像一个人正常打开页面。参考配置如下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, Referer: https://bj.lianjia.com/, Accept-Language: zh-CN,zh;q0.9, }有一个容易忽略的点是编码。requests 拿到的响应到底用什么编码有时候判断不准我的处理是显式设置resp.encoding resp.apparent_encoding中文乱码基本可以解决。链家页面一般是 UTF-8但碰到过个别情况别偷懒统一处理一下。另外不建议搞一套疯狂随机切换 UA 的逻辑那反而会触发风控。真实的做法是固定用一个比较新的 Chrome UA保持低调别频繁更换。请求头是服务器判断爬虫的第一道关卡做得到位后面会省心很多。2.3 重试与随机延时慢就是快反爬的第一道防线就是请求频率。很多新手写完循环不加延时几十个请求出去没多久就被限制了。我习惯写一个带重试的请求函数加上随机延时代码大致是这样import time import random import requests def fetch(url, headers, retries3): for i in range(retries): try: resp requests.get(url, headersheaders, timeout10) if resp.status_code 200: resp.encoding resp.apparent_encoding return resp except Exception as exc: print(f第{i1}次请求失败: {exc}) time.sleep(2 random.random()) return None两个页面之间再加一句time.sleep(random.uniform(1, 3))这个随机延时是防反爬最基础也最有效的动作。别觉得这样慢爬租房数据不是抢竞拍慢一点换来稳定。我自己的标准是单页请求间隔不低于 1 秒如果是连续跑几百页间隔还会放大到 2-4 秒。这里再提醒一句一旦发现解析结果为空先打印原始 HTML 片段不要猜。我见过太多人对着一个被反爬拦下的空页面改了半天解析逻辑最后才意识到问题根本不在代码而在请求频率上。3. 解析与清洗把页面文本变成结构化表格3.1 BeautifulSoup解析列表项列表页每页大约 30 条房源整个页面解析其实不复杂。先创建一个解析单条的函数传入一条房源节点的 BeautifulSoup 对象返回一个 dictfrom bs4 import BeautifulSoup import re def parse_item(item): title_tag item.select_one(div[class*content__list--item--title] a) desc_tag item.select_one(p[class*content__list--item--des]) price_tag item.select_one(span[class*content__list--item-price]) if not title_tag or not price_tag: return None return { title: title_tag.get_text(stripTrue), link: title_tag.get(href, ), desc: desc_tag.get_text( , stripTrue) if desc_tag else , price_text: price_tag.get_text(stripTrue), }注意选择器里的*表示模糊匹配这样即使 class 带了一串动态后缀也能命中。链家页面改版过几次最稳妥的方式是抓一次页面后用浏览器的 Copy selector 生成目标元素的选择器再拿去程序里验证。写代码时不要一次性把全部字段都写完先只解析标题和价格打印前 3 条确认无误后再补其他字段。这样调试成本最低。网上的老教程选择器经常失效不是代码写错了是页面变了判断这个问题的唯一办法就是打印真实 HTML 去对。3.2 字段清洗的几个细节HTML 里拿到的文本通常会带空格、换行、单位必须做清洗。价格可能是“5600元/月”面积是“45.32㎡”户型是“2室1厅”区域是“朝阳-望京-融科橄榄城”。我建议统一用正则提取核心数字再做类型转换price_match re.search(r\d, item.get(price_text, )) price float(price_match.group()) if price_match else None area_match re.search(r[\d.], item.get(area_text, )) area float(area_match.group()) if area_match else None然后把区域字段按“-”拆开分别存到 district、bizcircle、community 三列。标签字段可能有多个比如“近地铁”“随时看房”我习惯用竖线连接。这里有个坑文本里可能混着“整租”“押一付三”这些说明不要统统塞进面积字段先打印一段原始文本看清楚再写正则。字段清洗是爬虫里最费时间的环节但做好之后后续分析会顺滑很多。我第一次跑的时候没仔细看把“2室1厅”里的数字当成了面积统计出来的数据惨不忍赌后来加了一堆校验才恢复正常。3.3 多页翻页逻辑翻页逻辑也很直接从第一页开始把 URL 里的 pg1 换成 pgN。页面上通常有翻页控件部分版本的 HTML 里带了 JSON 格式的 page-data 属性里面有 totalPage 字段可以优先读取。拿不到就循环抓直到解析出来的房源列表为空为止。建议加一个最大页数限制比如最多 100 页避免程序失控。每爬完一页打印一条日志记录页码、条数和这一页第一条的价格这样中途出问题也能快速定位。我在实跑中发现链家有时候会返回“已为您暂时跳过部分房源”的提示说明列表被截断这时候可以按区域拆分后再爬而不是死磕一个 URL。比如按朝阳、海淀、丰台分别抓一遍既能绕过列表截断又能让数据更稳定。4. 数据落地与快速验证CSV、SQLite和第一轮统计4.1 CSV直接保存数据量不大时CSV 是最方便的交付格式。把所有解析结果收集成 list of dict用 pandas 写出去import pandas as pd df pd.DataFrame(all_items) df.to_csv(lianjia_zufang.csv, indexFalse, encodingutf-8-sig)用utf-8-sig编码是给 Excel 看的否则直接用 Excel 打开 CSV 中文容易乱码。这个细节我每次都会讲因为它能在你朋友面前省掉很多尴尬。缺点也明显重复运行会覆盖如果想追加需要改成modea但 CSV 在追加时不能方便去重。所以如果只是想临时跑一两次CSV 够用如果打算长期采集我更推荐 SQLite。CSV 还有一个问题字段内容里如果包含逗号或换行容易让表格错位所以前面清洗时我会把标签字段里的逗号替换成竖线这个习惯帮我避过很多莫名其妙的 file parsing 报错。4.2 SQLite便于去重和增量SQLite 是 Python 自带的数据库不需要额外安装服务很适合这种小规模爬虫。建表时给房源链接加 UNIQUE 约束插入时用INSERT OR IGNORE天然去重import sqlite3 conn sqlite3.connect(lianjia.db) conn.execute( CREATE TABLE IF NOT EXISTS house ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, district TEXT, bizcircle TEXT, layout TEXT, area REAL, price REAL, link TEXT UNIQUE ) ) rows [ (h[title], h[district], h[bizcircle], h[layout], h[area], h[price], h[link]) for h in all_items ] conn.executemany( INSERT OR IGNORE INTO house (title,district,bizcircle,layout,area,price,link) VALUES (?,?,?,?,?,?,?), rows, ) conn.commit() conn.close()这样重复爬同一条房源时UNIQUE 约束会阻止重复插入。我个人的习惯是 CSV 做最终交付SQLite 做过程存储。给非技术背景的朋友看数据直接丢 Excel 文件自己后续写分析脚本则从 SQLite 读数据。两不耽误。增量爬取的关键就是这个 UNIQUE 约束没有它第二次跑的时候数据会翻倍最后做统计全是错的。4.3 落库后的快速验证先看一眼区域统计数据落库之后别急着去画图先用一两行 pandas 代码验证一下数据合理性。把价格、面积转成数值类型然后按区域统计df pd.read_csv(lianjia_zufang.csv) df[price] pd.to_numeric(df[price], errorscoerce) df[area] pd.to_numeric(df[area], errorscoerce) df.dropna(subset[price, area], inplaceTrue) result df.groupby(district)[price].agg([count, median, mean]) print(result.sort_values(count, ascendingFalse))这一步能立刻发现解析函数有没有问题。如果某个区域房源数异常少或者价格中位数是 0多半是清洗逻辑有遗漏回头去改。我每次跑完都会先看这个统计确认数据合理再继续做更复杂的分析。用 pandas 的to_numeric配合errorscoerce遇到转换不了的值会变成 NaN非常省心。从爬数据到验证数据这才是完整的闭环。5. 链家反爬实测空页面、验证码与降级方案5.1 最常见的三种反爬表现这个章节下面的经验全是我实际跑出来的不是道听途说。最常见的表现有三种。第一种是 HTTP 状态码 200但页面列表区域是空的或者房源数量为 0这种最阴因为状态码完全正常代码也不报错。第二种是页面直接弹出访问过于频繁的提示要求拖动拼图验证。第三种是返回 403 或 521 这类异常状态码一看就知道被拒了。我之前犯过一个错用并发池同时发几十个请求跑了几分钟所有页面突然全是空的退回去看请求日志发现服务器返回了“当前访问人数过多请稍后再试”。当时没有打印响应内容对着空 HTML 瞎折腾了很久浪费了大半天。所以遇到空页面第一件事是打印响应文本而不是改选择器。5.2 降级处理策略遇到反爬最正确的做法是停手而不是硬刚。验证码这一层我不建议去搞什么自动识别既不稳定也不符合规矩。我的降级策略很朴素先停止脚本休息 20-30 分钟再把并发改成串行加长延时到 3-5 秒最后把任务拆小。比如原来一次性爬整个城市的租房列表改成按行政区分批跑每批只跑 100-200 条跑完休息几分钟再跑下一批。还可以给脚本加一个总时长上限比如最多运行 20 分钟到点自动退出避免你睡觉时脚本在那儿一顿猛跑第二天起来发现 IP 被限制得更狠。另一个小技巧是设置合理的运行时段比如工作日的晚上或者周末的白天更像真实用户的行为也不至于给服务器造成压力。5.3 合规边界问题虽然标题是爬取但我还是要说几句守规矩的话。数据只用于个人学习研究和非商业用途这是底线。请求频率一定要控制别给目标服务器造成压力。如果网站的服务条款或 robots.txt 明确表示不允许抓取这类数据那就停手改从公开数据集或官方渠道获取。爬虫这个领域技术本身是中性的但用在什么场景、怎么用决定了它安不安全。我自己写这类脚本只会做有限量级的数据采集拿到结果做统计分析就停不会把数据再打包出售也不会做商业推荐之类的衍生用途。这种边界感比任何技术技巧都重要。尤其是租房数据这种涉及大量个人生活信息的列表哪怕字段是公开展示的也要谨慎使用。最后分享两个小习惯。第一每次跑完把 CSV 按日期归档类似zufang_20250101.csv连续积累几周就能对比同一商圈的租金变化甚至能发现某些小区的挂牌价波动第二把脚本参数化城市、最大页数、延时范围都用命令行参数传换个城市不用改代码。爬虫写到能稳定出数据只是开始能坚持积累数据并从中看出规律才算真正玩明白了。本文还有配套的精品资源点击获取