Python爬虫实战:链家租房数据采集与市场分析 简介本资源是一套面向房地产数据分析初学者与行业研究者的Python链家二手房及租房数据爬虫实战源码解决房产市场信息采集效率低、结构化难的问题适用于市场调研、投资分析、教学实践等场景。压缩包共20个文件含8个核心Python脚本如core.py主控逻辑、woaiwojialib.py专用于链家反爬适配、scrawl.py数据抓取模块、2个CSV数据文件houseinfo.csv与community.csv、2个PNG可视化图示、2个文本配置/日志文件以及Jupyter笔记本lianjia.ipynb用于交互式分析演示整体大小10.85MB。已有704人学习下载。读者可直接运行完整爬虫流程获得结构化房源与小区数据配套notebook提供清洗、统计与可视化全流程代码目录按功能分层清晰含data、jupyter、lib等逻辑子目录并附LICENSE开源许可与README说明降低二次开发门槛。1. 项目缘起为什么我们需要一个链家租房数据爬虫最近在帮一个朋友分析租房市场他打算换个离公司近点的地方但预算又有限。我们俩对着链家网站刷了整整一个周末眼睛都快看花了筛选条件调来调去价格、面积、朝向、楼层……信息太零散了根本没法做横向对比。更头疼的是今天看中的房子明天可能就下架或者涨价了信息变动太快手动记录效率极低。那一刻我就想要是能有个工具自动把特定区域的房源信息抓下来整理成表格还能定时更新那该多省事。这就是我做这个链家二手房租房数据爬虫的初衷。它不是什么高深莫测的AI项目而是一个非常实用的“数据采集器”。核心目标很简单自动化、批量化地从链家官网获取公开的租房房源信息并将其结构化存储为后续的市场分析、价格监控、房源筛选提供数据基础。无论是个人找房想做个历史价格曲线还是做租房市场的小型研究这个工具都能帮你把繁琐的信息收集工作自动化。你可能听过很多关于Python爬虫的教程但很多要么过于理论化要么就是拿一些简单的、没有反爬措施的静态网站练手。链家作为国内最大的房产信息平台之一其页面结构和反爬机制都具有一定的代表性。搞定它你就能掌握处理动态加载、应对基础反爬策略、解析复杂页面结构等一系列实战技能。这个项目会涉及requests库发送网络请求、BeautifulSoup和lxml解析HTML、pandas整理数据以及如何设计稳健的爬虫逻辑来应对网站的变化。接下来我就带你从零开始一步步拆解并实现这个爬虫。2. 核心工具链选型与环境搭建工欲善其事必先利其器。在动手写代码之前选择合适的工具并配置好环境是第一步。这里的选择都是基于“简单、高效、社区支持好”的原则也是我个人在多次爬虫项目后沉淀下来的组合。2.1 Python版本与主要库我强烈推荐使用Python 3.8 或更高版本。3.8之后的版本在异步支持和语法特性上更加成熟稳定。主要依赖以下几个库requests用于发送HTTP请求获取网页源代码。它是同步请求库中的事实标准简单易用。为什么不直接用urllibrequests的API设计更人性化处理Cookie、Session、代理等更方便。BeautifulSoup4(bs4)用于解析HTML或XML文档从复杂的标签结构中提取我们需要的数据。它支持多种解析器对于链家这种结构清晰的页面用起来非常顺手。lxml一个高性能的HTML/XML解析库。BeautifulSoup可以选用lxml作为其解析引擎速度比Python内置的html.parser快很多特别是在处理大量页面时差异明显。pandas数据处理和分析的核心。爬取到的零散数据用pandas的DataFrame来组织、清洗、保存为CSV或Excel文件简直不能再方便。fake-useragent随机生成User-Agent请求头。这是绕过网站基础反爬机制检查请求头最常用、也最必要的手段之一。2.2 环境搭建详细步骤假设你已经在电脑上安装好了Python和pip我们通过命令行来安装这些库。打开你的终端Windows上是CMD或PowerShellMac/Linux上是Terminal。首先创建一个专属的项目目录是个好习惯可以避免库版本冲突。# 创建一个新的项目文件夹并进入 mkdir lianjia_rent_spider cd lianjia_rent_spider接下来使用pip一次性安装所有依赖。我习惯将依赖写在一个requirements.txt文件里方便管理和复现环境。# 创建 requirements.txt 文件并写入内容 echo requests2.28.0 beautifulsoup44.11.0 lxml4.9.0 pandas1.5.0 fake-useragent1.4.0 requirements.txt # 安装所有依赖 pip install -r requirements.txt注意如果你在安装lxml时遇到编译错误特别是在Windows上可能是因为缺少C语言编译环境。一个简单的解决办法是访问 https://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml 这个非官方库下载对应你Python版本和系统位数的.whl文件例如lxml‑4.9.0‑cp38‑cp38‑win_amd64.whl然后通过pip install 文件名.whl进行安装。安装完成后可以创建一个Python文件比如spider.py来测试环境是否正常。# spider.py import requests from bs4 import BeautifulSoup import pandas as pd from fake_useragent import UserAgent print(所有库导入成功) ua UserAgent() print(f随机生成的User-Agent: {ua.random})运行这个脚本如果没有报错并且打印出了一个浏览器样的User-Agent字符串那么你的基础环境就准备好了。2.3 开发工具的选择写Python代码一个好用的编辑器或IDE能极大提升效率。这里有几个推荐VS Code轻量级插件生态丰富。安装Python扩展后代码提示、调试、虚拟环境管理等功能一应俱全。对于爬虫这种项目非常合适。PyCharm专业的Python IDE功能强大开箱即用。社区版免费对于初学者和专业开发者都很好。Jupyter Notebook特别适合做数据分析和探索性爬取。你可以分步骤执行代码实时看到每一步的结果方便调试解析逻辑。我个人在这个项目中使用的是VS Code因为它足够轻快配置也简单。你需要确保在VS Code中选择了正确的Python解释器对应我们刚才安装库的环境。3. 链家租房页面结构与数据定位分析写爬虫就像做外科手术你得先看清楚“器官”的结构和位置。盲目下刀只会得到一堆无用的HTML标签。因此在写任何一行爬取代码之前我们必须先对链家租房页面的结构进行细致的分析。3.1 页面概览与URL规律首先打开链家租房网站例如北京地区https://bj.lianjia.com/zufang/。你会发现页面列出了很多房源并且有分页。我们的目标是爬取列表页中每一个房源条目下的详细信息。关键观察点1URL的构造规律。尝试切换不同区域、价格、房型等筛选条件并观察浏览器地址栏URL的变化。例如https://bj.lianjia.com/zufang/- 北京全部https://bj.lianjia.com/zufang/dongcheng/- 北京东城区https://bj.lianjia.com/zufang/pg2/- 第二页https://bj.lianjia.com/zufang/pg2rt200600000001l1l2/- 第二页租金2000-6000元一室和二室你会发现链家采用了路径/zufang/区域/和查询参数pg2表示页码混合的方式。但更常见的是筛选条件会编码成类似rt200600000001l1l2这样的字符串附加在页码后面。对于初级爬虫我们可以采用一个更简单的策略不直接模拟复杂的筛选URL而是通过遍历页码然后对获取到的列表页进行本地筛选。比如我们先爬下所有房源再用pandas根据价格、房型进行过滤。这样更稳定因为筛选逻辑在我们自己手里不受网站URL规则变化的影响。所以我们爬取的目标URL可以简化为https://bj.lianjia.com/zufang/pg{页码}/。我们需要找出最大页码。3.2 使用开发者工具定位数据这是爬虫最核心的一步。在列表页比如第一页上按F12打开浏览器的开发者工具切换到Elements元素标签页。关键观察点2列表项容器。使用检查工具通常是左上角的箭头图标去点击一个房源条目。你会发现所有房源条目通常都被包裹在一个具有特定class的div标签内。在链家上这个容器很可能是div classcontent__list而里面的每个房源是div classcontent__list--item。记下这个特征。关键观察点3关键信息所在标签。在一个房源条目内继续使用检查工具查看房源标题、价格、面积、朝向、楼层等信息。你需要像侦探一样找到包裹这些信息的HTML标签和其属性如class、标签类型。标题和链接通常在一个a标签里这个链接指向房源的详情页。href属性值就是详情页URL可能是相对路径需要拼接。价格可能在一个em或span标签里class可能是content__list--item-price。面积、房型、朝向、楼层这些信息经常被放在一起用|分隔位于某个p标签内。你需要用字符串方法或正则表达式将它们拆分。关键观察点4分页信息。翻到页面底部找到分页控件。查看“下一页”或页码按钮对应的HTML。最大页码可能在一个a标签的文本里也可能通过>import requests from fake_useragent import UserAgent import time import random def get_page(url, retry_times3, delay2): 发送HTTP GET请求获取页面内容。 参数: url (str): 目标URL。 retry_times (int): 请求失败重试次数默认为3。 delay (int): 请求之间的基础延迟秒数用于礼貌爬取。 返回: str: 成功则返回页面HTML文本失败则返回None。 ua UserAgent() headers { User-Agent: ua.random, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, } # 添加随机延迟模拟人类操作避免请求过快被封IP time.sleep(delay random.uniform(0, 1)) for i in range(retry_times): try: response requests.get(url, headersheaders, timeout10) # 检查HTTP状态码200表示成功 response.raise_for_status() # 检查编码链家通常是utf-8 response.encoding response.apparent_encoding or utf-8 return response.text except requests.exceptions.RequestException as e: print(f第{i1}次请求失败URL: {url}, 错误: {e}) if i retry_times - 1: wait_time delay * (i 1) # 重试等待时间递增 print(f等待{wait_time}秒后重试...) time.sleep(wait_time) else: print(f重试{retry_times}次后仍失败放弃URL: {url}) return None # 测试请求函数 test_url https://bj.lianjia.com/zufang/pg1/ html_content get_page(test_url, delay3) # 首次请求延迟稍长 if html_content: print(f页面获取成功长度{len(html_content)} 字符)代码解析与注意事项fake_useragent每次请求生成随机的User-Agent这是绕过基于请求头识别的最基本措施。time.sleep在请求之间加入随机延迟。这是“礼貌爬虫”的黄金法则。链家这类网站对快速连续的请求非常敏感不加延迟很可能在爬了几页后就被暂时屏蔽。delay参数可以根据实际情况调整抓取速度越慢越安全。retry机制网络请求可能因各种原因失败超时、连接错误等。加入重试逻辑可以提高爬虫的鲁棒性。response.raise_for_status()如果HTTP返回码不是200成功这个方法会抛出一个异常让我们能捕获到请求失败的情况而不是得到一个包含错误信息的页面内容。超时设置timeout10意味着如果服务器10秒内没有响应就放弃这次请求。防止因某个请求卡住而阻塞整个程序。4.2 解析函数从HTML中提取数据拿到HTML后下一步就是用BeautifulSoup解析并提取我们关心的数据。from bs4 import BeautifulSoup def parse_list_page(html): 解析列表页HTML提取当前页所有房源的基本信息。 参数: html (str): 列表页的HTML文本。 返回: list: 一个列表其中每个元素是一个字典代表一个房源的信息。 如果解析失败或没有房源返回空列表。 if not html: return [] soup BeautifulSoup(html, lxml) # 使用lxml解析器速度更快 house_list [] # 1. 找到所有房源条目 # 根据之前分析房源条目在 class 为 ‘content__list--item’ 的 div 标签里 items soup.find_all(div, class_content__list--item) if not items: print(警告未找到房源条目页面结构可能已变化) # 可以在这里打印一部分HTML辅助调试 # print(soup.prettify()[:2000]) return [] for item in items: house_info {} # 2. 提取标题和详情页链接 title_tag item.find(a, class_content__list--item--aside) if title_tag and title_tag.has_attr(href): house_info[title] title_tag.get(title, ).strip() # 链接可能是相对路径需要补全为绝对URL detail_url title_tag[href] if detail_url.startswith(/): house_info[detail_url] fhttps://bj.lianjia.com{detail_url} else: house_info[detail_url] detail_url else: house_info[title] house_info[detail_url] # 3. 提取价格 price_tag item.find(em) if price_tag: house_info[price] price_tag.get_text().strip() else: house_info[price] # 4. 提取房源特征面积、房型、朝向、楼层 # 这些信息通常在一个 class 为 ‘content__list--item--des’ 的 div 里 desc_div item.find(div, class_content__list--item--des) if desc_div: # 获取所有文本然后用换行或斜杠分割 full_desc desc_div.get_text(separator|, stripTrue) # 示例字符串朝阳 | 1室1厅 | 58.66平米 | 南 | 低楼层/共6层 parts [p.strip() for p in full_desc.split(|) if p.strip()] # 根据链家当前格式按顺序分配但更健壮的做法是识别关键词 house_info[area] parts[2] if len(parts) 2 else # 假设第三部分是面积 house_info[layout] parts[1] if len(parts) 1 else # 假设第二部分是房型 house_info[orientation] parts[3] if len(parts) 3 else # 假设第四部分是朝向 house_info[floor] parts[4] if len(parts) 4 else # 假设第五部分是楼层 # 注意这种基于位置的解析很脆弱网站改版就会失效。 # 更好的方法是遍历parts寻找包含‘平米’、‘室’、‘东/南/西/北’、‘层’等关键词的片段。 else: house_info[area] house_info[layout] house_info[orientation] house_info[floor] # 5. 提取小区名称和区域 # 小区和区域信息可能在多个标签里这里尝试查找 region_tag item.find(p, class_content__list--item--des__others) if region_tag: # 文本可能像 “劲松 / 潘家园” region_text region_tag.get_text(stripTrue) house_info[region] region_text else: house_info[region] # 将当前房源信息字典添加到列表 house_list.append(house_info) return house_list # 测试解析函数 if html_content: houses parse_list_page(html_content) print(f本页解析到 {len(houses)} 条房源信息) if houses: # 打印第一条房源信息看看结构 import pprint pprint.pprint(houses[0])代码解析与避坑指南解析器选择BeautifulSoup(html, lxml)这里指定了lxml解析器。确保你已经安装了lxml库否则会回退到速度慢的html.parser。find_all与findfind_all返回所有匹配的标签列表find只返回第一个匹配的标签。根据页面结构灵活选择。属性获取使用tag[href]获取属性值但最好先用tag.has_attr(href)判断是否存在该属性避免报错。使用tag.get(href, )可以提供默认值。文本提取tag.get_text(stripTrue)可以获取标签内所有文本并去除首尾空白。separator参数可以指定子标签文本之间的分隔符非常有用。最脆弱的环节对房源特征面积、房型等的解析。代码中使用了基于位置的假设parts[2]是面积这非常容易因为网站前端微调而失效。更健壮的做法是写一个辅助函数遍历分割后的字符串片段用关键词如‘平米’、‘室’、‘厅’、‘东’、‘南’、‘低楼层’来识别和提取对应信息。这是爬虫维护中最常需要修改的部分。调试技巧当find_all找不到元素时打印一小部分soup.prettify()的输出或者将HTML保存到本地文件查看确认当前的页面结构是否和你分析时一致。4.3 分页逻辑与主循环我们需要知道一共有多少页然后循环爬取每一页。def get_max_page(html): 从列表页HTML中解析出总页数。 参数: html (str): 列表页通常是第一页的HTML文本。 返回: int: 最大页码。如果解析失败返回1。 if not html: return 1 soup BeautifulSoup(html, lxml) # 查找分页组件链家可能放在 class 为 ‘content__pg’ 的 div 里 page_div soup.find(div, class_content__pg) if page_div: # 通常最后一个‘a’标签的‘data-page’属性或文本是总页数 # 也可能是带有‘totalPage’字样的属性 # 这里需要根据实际页面调整 last_page_tag page_div.find_all(a)[-1] if last_page_tag and last_page_tag.has_attr(data-totalpage): max_page int(last_page_tag[data-totalpage]) else: # 如果属性不存在尝试从文本中提取数字 import re text last_page_tag.get_text(stripTrue) numbers re.findall(r\d, text) max_page int(numbers[-1]) if numbers else 1 return max_page else: print(未找到分页信息默认只爬取第一页。) return 1 def main_spider(base_url, start_page1, max_pageNone, delay3): 爬虫主函数控制整个爬取流程。 参数: base_url (str): 列表页的基础URL不包含页码。例如https://bj.lianjia.com/zufang/pg start_page (int): 起始页码。 max_page (int, optional): 最大爬取页码。如果为None则自动从第一页解析。 delay (int): 请求延迟秒数。 返回: list: 爬取到的所有房源信息列表。 all_houses [] # 1. 获取第一页并解析最大页码 first_page_url f{base_url}{start_page}/ print(f正在获取初始页: {first_page_url}) first_page_html get_page(first_page_url, delaydelay) if not first_page_html: print(初始页面获取失败程序退出。) return all_houses if max_page is None: max_page get_max_page(first_page_html) print(f解析到总页数: {max_page}) # 2. 解析第一页的数据 houses_from_first parse_list_page(first_page_html) all_houses.extend(houses_from_first) print(f第{start_page}页爬取完成累计房源数: {len(all_houses)}) # 3. 循环爬取后续页面 for page in range(start_page 1, max_page 1): page_url f{base_url}{page}/ print(f正在获取第{page}页: {page_url}) page_html get_page(page_url, delaydelay) if not page_html: print(f第{page}页获取失败跳过。) continue houses parse_list_page(page_html) if not houses: print(f第{page}页未解析到房源可能已爬完或结构有变停止爬取。) break all_houses.extend(houses) print(f第{page}页爬取完成累计房源数: {len(all_houses)}) # 每爬完5页可以短暂休息一下降低被封风险 if page % 5 0: long_delay delay * 3 print(f已爬取{page}页休息{long_delay}秒...) time.sleep(long_delay) print(f爬取结束总共获取 {len(all_houses)} 条房源信息。) return all_houses # 运行主爬虫 if __name__ __main__: # 以北京租房为例 BASE_URL https://bj.lianjia.com/zufang/pg # 为了演示只爬取前3页。实际使用时可以将 max_page 设为 None 以自动探测总页数。 houses_data main_spider(BASE_URL, start_page1, max_page3, delay4)代码解析与核心逻辑get_max_page函数尝试从分页组件中解析出总页数。这里是最容易出问题的地方之一因为网站分页的实现方式多样直接显示数字、通过JavaScript加载、有“末页”按钮等。代码中提供了两种尝试方式查找>import pandas as pd from datetime import datetime def save_to_csv(data_list, filenameNone): 将房源数据列表保存为CSV文件。 参数: data_list (list): 房源信息字典组成的列表。 filename (str, optional): 输出文件名。如果为None则自动生成带时间戳的文件名。 if not data_list: print(数据为空无需保存。) return df pd.DataFrame(data_list) # 定义理想的列顺序便于阅读 column_order [title, price, area, layout, orientation, floor, region, detail_url] # 只保留data_list中存在的列 existing_columns [col for col in column_order if col in df.columns] # 将其他列放在后面 other_columns [col for col in df.columns if col not in existing_columns] df df[existing_columns other_columns] if filename is None: # 生成包含时间戳的默认文件名 timestamp datetime.now().strftime(%Y%m%d_%H%M%S) filename flianjia_rent_{timestamp}.csv try: df.to_csv(filename, indexFalse, encodingutf-8-sig) # 使用utf-8-sig支持Excel直接打开中文 print(f数据已成功保存至文件: {filename}) print(f数据形状: {df.shape} (行数 x 列数)) # 打印前几行预览 print(\n数据预览:) print(df.head()) except Exception as e: print(f保存文件时出错: {e}) # 在主函数调用后保存数据 if __name__ __main__: houses_data main_spider(BASE_URL, start_page1, max_page3, delay4) save_to_csv(houses_data)代码解析与技巧pd.DataFrame直接将字典列表转换为数据框每个字典的键成为列名值成为行数据。utf-8-sig编码这是为了兼容Windows系统下的Excel。如果直接用utf-8Excel打开时中文可能会乱码。utf-8-sig会在文件开头添加一个BOM头让Excel正确识别编码。indexFalse保存时不将DataFrame的索引写入文件。数据预览保存后打印df.head()和df.shape可以快速确认数据是否正常以及爬取了多少条记录。运行完整的脚本你应该能在项目目录下得到一个名为lianjia_rent_20231027_143022.csv的CSV文件用Excel或文本编辑器打开里面就是结构化的租房数据了。5. 反爬策略应对与爬虫优化如果只是偶尔爬取几页数据上面的代码可能工作得很好。但一旦你尝试大规模、长时间爬取很快就会遇到各种限制和封锁。下面分享几个我实践中总结的应对策略和优化点。5.1 基础反爬手段与破解User-Agent检测我们已经通过fake-useragent库解决了每次请求使用随机的、真实的浏览器UA。请求频率限制这是最普遍的防御。我们的time.sleep延迟是基础。但固定延迟模式容易被识别。更好的做法是随机延迟time.sleep(delay random.uniform(-0.5, 1.5))让间隔时间有波动。模拟人类浏览节奏可以在爬取一定数量页面后插入一个较长的休息时间比如几十秒到几分钟。IP封锁如果你的IP在短时间内发送了过多请求服务器可能会暂时或永久封禁该IP。解决方案使用代理IP池这是应对IP封锁最有效的方法。你可以购买付费代理服务或者寻找免费的代理IP但免费代理通常不稳定、速度慢。在requests.get()中通过proxies参数设置代理。proxies { http: http://your_proxy_ip:port, https: https://your_proxy_ip:port, } response requests.get(url, headersheaders, proxiesproxies, timeout10)降低请求速度这是成本最低的方法但效率也最低。将delay参数设置得足够大比如5-10秒对于小规模爬取可能够用。5.2 会话Session保持使用requests.Session()可以复用TCP连接并在多次请求间保持Cookies有时能提升成功率和速度。import requests from fake_useragent import UserAgent import time session requests.Session() ua UserAgent() def get_page_with_session(url, retry3): headers {User-Agent: ua.random} for i in range(retry): try: # 使用session发起请求 resp session.get(url, headersheaders, timeout10) resp.raise_for_status() resp.encoding resp.apparent_encoding return resp.text except Exception as e: print(f请求失败 (尝试 {i1}/{retry}): {e}) time.sleep(2 * (i1)) return None5.3 解析逻辑的健壮性优化前面提到基于位置的解析很脆弱。我们来优化一下parse_list_page函数中处理房源特征的部分。def extract_house_features(desc_text): 从房源描述字符串中提取面积、房型、朝向、楼层。 使用关键词匹配比基于位置更健壮。 参数: desc_text (str): 例如 “朝阳 | 1室1厅 | 58.66平米 | 南 | 低楼层/共6层” 返回: dict: 包含 area, layout, orientation, floor 的字典。 features {area: , layout: , orientation: , floor: } if not desc_text: return features parts [p.strip() for p in desc_text.split(|) if p.strip()] for part in parts: if 平米 in part: features[area] part elif 室 in part and 厅 in part: features[layout] part # 朝向可能包含东、南、西、北、东南、西南等 elif any(dir_char in part for dir_char in [东, 南, 西, 北]): features[orientation] part elif 层 in part: features[floor] part # 如果上述都没匹配到可以尝试其他逻辑或保留原样 return features # 然后在 parse_list_page 函数中替换原来的基于位置的解析 # if desc_div: # full_desc desc_div.get_text(separator|, stripTrue) # features extract_house_features(full_desc) # house_info.update(features) # 将特征字典合并到房源信息中这种方法即使网站调整了特征的顺序只要关键词还在就能正确提取。当然如果网站连关键词都改了比如把“平米”改成“㎡”那解析函数就需要同步更新。这就是爬虫需要“维护”的原因。5.4 异常处理与日志记录一个工业级的爬虫必须有完善的异常处理和日志记录方便排查问题。import logging # 配置日志 logging.basicConfig( levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(spider.log, encodingutf-8), logging.StreamHandler() # 同时输出到控制台 ] ) logger logging.getLogger(__name__) def get_page_with_logging(url): try: # ... 请求逻辑 ... logger.info(f成功获取页面: {url}) return html except requests.exceptions.Timeout: logger.error(f请求超时: {url}) except requests.exceptions.HTTPError as e: logger.error(fHTTP错误 {e.response.status_code}: {url}) except Exception as e: logger.exception(f获取页面时发生未知错误: {url}) # 会打印堆栈跟踪 return None将print语句替换为logger.info/warning/error所有运行信息都会同时记录在spider.log文件和终端上便于事后分析哪一页失败了、失败原因是什么。6. 从数据爬取到简单分析数据爬下来不是终点而是起点。我们可以用pandas快速进行一些初步分析让数据产生价值。假设我们已经将数据保存为rent_data.csv。import pandas as pd import matplotlib.pyplot as plt # 用于绘图 # 1. 加载数据 df pd.read_csv(rent_data.csv, encodingutf-8-sig) # 2. 数据清洗 # 价格字段可能是字符串“5000”需要转为数字 # 注意有些价格可能带单位如“5000元/月”需要处理 def clean_price(price_str): if isinstance(price_str, str): # 提取数字部分 import re match re.search(r(\d), price_str) if match: return int(match.group(1)) return None df[price_num] df[price].apply(clean_price) # 面积字段清洗提取数字 def clean_area(area_str): if isinstance(area_str, str): import re match re.search(r([\d\.]), area_str) if match: return float(match.group(1)) return None df[area_num] df[area].apply(clean_area) # 3. 基本统计分析 print( 基本统计信息 ) print(f房源总数: {len(df)}) if price_num in df.columns: print(f平均租金: {df[price_num].mean():.2f} 元) print(f租金中位数: {df[price_num].median():.2f} 元) print(f最高租金: {df[price_num].max():.2f} 元) print(f最低租金: {df[price_num].min():.2f} 元) # 按区域统计 if region in df.columns: region_stats df.groupby(region)[price_num].agg([count, mean, median]).round(2) region_stats.columns [房源数量, 平均租金, 租金中位数] print(\n 按区域统计 ) print(region_stats.sort_values(房源数量, ascendingFalse).head(10)) # 展示房源最多的前10区域 # 4. 简单可视化可选 if not df.empty and price_num in df.columns: # 绘制租金分布直方图 plt.figure(figsize(10, 6)) df[price_num].dropna().hist(bins30, edgecolorblack) plt.title(租金分布直方图) plt.xlabel(租金 (元)) plt.ylabel(房源数量) plt.grid(axisy, alpha0.75) plt.tight_layout() plt.savefig(rent_distribution.png) # 保存图片 plt.show() # 计算单价元/平米/月如果面积数据可用 if area_num in df.columns: df[unit_price] df[price_num] / df[area_num] # 找出单价最高和最低的房源 print(\n 单价元/平米/月分析 ) print(单价最高的5套房源:) print(df[[title, region, price_num, area_num, unit_price]].nlargest(5, unit_price)) print(\n单价最低的5套房源:) print(df[[title, region, price_num, area_num, unit_price]].nsmallest(5, unit_price))通过这几步你就能从原始的房源列表中快速得到市场的平均租金水平、热门租房区域、以及哪些房子性价比高单价低等洞察。这比人工一页页翻看效率提升了不止一个数量级。7. 项目总结与扩展思考这个爬虫项目从分析页面开始到数据落地和初步分析完成了一个完整的数据采集流水线。它麻雀虽小五脏俱全涵盖了爬虫最核心的步骤请求、解析、存储、反爬应对。几个关键的踩坑点与心得延迟是美德对商业网站保持礼貌的访问间隔是爬虫能长期稳定运行的第一要素。不要贪快time.sleep是你最好的朋友。解析逻辑要健壮也要易维护不要写死解析规则。尽量使用标签的id或具有唯一性的class来定位。对于文本信息多用split()、find()、正则表达式结合关键词来提取而不是依赖固定的位置索引。将解析关键信息的代码封装成函数这样当网站改版时你只需要修改这个函数而不是满篇找代码。错误处理要周全网络请求可能失败页面结构可能变化数据可能缺失。你的代码必须能优雅地处理这些异常记录日志然后跳过或重试而不是直接崩溃。数据清洗很重要爬下来的原始数据往往是“脏”的包含多余字符、格式不一致等问题。在分析前必须进行清洗和类型转换如字符串价格转数字。这个爬虫还可以如何扩展爬取详情页数据列表页的信息有限。你可以修改爬虫在获取列表后再根据detail_url去爬取每个房源的详情页获取更全面的信息如房屋描述、配套设施、经纪人信息、具体地址、经纬度等。定时任务与监控使用schedule库或操作系统的cron/Task Scheduler让爬虫每天定时运行监控特定区域房源的价格变化和新上架情况。构建数据管道将数据存储到数据库如SQLite、MySQL而非CSV文件便于更复杂的查询和历史对比。增加并发对于大量页面可以使用concurrent.futures模块进行多线程爬取但务必注意控制总体请求速率并妥善处理共享数据。封装成API或工具使用Flask或FastAPI框架将爬虫包装成一个简单的Web服务提供按条件查询租房数据的API。最后请务必记住爬虫技术是一把双刃剑。本项目仅用于学习和技术交流在实际使用中必须严格遵守目标网站的robots.txt协议尊重网站的数据权益控制爬取频率和数据量避免对目标网站的正常运行造成影响。本文还有配套的精品资源点击获取