1. 爬虫开发中的两段式采集模式解析
在真实爬虫项目中,约90%的案例都采用"列表页→详情页"的两段式采集架构。这种模式完美契合了现代网站的内容组织形式——列表页提供条目索引,详情页承载完整内容。以电商平台为例,商品列表页通常只展示价格、标题和缩略图等基础信息,而商品详情页则包含规格参数、用户评价等深度数据。
两段式采集的核心优势在于:
- 资源利用率高:先轻量级抓取列表页获取URL集合,再针对性爬取详情页
- 容错性强:单个详情页采集失败不影响整体任务
- 结构清晰:符合人类浏览习惯,代码可维护性好
重要提示:实际开发中务必遵守robots.txt协议,控制请求频率(建议间隔2秒以上),避免对目标服务器造成负担。
2. 列表页解析与URL提取实战
2.1 列表页结构分析技巧
使用Chrome开发者工具(F12)观察目标网站:
python复制import requests
from bs4 import BeautifulSoup
url = "https://example.com/products"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, 'html.parser')
常见列表元素定位方式:
- CSS选择器:
soup.select('div.product-item > a') - XPath:
//div[@class="product-list"]/a/@href(需lxml库支持) - 正则表达式:适合处理JavaScript动态生成的URL
2.2 URL规范化处理
采集到的URL往往需要补全处理:
python复制from urllib.parse import urljoin
base_url = "https://example.com"
relative_urls = [a['href'] for a in soup.select('a.product-link')]
absolute_urls = [urljoin(base_url, url) for url in relative_urls]
# 去重处理
unique_urls = list(set(absolute_urls))
典型问题处理:
- 分页逻辑识别:观察URL参数规律或寻找"下一页"按钮
- AJAX动态加载:使用Selenium或分析XHR请求
- 反爬机制应对:随机User-Agent、代理IP轮换
3. 详情页数据提取高阶技巧
3.1 结构化数据提取方案
针对详情页的三种解析策略:
| 解析方式 | 适用场景 | 示例代码 |
|---|---|---|
| CSS选择器 | 传统静态页面 | soup.select('div.price::text') |
| XPath | 复杂嵌套结构 | //meta[@property="og:price"]/@content |
| 正则表达式 | 非标准格式文本 | re.search(r'¥(\d+\.\d{2})', text) |
3.2 数据清洗与存储
典型数据处理流程:
python复制def clean_price(price_str):
return float(price_str.replace('¥', '').strip())
product_data = {
'title': soup.find('h1').get_text().strip(),
'price': clean_price(soup.select_one('.price').text),
'description': soup.select('.product-desc p')[0].text,
'sku': re.search(r'ID:(\w+)', str(soup)).group(1)
}
# 存储示例(JSON格式)
import json
with open('products.json', 'a', encoding='utf-8') as f:
json.dump(product_data, f, ensure_ascii=False)
f.write('\n') # 每行一个JSON对象
4. 工程化实践与性能优化
4.1 请求会话管理
使用Session对象保持连接:
python复制session = requests.Session()
session.headers.update({'User-Agent': 'Mozilla/5.0'})
# 自动处理cookies
login_page = session.get(login_url)
session.post(login_url, data={'user': 'name', 'pass': 'word'})
4.2 异步采集加速
concurrent.futures基础实现:
python复制from concurrent.futures import ThreadPoolExecutor
def fetch_detail(url):
try:
resp = session.get(url, timeout=10)
return parse_detail(resp.text)
except Exception as e:
print(f"Error fetching {url}: {str(e)}")
with ThreadPoolExecutor(max_workers=5) as executor:
results = list(executor.map(fetch_detail, detail_urls))
4.3 反爬对抗策略
常见防御措施应对方案:
| 反爬类型 | 解决方案 | 实现要点 |
|---|---|---|
| User-Agent检测 | 轮换UA池 | 准备50+常见UA字符串 |
| IP频率限制 | 代理IP池 | 付费API或自建代理服务器 |
| 验证码 | 识别服务/人工打码 | 对接第三方打码平台 |
| 行为指纹 | 模拟鼠标移动 | 使用selenium-webdriver |
5. 异常处理与日志系统
5.1 健壮性增强方案
python复制import logging
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
logging.basicConfig(
filename='crawler.log',
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[500, 502, 503, 504]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)
5.2 断点续采实现
使用SQLite记录采集状态:
python复制import sqlite3
def init_db():
conn = sqlite3.connect('crawl_state.db')
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS progress
(url TEXT PRIMARY KEY, status TEXT)''')
conn.commit()
return conn
def save_progress(conn, url, status='done'):
conn.cursor().execute(
"INSERT OR REPLACE INTO progress VALUES (?, ?)",
(url, status)
)
conn.commit()
6. 项目实战:图书商城爬虫
6.1 目标分析
以某图书网站为例:
- 列表页URL:
/category/1?page={page} - 详情页包含:书名、ISBN、作者、定价、折扣价、库存状态
- 特殊要求:需要处理星级评分(SVG图标)
6.2 核心代码实现
评分解析示例:
python复制def parse_rating(soup):
style = soup.select('.rating-stars div')[0]['style']
return int(re.search(r'width: (\d+)%', style).group(1)) / 20
完整采集流程:
python复制def crawl_book_site():
conn = init_db()
session = create_session()
for page in range(1, 6):
list_url = f"https://bookstore.com/category/1?page={page}"
try:
detail_urls = extract_detail_urls(session.get(list_url).text)
for url in detail_urls:
if not check_crawled(conn, url):
data = fetch_book_detail(session, url)
save_to_database(data)
save_progress(conn, url)
except Exception as e:
logging.error(f"Page {page} error: {str(e)}")
conn.close()
7. 常见问题排错指南
7.1 高频错误解决方案
| 错误现象 | 可能原因 | 排查方法 |
|---|---|---|
| 返回403状态码 | 被服务器屏蔽 | 检查Headers完整性,添加Referer |
| 提取数据为空 | 页面动态加载 | 查看网页源码确认元素是否存在 |
| 连接超时 | 网络问题/反爬 | 测试直接浏览器访问目标URL |
| 编码错误 | 响应头缺失charset | 手动指定response.encoding='utf-8' |
7.2 调试技巧
使用中间变量检查:
python复制print(f"Response status: {response.status_code}")
with open('debug.html', 'w', encoding='utf-8') as f:
f.write(response.text)
浏览器对比验证:
javascript复制// 在开发者工具Console中测试选择器
document.querySelectorAll('div.product-title')
8. 进阶路线建议
掌握基础两段式采集后,可进一步研究:
- Scrapy框架搭建分布式爬虫
- 使用Splash处理JavaScript渲染
- 基于机器学习的验证码识别
- 增量采集与数据去重方案
- 爬虫监控与告警系统建设
实际项目中建议从简单目标开始,逐步增加复杂度。我的经验是先用单线程实现核心逻辑,再逐步引入并发、缓存等优化措施,这样更容易定位问题。记得始终遵循"采集有度"的原则,控制请求频率在合理范围内。