1. 项目背景与核心价值
企业级数据采集与传统爬虫开发最大的区别在于稳定性和可维护性。我们团队在多个商业项目中验证了Playwright作为新一代采集工具的优势:它不仅支持动态页面渲染,还能模拟真实用户操作行为。以CSDN资讯为例,这个平台采用Vue.js动态加载内容,传统requests+BeautifulSoup方案需要逆向接口,而Playwright可以直接获取渲染后的完整DOM。
这个实战项目包含五个关键模块:
- 配置化管理(不同栏目URL、分页规则可配置)
- 全链路日志(记录每个操作步骤和异常)
- 智能重试机制(网络波动、元素加载失败等情况自动重试)
- 定时任务调度(基于APScheduler实现)
- 详情页正文提取(自适应不同文章模板)
提示:企业级采集要特别注意合规性,建议控制请求频率在5-10秒/次,避免对目标服务器造成压力
2. 环境搭建与基础配置
2.1 依赖安装与初始化
推荐使用Python 3.8+环境,核心依赖包括:
bash复制pip install playwright==1.32.0
playwright install # 自动下载浏览器驱动
pip install apscheduler==3.9.1 loguru==0.7.0
基础配置文件config.yaml示例:
yaml复制csdn:
base_url: "https://blog.csdn.net"
channels:
- name: "AI"
path: "/nav/ai"
- name: "区块链"
path: "/nav/blockchain"
retry:
max_attempts: 3
delay: 5
2.2 日志系统设计
使用loguru实现结构化日志:
python复制from loguru import logger
logger.add(
"runtime_{time}.log",
rotation="100 MB",
retention="10 days",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}"
)
典型日志输出示例:
code复制2023-08-20 14:30:45 | INFO | 开始采集频道:AI
2023-08-20 14:30:50 | WARNING | 元素加载超时,尝试重新定位...
2023-08-20 14:30:52 | SUCCESS | 已采集到15条文章列表
3. 核心采集逻辑实现
3.1 页面导航与列表抓取
使用Playwright的智能等待机制:
python复制async def crawl_list(page, channel):
await page.goto(f"{config['base_url']}{channel['path']}")
# 等待主要内容区域加载
await page.wait_for_selector(
".mainContent",
state="attached",
timeout=10000
)
# 自动滚动加载分页
for _ in range(3):
await page.mouse.wheel(0, 1000)
await page.wait_for_timeout(2000)
articles = await page.query_selector_all(".article-item")
results = []
for article in articles:
title = await article.query_selector("h2")
results.append({
"title": await title.inner_text(),
"url": await article.get_attribute("href")
})
return results
3.2 详情页正文提取策略
CSDN文章正文存在多种模板,需要自适应处理:
python复制async def extract_content(page):
# 尝试多种可能的正文选择器
selectors = [
".blog-content-box",
"#articleContent",
".article_content"
]
for selector in selectors:
if await page.query_selector(selector):
content = await page.evaluate(f"""
el => el.innerText
""", await page.query_selector(selector))
return content.strip()
logger.warning("未找到正文内容")
return None
4. 稳定性增强设计
4.1 智能重试机制
实现指数退避重试策略:
python复制async def with_retry(func, *args, **kwargs):
max_attempts = config['retry']['max_attempts']
base_delay = config['retry']['delay']
for attempt in range(1, max_attempts + 1):
try:
return await func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts:
raise
delay = base_delay * (2 ** (attempt - 1))
logger.warning(f"第{attempt}次尝试失败,{delay}秒后重试...")
await asyncio.sleep(delay)
4.2 浏览器上下文管理
使用上下文管理器确保资源释放:
python复制class PlaywrightManager:
def __init__(self):
self.playwright = None
self.browser = None
async def __aenter__(self):
self.playwright = await async_playwright().start()
self.browser = await self.playwright.chromium.launch(
headless=True,
args=["--disable-blink-features=AutomationControlled"]
)
return self.browser
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.browser.close()
await self.playwright.stop()
5. 任务调度与执行
5.1 APScheduler定时配置
python复制from apscheduler.schedulers.blocking import BlockingScheduler
scheduler = BlockingScheduler()
@scheduler.scheduled_job('cron', hour=8, minute=30)
def daily_crawl():
asyncio.run(main())
scheduler.start()
5.2 分布式任务扩展
对于大规模采集,可以结合Redis实现分布式锁:
python复制from redis import Redis
def acquire_lock(conn, lock_name, acquire_timeout=10):
identifier = str(uuid.uuid4())
end = time.time() + acquire_timeout
while time.time() < end:
if conn.setnx(lock_name, identifier):
return identifier
time.sleep(0.001)
return False
6. 实战经验与优化建议
-
元素定位优化:
- 优先使用
data-testid等测试属性 - 避免使用易变的CSS类名如
class^="style__" - 对动态生成的元素使用
page.wait_for_selector()配合state="attached"
- 优先使用
-
性能调优技巧:
python复制# 禁用不必要的资源加载 await page.route("**/*.{png,jpg,jpeg}", lambda route: route.abort()) await page.route("**/*.css", lambda route: route.abort()) -
反检测策略:
- 随机化鼠标移动轨迹
- 模拟人类输入速度(每个字符间隔100-300ms)
- 设置合理的Viewport尺寸
python复制await page.set_viewport_size({"width": 1366, "height": 768}) -
数据存储建议:
- 原始HTML存放到对象存储(如MinIO)
- 结构化数据存入PostgreSQL
- 使用
messagepack压缩传输数据
7. 异常处理全集
常见问题及解决方案:
| 异常类型 | 触发场景 | 解决方案 |
|---|---|---|
| TimeoutError | 元素加载超时 | 增加wait_for_selector超时时间,添加重试 |
| NetworkError | 代理不稳定 | 切换代理IP,检查网络连接 |
| SelectorError | DOM结构变更 | 更新选择器,添加备用选择器 |
| CAPTCHA | 触发反爬 | 降低采集频率,模拟人类行为 |
调试技巧:
python复制# 发生异常时截图
await page.screenshot(path=f"debug_{int(time.time())}.png")
# 保留现场HTML
html = await page.content()
with open("debug.html", "w") as f:
f.write(html)
这个方案在我们实际项目中实现了98%以上的采集成功率,日均稳定处理10万+页面。关键点在于:合理的请求间隔、完善的错误处理、以及模拟真实用户行为模式。对于需要登录的场景,建议使用playwright的storage_state保存认证状态,避免频繁登录触发风控。