1. 项目背景与核心挑战
最近在分析知识付费领域的用户互动数据时,发现得到App的知识城邦板块藏着大量有价值的UGC内容。但想要批量获取这些动态数据却遇到了几个硬骨头:
- 反爬机制严密:得到App的接口不仅做了参数加密,还采用了动态token验证,传统requests直接抓包的方式基本失效
- 动态渲染复杂:圈子里的用户动态包含懒加载图片、下拉刷新、时间戳动态计算等前端特性
- 登录状态维持:需要模拟完整的微信授权登录流程才能获取有效数据
经过多轮技术选型测试,最终确定Playwright+Python的组合方案。这个选择基于三个关键判断:
- Playwright的自动等待机制能完美处理SPA应用的动态渲染
- 支持完整的浏览器上下文保存,解决登录态保持问题
- 内置请求拦截功能可以捕获加密前的原始数据
实测发现:得到App的接口加密主要发生在请求头部的x-sign字段,采用时间戳+设备ID的MD5哈希,而Playwright可以直接获取渲染后的DOM内容绕过这层验证。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境搭建与核心配置
2.1 基础环境准备
推荐使用Python 3.8+版本,太新的版本可能遇到库兼容问题。安装时务必勾选"Add Python to PATH"选项:
bash复制# 创建虚拟环境(避免污染全局)
python -m venv got_data
source got_data/bin/activate # Linux/Mac
got_data\Scripts\activate # Windows
# 安装核心依赖
pip install playwright
playwright install chromium # 推荐chromium内核
2.2 Playwright特殊配置
在launch时需要开启几个关键参数:
python复制browser = await playwright.chromium.launch(
headless=False, # 调试阶段建议可视化
proxy={
"server": "http://your-proxy",
"username": "user",
"password": "pass"
}, # 高频率请求时需要
args=[
"--disable-blink-features=AutomationControlled",
"--user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X)"
]
)
关键细节:得到App会检测WebDriver特征,必须通过
args禁用自动化控制特征,并模拟移动端UA。实测iOS的UA识别通过率最高。
3. 核心爬取逻辑实现
3.1 登录态保持方案
得到App的登录流程涉及微信OAuth授权,手动操作后保存状态是最稳妥的方案:
python复制context = await browser.new_context(
storage_state="auth.json", # 保存的登录态
viewport={"width": 375, "height": 812}, # iPhone X尺寸
locale="zh-CN"
)
操作技巧:
- 首次手动完成微信扫码登录
- 通过
await context.storage_state(path="auth.json")保存cookies - 后续爬取直接加载该文件即可维持会话
3.2 动态内容捕获策略
知识城邦的动态加载采用滚动触发的分页模式,需要组合多种等待策略:
python复制async def scroll_page(page, scroll_times=5):
for i in range(scroll_times):
# 先等待可能的网络请求
await page.wait_for_timeout(2000 + random.randint(0,1000))
# 模拟人类滚动行为
scroll_height = await page.evaluate("document.body.scrollHeight")
current_pos = await page.evaluate("window.pageYOffset")
scroll_step = random.randint(300, 800)
await page.mouse.wheel(0, scroll_step)
# 关键:等待动态卡片渲染
await page.wait_for_selector(
f".dynamic-item:nth-child({(i+1)*10})",
state="attached",
timeout=5000
)
3.3 数据解析的三种方案对比
| 方案 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| DOM解析 | querySelector获取元素 | 无需处理接口加密 | 受布局变更影响大 |
| API拦截 | page.on("request")监听 | 获取原始数据 | 需解密处理 |
| 混合模式 | 两者结合 | 数据互补 | 实现复杂 |
推荐方案:初期使用DOM解析快速验证,稳定运行后切换到API拦截模式。关键代码示例:
python复制async def handle_request(request):
if "api.igetget.com" in request.url:
try:
response = await request.response()
data = await response.json()
if "dynamic/list" in request.url:
save_to_mongo(data["data"]["list"])
except:
pass
page.on("request", handle_request)
4. 反反爬实战技巧
4.1 行为伪装体系
得到App的风控系统会检测异常行为模式,需要建立完整的行为模拟:
python复制async def human_like_actions(page):
# 随机移动鼠标轨迹
for _ in range(random.randint(3,7)):
x = random.randint(0, 300)
y = random.randint(0, 600)
await page.mouse.move(x, y)
await page.wait_for_timeout(200 + random.randint(0,300))
# 随机点击空白处
if random.random() > 0.7:
await page.click("body", position={
"x": random.randint(0, 300),
"y": random.randint(0, 200)
}, delay=random.randint(100, 500))
4.2 请求指纹混淆
在context初始化时注入随机化参数:
javascript复制// 在page.evaluate中执行
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined
});
window.navigator.chrome = {
runtime: {},
// 其他属性...
};
5. 数据存储与清洗
5.1 MongoDB存储优化
针对动态内容的特点设计分片集合:
python复制from pymongo import MongoClient
from datetime import datetime
client = MongoClient("mongodb://localhost:27017")
db = client["igetget"]
collection = db["dynamics"]
def process_item(item):
return {
"dynamic_id": item["id"],
"content": item["content"],
"images": [img["url"] for img in item.get("images", [])],
"user_info": {
"uid": item["user"]["id"],
"name": item["user"]["name"]
},
"stats": {
"likes": item["like_count"],
"comments": item["comment_count"]
},
"crawl_time": datetime.utcnow(),
"tags": extract_tags(item["content"]) # NLP提取关键词
}
5.2 增量爬取策略
基于最后更新时间戳的优化方案:
python复制last_record = collection.find_one(
sort=[("dynamic_id", -1)]
)
since_id = last_record["dynamic_id"] if last_record else 0
# 在请求URL中添加参数
url = f"https://api.igetget.com/dynamic/list?since_id={since_id}"
6. 异常处理与监控
6.1 熔断机制实现
当连续出现5次请求失败时自动休眠:
python复制error_count = 0
max_retries = 5
async def safe_request(url):
global error_count
try:
response = await page.goto(url, timeout=15000)
if response.status != 200:
raise Exception
error_count = 0
return response
except Exception as e:
error_count += 1
if error_count >= max_retries:
await asyncio.sleep(300) # 休眠5分钟
error_count = 0
return None
6.2 分布式部署方案
使用Redis作为任务队列:
python复制import redis
from rq import Queue
redis_conn = redis.Redis(host='localhost', port=6379)
q = Queue(connection=redis_conn)
def enqueue_crawl_task(circle_id):
q.enqueue(
"crawl.circle_dynamics",
circle_id,
result_ttl=0,
timeout=3600
)
7. 性能优化实测数据
经过多轮调优后的性能对比:
| 优化项 | 优化前 | 优化后 | 提升幅度 |
|---|---|---|---|
| 单次请求耗时 | 3.2s | 1.8s | 43% |
| 内存占用 | 420MB | 280MB | 33% |
| 日均抓取量 | 1.2万条 | 3.5万条 | 192% |
关键优化点:
- 启用HTTP缓存:
context.set_http_cache(enable=True) - 禁用无用资源加载:
python复制await page.route("**/*.{png,jpg,jpeg}", lambda route: route.abort()) await page.route(re.compile(r"(analytics|tracking)"), lambda route: route.abort()) - 使用WebSocket替代轮询:监听动态更新事件
8. 法律合规边界
在开发过程中必须注意:
- 严格遵守robots.txt的禁止规则
- 单个IP的请求频率控制在30次/分钟以下
- 数据仅用于分析研究,不得商用
- 用户敏感信息必须脱敏处理
建议在代码中加入合规检查:
python复制def compliance_check(data):
# 删除手机号、微信号等
data["content"] = re.sub(r"1[3-9]\d{9}", "[PHONE]", data["content"])
# 其他处理...
return data
这个项目最让我意外的是Playwright处理iOS端WebView的能力——通过调整设备参数,可以完美模拟得到App内嵌浏览器的行为特征。在最新的一次测试中,连续运行72小时未被封禁,证明这套方案具有很好的稳定性。对于需要更高匿名的场景,可以考虑结合Playwright的Proxy Chain插件实现请求IP的自动轮换。
