一、技术选型与核心原理
1.1 技术栈解析
- Selenium:自动化测试工具,可模拟浏览器行为加载动态页面,解决 JavaScript 渲染的数据获取问题,本文使用 ChromeDriver 作为浏览器驱动。
- BeautifulSoup:Python HTML/XML 解析库,支持 CSS 选择器、正则匹配,能快速提取结构化数据,相比 XPath 更简洁易维护。
- Python :核心开发语言,生态丰富,结合
<font style="color:rgb(34, 34, 34);">requests</font>(备用)、<font style="color:rgb(34, 34, 34);">time</font>(延时)、<font style="color:rgb(34, 34, 34);">csv</font>(数据存储)等库完成全流程。
1.2 核心原理
链家房源列表页与详情页的核心数据(如价格、面积、户型)通过 AJAX 异步加载,静态请求(如 requests.get)只能获取空壳 HTML。Selenium 通过驱动真实浏览器,等待页面完全渲染后获取完整 DOM 结构,再交由 BeautifulSoup 解析提取目标数据,实现动态数据的抓取。
二、环境搭建
2.1览器驱动配置
python
python
import chromedriver_autoinstaller
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
# 自动安装并配置ChromeDriver
chromedriver_autoinstaller.install()
# 配置Chrome选项,提升爬虫稳定性
chrome_options = Options()
# 无头模式,不显示浏览器窗口(调试时可注释)
chrome_options.add_argument('--headless=new')
# 禁用图片加载,提升速度
chrome_options.add_argument('--blink-settings=imagesEnabled=false')
# 禁用GPU加速
chrome_options.add_argument('--disable-gpu')
# 设置用户代理,模拟真实浏览器
chrome_options.add_argument('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')
# 初始化浏览器驱动
driver = webdriver.Chrome(options=chrome_options)
# 设置页面加载超时时间
driver.set_page_load_timeout(30)
三、爬虫核心实现
3.1 页面访问与等待策略
链家页面加载存在延迟,需使用 Selenium 的显式等待确保数据加载完成:
python
python
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
def get_lianjia_page(url):
"""
访问链家页面,等待核心元素加载
:param url: 目标页面URL
:return: 页面完整HTML源码
"""
try:
driver.get(url)
# 等待房源列表加载(核心元素:房源卡片)
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "sellListContent"))
)
# 模拟滚动加载更多(链家列表页滚动触底加载)
for _ in range(3):
# 滚动到页面底部
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2) # 等待异步加载
return driver.page_source
except Exception as e:
print(f"页面访问失败:{e}")
return None
3.2 数据解析(BeautifulSoup 核心)
以链家北京二手房列表页为例,提取房源标题、总价、单价、户型、面积等核心字段:
python
python
from bs4 import BeautifulSoup
import pandas as pd
def parse_lianjia_data(html):
"""
解析链家房源数据
:param html: 页面HTML源码
:return: 结构化房源数据列表
"""
soup = BeautifulSoup(html, 'lxml') # lxml解析器效率更高
house_list = soup.find('ul', class_='sellListContent').find_all('li', class_='clear')
data = []
for house in house_list:
try:
# 房源标题
title = house.find('a', class_='VIEWDATA CLICKDATA maidian-detail').get_text().strip()
# 房源总价(万)
total_price = house.find('div', class_='totalPrice').span.get_text().strip()
# 房源单价(元/㎡)
unit_price = house.find('div', class_='unitPrice').span.get_text().replace('元/㎡', '').strip()
# 户型、面积、朝向等信息
house_info = house.find('div', class_='houseInfo').get_text().strip().split('|')
layout = house_info[0].strip() if len(house_info) > 0 else '' # 户型
area = house_info[1].replace('㎡', '').strip() if len(house_info) > 1 else '' # 面积
orientation = house_info[2].strip() if len(house_info) > 2 else '' # 朝向
# 小区名称
community = house.find('div', class_='positionInfo').a.get_text().strip()
data.append({
'标题': title,
'总价(万)': total_price,
'单价(元/㎡)': unit_price,
'户型': layout,
'面积(㎡)': area,
'朝向': orientation,
'小区': community
})
except Exception as e:
print(f"单条房源解析失败:{e}")
continue
return data
# 测试解析函数
if __name__ == '__main__':
# 链家北京二手房列表页(可替换为其他城市)
target_url = 'https://bj.lianjia.com/ershoufang/'
html = get_lianjia_page(target_url)
if html:
house_data = parse_lianjia_data(html)
# 转换为DataFrame,方便后续分析
df = pd.DataFrame(house_data)
# 保存为CSV文件
df.to_csv('lianjia_beijing_ershoufang.csv', index=False, encoding='utf-8-sig')
print(f"共抓取{len(df)}条房源数据,已保存至lianjia_beijing_ershoufang.csv")
3.3 分页爬取实现
链家列表页采用分页机制,需遍历多页数据:
python
python
def crawl_lianjia_pagination(base_url, pages=5):
"""
分页爬取链家房源数据
:param base_url: 基础URL(如https://bj.lianjia.com/ershoufang/)
:param pages: 爬取页数
:return: 所有页面的房源数据
"""
all_data = []
for page in range(1, pages + 1):
print(f"正在爬取第{page}页...")
# 构造分页URL
page_url = f"{base_url}pg{page}/"
html = get_lianjia_page(page_url)
if not html:
continue
page_data = parse_lianjia_data(html)
all_data.extend(page_data)
# 随机延时,降低反爬风险
time.sleep(pd.np.random.uniform(3, 5))
return all_data
# 调用分页爬取
if __name__ == '__main__':
base_url = 'https://bj.lianjia.com/ershoufang/'
all_house_data = crawl_lianjia_pagination(base_url, pages=5)
df = pd.DataFrame(all_house_data)
df.to_csv('lianjia_beijing_5pages.csv', index=False, encoding='utf-8-sig')
print(f"分页爬取完成,共获取{len(df)}条数据")
四、反爬策略应对
4.1 核心反爬措施
- 请求频率控制:设置随机延时(3-5 秒),避免固定间隔触发反爬;
- 用户代理轮换:构建 UA 池,每次请求随机选择;
- Cookie 保持:Selenium 默认保持浏览器 Cookie,模拟真实用户会话;
- 避免高频操作:禁止短时间内快速滚动、点击,模拟人类操作节奏;
- IP 代理(可选):若爬取量较大,可结合 IP 代理池(如 亿牛云隧道代理)避免 IP 封禁。
五、合规性与风险提示
- 遵守 robots 协议 :链家
<font style="color:rgb(34, 34, 34);">robots.txt</font>限制爬虫抓取,需谨慎评估使用场景,仅用于学习和非商业研究; - 避免过度爬取:高频爬取可能导致 IP 封禁,建议控制爬取频率和总量;
- 数据使用规范:抓取的房源数据仅可用于个人学习,禁止商用或违规传播;
- 法律风险:未经授权抓取网站数据可能违反《网络安全法》,需确保符合当地法律法规。
六、优化与扩展方向
- 异步优化 :结合
<font style="color:rgb(34, 34, 34);">asyncio</font>+<font style="color:rgb(34, 34, 34);">aiohttp</font>(静态部分)+ Selenium(动态部分)提升爬取效率; - 数据清洗:增加数据校验逻辑,处理缺失值、异常值(如单价为 0 的情况);
- 可视化分析:利用 Matplotlib/Seaborn 对抓取的房源数据进行价格分布、户型占比等分析;
- 定时爬取 :结合
<font style="color:rgb(34, 34, 34);">APScheduler</font>实现定时抓取,监控房源价格变化; - 异常重试:增加失败重试机制,提升爬虫稳定性。
七、完整代码汇总
python
运行
python
import chromedriver_autoinstaller
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from bs4 import BeautifulSoup
import pandas as pd
import time
import random
# 自动安装ChromeDriver
chromedriver_autoinstaller.install()
# 配置Chrome选项
def init_driver():
chrome_options = Options()
chrome_options.add_argument('--headless=new')
chrome_options.add_argument('--blink-settings=imagesEnabled=false')
chrome_options.add_argument('--disable-gpu')
# 随机UA
USER_AGENTS = [
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
]
chrome_options.add_argument(f'user-agent={random.choice(USER_AGENTS)}')
driver = webdriver.Chrome(options=chrome_options)
driver.set_page_load_timeout(30)
return driver
# 访问页面
def get_lianjia_page(driver, url):
try:
driver.get(url)
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, "sellListContent"))
)
for _ in range(3):
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2)
return driver.page_source
except Exception as e:
print(f"页面访问失败:{e}")
return None
# 解析数据
def parse_lianjia_data(html):
soup = BeautifulSoup(html, 'lxml')
house_list = soup.find('ul', class_='sellListContent').find_all('li', class_='clear')
data = []
for house in house_list:
try:
title = house.find('a', class_='VIEWDATA CLICKDATA maidian-detail').get_text().strip()
total_price = house.find('div', class_='totalPrice').span.get_text().strip()
unit_price = house.find('div', class_='unitPrice').span.get_text().replace('元/㎡', '').strip()
house_info = house.find('div', class_='houseInfo').get_text().strip().split('|')
layout = house_info[0].strip() if len(house_info) > 0 else ''
area = house_info[1].replace('㎡', '').strip() if len(house_info) > 1 else ''
orientation = house_info[2].strip() if len(house_info) > 2 else ''
community = house.find('div', class_='positionInfo').a.get_text().strip()
data.append({
'标题': title,
'总价(万)': total_price,
'单价(元/㎡)': unit_price,
'户型': layout,
'面积(㎡)': area,
'朝向': orientation,
'小区': community
})
except Exception as e:
print(f"单条房源解析失败:{e}")
continue
return data
# 分页爬取
def crawl_lianjia_pagination(driver, base_url, pages=5):
all_data = []
for page in range(1, pages + 1):
print(f"正在爬取第{page}页...")
page_url = f"{base_url}pg{page}/"
html = get_lianjia_page(driver, page_url)
if not html:
continue
page_data = parse_lianjia_data(html)
all_data.extend(page_data)
time.sleep(random.uniform(3, 5))
return all_data
# 主函数
if __name__ == '__main__':
# 初始化驱动
driver = init_driver()
# 目标URL(北京二手房)
base_url = 'https://bj.lianjia.com/ershoufang/'
# 爬取5页数据
all_house_data = crawl_lianjia_pagination(driver, base_url, pages=5)
# 保存数据
df = pd.DataFrame(all_house_data)
df.to_csv('lianjia_beijing_ershoufang.csv', index=False, encoding='utf-8-sig')
print(f"爬取完成,共获取{len(df)}条房源数据")
# 关闭驱动
driver.quit()
总结
本文通过 Selenium 解决链家动态页面渲染问题,结合 BeautifulSoup 实现高效数据解析,构建了一套完整的动态爬虫方案。核心亮点在于:通过显式等待确保数据加载、模拟人类操作降低反爬风险、分页爬取实现批量数据获取,同时兼顾了代码的可维护性与扩展性。需要注意的是,爬虫的使用必须遵守法律法规和网站规则,建议仅用于技术学习和非商业研究。在此基础上,可进一步优化爬取效率、完善数据清洗逻辑,或结合数据分析工具挖掘房源数据的商业价值。