尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python爬虫实战:链家租房数据采集与清洗

Python爬虫实战:链家租房数据采集与清洗 简介面向Python爬虫入门与进阶学习者这份资源以链家网租房数据为实战场景演示从分析页面结构、发送请求、解析HTML到清洗并保存数据的完整流程。包内共8个文件以5个py源码和3个pyc编译文件为主整体仅4KB代码精炼便于快速拆解与复用。已有6663人学习下载。资源涵盖主爬虫脚本、登录模块、异常URL处理与文件保存等关键部分可系统学习requests网络请求、BeautifulSoup页面解析、分页遍历、异常处理及反爬延时等实用技巧同时提供pandas与CSV存储思路为后续房价趋势分析和区域热度对比等数据探索打下基础。整体结构清晰适合希望快速掌握Python爬虫落地流程并积累真实站点抓取经验的开发者。 链家租房数据是我个人很推荐的Python爬虫练手项目。原因很简单页面结构规整、数据密度高、反爬强度适中既不会让你在入门阶段就被各种加密参数劝退也不会轻松到毫无收获。很多朋友问我“第一个爬虫项目写什么”我一般都会说别去折腾那些需要登录、需要逆向的网站先把链家这种静态列表页吃透你的Requests、BeautifulSoup、数据清洗、CSV存储这些基本功就全练到位了。这篇文章我直接把完整的实现思路和代码拆开讲从页面分析到最终保存数据每一步都说明白“为什么这么做”。适合已经懂一点Python基础语法、想通过实战项目进阶的读者也适合想采集城市租房数据做分析的朋友参考。1. 动手之前先想清楚爬虫项目的设计与合规1.1 为什么选链家租房数据链家网是国内头部的房产信息平台它的租房频道zufang有一个很典型的特征房源以列表页形式展示每页固定30条数据每条数据包含标题、户型、面积、朝向、租赁方式、楼层、小区名称、位置、关注人数、发布时间、价格等字段。这种结构对爬虫非常友好而且数据覆盖面广按区域、价格、户型都能筛选很适合做城市租房市场的数据分析。从学习角度来说链家这类网站比那些纯静态网页更有挑战性但又没到让你寸步难行的程度。它会校验User-Agent会有反爬检测偶尔要求你过验证码但不会像电商平台那样搞滑块、搞字体反爬、搞参数加密。拿它练手你能真实体验到“爬虫和反爬的攻防博弈”但不会被劝退。1.2 技术选型与合规底线先说技术栈。我选用的是Python 3加上Requests、BeautifulSoup4、lxml这三件套存储直接落到CSV文件。这套组合是爬虫入门的标准配置Requests负责请求页面BeautifulSoup负责解析HTMLlxml是解析器引擎比Python自带的html.parser快不少。如果你后面想进一步做数据分析CSV可以直接用pandas读入非常顺滑。需要提前说明的是爬虫一定要守住合规底线。链家有robots.txt明确限制了部分路径的爬取规则而且租房数据本身受版权保护。我这里讨论的爬取仅限于个人学习、技术研究、少量数据试采绝对不要用于商业用途也不要去高频请求、不要恶意冲击对方服务器。实践中我个人的节奏是请求间隔至少2到5秒随机延迟单次运行采集几十页就停绝不长时间循环抓取。这既是法律意识的体现也是避免IP被封锁的自我保护。2. 解析数据结构看清页面再写代码2.1 页面结构分析写爬虫最忌讳上来就写代码。你先得打开链家租房页面按F12打开开发者工具把页面结构看清楚。我这里用Chrome的检查功能定位到房源列表的每个卡片元素。链家租房列表页的每个房源卡片外层是一个divclass属性包含content__list--item。卡片里包含的信息从内到外大概是标题在div.title下的a标签里是房源的核心卖点描述元信息p.content__list--item--des下有一串span和i标签依次是租赁方式、户型、面积、朝向、楼层等价格span.content__list--item-price里的em标签单位是元/月其他信息关注人数、发布时间在下方的小字里标签是span.content__list--item--time链家的分页逻辑也很有规律首页URL是https://xx.lianjia.com/zufang/第n页是https://xx.lianjia.com/zufang/pg{n}/。比如北京租房第2页就是https://bj.lianjia.com/zufang/pg2/。这里xx是城市拼音缩写bj代表北京sh代表上海sz代表深圳不同城市替换一下就通用。2.2 字段设计与数据处理逻辑看清楚了页面我们再设计数据字段。结合租房数据分析的常见需求我保留了以下字段字段名来源说明title标题标签房源卖点描述rental_type元信息第1个span整租/合租house_type元信息第2个span几室几厅area元信息第3个span面积单位平米direction元信息第4个span朝向floor元信息第5个span所在楼层community小区名称从元信息中提取location位置信息商圈/地铁站price价格em元/月focus_num关注人数热度指标release_time发布时间判断房源新鲜度这里有一个值得注意的细节链家页面上的面积是“80㎡”这种格式价格是“6500”这种纯数字如果后续要做数据分析最好在清洗阶段直接转成数值类型。还有标题里偶尔会带“已下架”或者“新上”这样的标签抓取的时候要过滤掉。3. 核心代码实现从请求到落盘3.1 请求模块伪装请求头与随机延迟请求模块是整个爬虫的地基。链家对User-Agent的校验比较严格如果你用Python默认的UA去请求大概率直接返回403。我们必须在请求头里带上浏览器的完整信息模拟真实用户访问。import requests import time import random from bs4 import BeautifulSoup 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: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Referer: https://www.lianjia.com/, Connection: keep-alive, } def get_page(url, sessionNone, retry3): 获取页面HTML带重试机制 if session is None: session requests.Session() for attempt in range(retry): try: resp session.get(url, headersHEADERS, timeout10) if resp.status_code 200: return resp.text elif resp.status_code 403: print(f[警告] 第{attempt1}次请求被拒绝: {url}) else: print(f[警告] 状态码异常: {resp.status_code}URL: {url}) except requests.exceptions.RequestException as e: print(f[错误] 请求异常: {e}) time.sleep(random.uniform(2, 5)) return None这里我做了几个细节处理第一请求头带全了Accept、Referer这些字段实践证明只带User-Agent还是不够稳第二每次请求之间随机睡眠2到5秒降低请求频率特征第三加了最多3次重试遇到网络抖动或临时封禁可以自动恢复。3.2 解析模块使用BeautifulSoup提取房源信息拿到HTML之后就是解析环节。BeautifulSoup的find_all配合CSS选择器可以高效定位元素。我写了专门的解析函数输入是HTML字符串输出是房源数据字典列表。def parse_house_list(html): 解析链家租房列表页提取房源信息 soup BeautifulSoup(html, lxml) items soup.find_all(div, class_content__list--item) houses [] for item in items: try: # 标题 title_tag item.find(div, class_title).find(a) title title_tag.get_text(stripTrue) if title_tag else if 已下架 in title: continue # 元信息 des_tag item.find(p, class_content__list--item--des) des_spans des_tag.find_all(span) if des_tag else [] info_texts [span.get_text(stripTrue) for span in des_spans] # 价格 price_tag item.find(span, class_content__list--item-price).find(em) price price_tag.get_text(stripTrue) if price_tag else 0 # 关注人数与发布时间 time_tag item.find(span, class_content__list--item--time) time_text time_tag.get_text(stripTrue) if time_tag else focus_num time_text.split(/)[0].replace(人关注, ).strip() if / in time_text else 0 release_time time_text.split(/)[-1].strip() if / in time_text else time_text house { title: title, rental_type: info_texts[0] if len(info_texts) 0 else , house_type: info_texts[1] if len(info_texts) 1 else , area: info_texts[2].replace(㎡, ).strip() if len(info_texts) 2 else , direction: info_texts[3] if len(info_texts) 3 else , floor: info_texts[4] if len(info_texts) 4 else , community: info_texts[5] if len(info_texts) 5 else , location: info_texts[6] if len(info_texts) 6 else , price: price, focus_num: focus_num, release_time: release_time, } houses.append(house) except Exception as e: print(f[跳过] 单条数据解析失败: {e}) continue return houses这个解析函数我用了严格的防御性写法每一步都判断标签是否存在索引越界就用空字符串兜底。这是因为在实际爬取中偶尔会遇到某个房源缺字段的情况如果不在解析层做好防御一条脏数据就能让整个程序崩溃。3.3 数据清洗与CSV存储解析出来的数据不能直接落盘因为页面上的“80㎡”带单位“5人关注”带文字价格字段也可能是“6500元/月”。我做了一个简单的清洗函数把数字统一处理成纯数值类型。import re def clean_value(value): 清洗数据提取纯数值 if not value: return 0 match re.search(r\d, str(value)) return match.group() if match else 0 def save_to_csv(houses, filenamelianjia_zufang.csv): 保存数据到CSV文件使用utf-8-sig编码避免Excel中文乱码 import csv if not houses: print(没有数据可保存) return fieldnames list(houses[0].keys()) with open(filename, w, newline, encodingutf-8-sig) as f: writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() writer.writerows(houses) print(f已保存 {len(houses)} 条数据到 {filename})这里必须提一下编码问题。CSV文件如果用utf-8保存Excel直接打开会中文乱码。解决方案就是用utf-8-sig编码它会自动加上BOM头Excel就能正确识别了。这个坑我一开始踩过后来凡是写CSV都用utf-8-sig再没出过问题。3.4 分页与全流程串联列表页的URL规律是pg{n}所以主循环逻辑非常简单遍历页码依次抓取、解析、清洗、累积最后统一保存。我加了错误退出机制——如果连续3页都没有数据就主动停止避免被封锁后还在空转。def main(citybj, max_pages10): 主函数抓取链家租房数据 session requests.Session() all_houses [] empty_count 0 for page in range(1, max_pages 1): if page 1: url fhttps://{city}.lianjia.com/zufang/ else: url fhttps://{city}.lianjia.com/zufang/pg{page}/ print(f[抓取] 第{page}页: {url}) html get_page(url, sessionsession) if html is None: empty_count 1 if empty_count 3: print([停止] 连续3次获取失败停止抓取) break continue houses parse_house_list(html) print(f[解析] 第{page}页获取到{len(houses)}条房源) if not houses: empty_count 1 if empty_count 3: print([停止] 连续3页无数据可能被反爬停止抓取) break continue empty_count 0 all_houses.extend(houses) time.sleep(random.uniform(2, 5)) save_to_csv(all_houses) print(f任务完成共获取 {len(all_houses)} 条房源数据) if __name__ __main__: main(citybj, max_pages10)我实测跑10页大概能拿到280到300条数据因为链家页面偶尔会有些位置被广告占位实际房源数可能略少于30。整个流程跑下来大概需要1到2分钟节奏很舒服。4. 常见问题与反爬实战4.1 请求返回403请求头伪装与Cookie处理403是爬虫新手最常见的报错。出现403九成是请求头出了问题。我遇到过三种情况第一User-Agent太老或者太简单被服务器识别为非浏览器请求第二缺少Referer字段链家会校验请求来源第三请求频率过高触发了频率限制。应对方案是在请求头里补全浏览器信息同时使用requests.Session()保持会话。Session有两个好处自动管理Cookie第二次及以后的请求会带上首次请求时服务器下发的Cookie连接复用减少TCP握手次数降低被识别为脚本的风险。4.2 页面能打开但解析不到数据有朋友遇到过这样的情况请求返回200但是解析结果为空。这个问题的根源往往是页面结构变了或者请求到的根本不是房源列表页。链家偶尔会改版class名可能调整也可能是你请求到了登录页或者验证码页面。排查方法是把返回的HTML保存到本地文件用浏览器打开看看。我习惯在解析前加一句with open(debug.html, w, encodingutf-8) as f: f.write(html)请求一次就把页面存下来肉眼检查是哪个环节出了问题。如果发现页面变成了验证码页说明IP已经被临时标记这时候立即停止程序等一段时间再跑。4.3 数据字段缺失或错位房源列表页里有几条数据字段特别少比如只有“整租·2室1厅·80㎡”但没有朝向和楼层。这种情况是因为页面本身信息缺失不是解析代码的问题。我的策略是解析时用if len(info_texts) n做判断缺的字段填空字符串保证数据结构完整。4.4 关于“被封IP”的正确应对很多人的第一个反应是上代理IP池我建议新手阶段别碰这个。代理IP质量参差不齐便宜的慢而且不稳定反而干扰你排查问题。更务实的做法是控制抓取频率、控制总量、随机延迟。链家这种面对普通用户的反爬策略只要你像一个正常用户在浏览页面被封锁的概率就非常低。如果真的遇到IP被临时限制最有效的做法是停止抓取等15到30分钟再继续或者换一个网络环境。4.5 常见问题速查表现象可能原因解决方案请求返回403请求头不完整补全User-Agent、Referer等字段返回200但解析为空页面结构变化或拿到验证码页保存HTML到本地人工排查连续几页房源数量为0IP已被临时标记停止程序间隔一段时间再跑CSV用Excel打开乱码编码不是utf-8-sig改用encodingutf-8-sig写入单条数据字段错位页面本身缺字段用索引边界判断做防护5. 写在最后一点个人实操体会这套代码我前后改过三版。第一版只有基础的请求和解析跑十几页就断第二版加了重试和随机延迟稳定性好了很多第三版补上了防御性解析和错误处理基本能一口气跑完几十页不中断。每一步踩坑都是实打实的经验积累这也是我推荐新手做这个项目的原因——你能在短时间内体会到爬虫开发的完整链路。最后再分享一个很实用的小技巧链家租房URL里的城市拼音可以替换成任意城市bj是北京、sh是上海、sz是深圳、cd是成都、hz是杭州。你只要换一个参数就能采集不同城市的租房数据做对比分析。如果想做更细致的分析比如某个商圈的平均租金、户型分布、面积和价格的关系把CSV读入pandas就能直接开搞。顺着这个方向往下走你就不只是在练爬虫而是把数据采集和数据分析串成了一条完整的流水线。本文还有配套的精品资源点击获取
返回列表