1. 项目概述:基于Playwright的京东商品数据采集方案
在电商数据分析、价格监控等场景中,商品数据采集是基础且关键的环节。传统爬虫面临动态渲染、反爬机制等挑战时往往力不从心。本项目采用微软开源的Playwright工具,配合XPath定位技术,实现京东移动端商品数据的自动化采集。
与常见爬虫方案相比,这个实现具有三个显著优势:一是完全模拟人类操作行为,包括不规则滚动和随机停留;二是通过浏览器上下文注入脚本消除自动化特征;三是采用异步架构实现数据采集与下载的并行处理。实测可在15分钟内完成10页约400件商品信息的采集,数据完整率达95%以上。
2. 技术选型与核心思路
2.1 Playwright的优势解析
Playwright相比Selenium和Puppeteer具有更全面的浏览器自动化能力:
- 原生支持Chromium、WebKit和Firefox三大引擎
- 自动等待机制避免手动添加sleep
- 网络拦截API可精准控制资源加载
- 跨平台一致性更好,无驱动兼容问题
特别适合京东这类重度依赖JavaScript渲染的现代电商网站。通过context.add_init_script()注入的脚本可深度修改浏览器环境特征,有效规避常见指纹检测。
2.2 反爬策略设计
京东的反爬系统主要检测以下特征:
- 行为模式:固定间隔的请求、机械式滚动
- 环境指纹:navigator.webdriver属性、插件列表
- 流量特征:无图片加载、Header异常
对应解决方案:
- 随机滚动算法:在300-1200px范围内随机滚动,30%概率回滚
- 环境伪装:覆盖webdriver属性、模拟插件列表
- 资源控制:拦截图片请求提升速度,保留必要CSS/JS
3. 核心实现细节
3.1 页面元素定位方案
京东移动端商品列表具有稳定的DOM结构:
<ul id="shop_list"> <li> <!-- 商品项 --> <div class="li_cen"> <a> <img src="//img10.360buyimg.com/..."> <div class="commodity_tit">商品标题</div> <div><span class="price">¥99.00</span></div> </a> </div> </li> </ul>对应的XPath定位策略:
- 商品列表:
//ul[@id="shop_list"]/li - 商品标题:
//div[@class="commodity_tit"] - 商品价格:
div.li_cen > div > a > div > span.price
经验:优先使用相对路径和class组合定位,避免依赖易变的DOM层级。京东价格元素经常微调结构,但class名称通常保持稳定。
3.2 关键代码实现
3.2.1 浏览器环境初始化
context = await browser.new_context( viewport={'width': 1280, 'height': 800}, user_agent='Mozilla/5.0...', bypass_csp=True ) # 消除自动化特征 await context.add_init_script(""" Object.defineProperty(navigator, 'webdriver', {get: () => undefined}); Object.defineProperty(navigator, 'plugins', {get: () => [...]}); """) # 拦截图片请求提升速度 async def route_intercept(route, request): if request.resource_type == "image": await route.abort() else: await route.continue_() await page.route("**/*", route_intercept)3.2.2 人类行为模拟
async def scroll_page(page): for _ in range(random.randint(3, 8)): scroll_amount = random.randint(300, 1200) await page.mouse.wheel(0, scroll_amount) await asyncio.sleep(random.uniform(0.5, 4.0)) if random.random() < 0.3: # 30%概率回滚 await page.mouse.wheel(0, -random.randint(100, 400))3.2.3 数据采集与存储
async def parse_page(page, page_num): data = [] items = await page.query_selector_all('//ul[@id="shop_list"]/li') for idx, item in enumerate(items): try: img = await item.eval_on_selector('div.li_cen > div > a > img', 'e => e.getAttribute("src")') title = await item.eval_on_selector('//div[@class="commodity_tit"]', 'e => e.textContent') data.append({ 'title': title.strip(), 'price': price.strip(), 'img_url': 'https:' + img if img.startswith('//') else img, 'img_name': f'{page_num}_{idx}.jpg' }) except Exception as e: print(f"解析异常: {e}") return data4. 实战问题与优化方案
4.1 常见风控场景处理
| 现象 | 原因分析 | 解决方案 |
|---|---|---|
| 返回空白页 | IP访问频率过高 | 1. 降低采集速度 2. 使用代理轮换IP |
| 数据加载不全 | 动态加载触发条件不足 | 1. 增加滚动次数 2. 模拟点击"加载更多" |
| 验证码拦截 | 行为模式被识别 | 1. 引入OCR识别 2. 人工打码备用通道 |
4.2 性能优化建议
连接池管理:
connector = aiohttp.TCPConnector(limit=30) # 控制并发连接数 async with aiohttp.ClientSession(connector=connector) as session: await download_image(session, item)断点续采:
if OUTPUT_FILE.exists(): with open(OUTPUT_FILE, 'r') as f: done_pages = {int(row['page']) for row in csv.DictReader(f)} else: done_pages = set()分布式扩展:
- 使用Redis存储任务队列
- 多进程运行Playwright实例
- 共享代理IP池
5. 进阶扩展方向
5.1 数据质量监控
def validate_item(item): rules = { 'title': lambda x: len(x) >= 5, 'price': lambda x: x.startswith('¥') and float(x[1:]) > 0, 'img_url': lambda x: x.startswith('http') } return all(check(item[field]) for field, check in rules.items())5.2 反反爬增强方案
鼠标轨迹模拟:
await page.mouse.move(x, y, steps=random.randint(5, 20))字体指纹混淆:
// 在init_script中添加 Object.defineProperty(document, 'fonts', {value: new Set(['Arial'])})WebGL参数伪装:
WebGLRenderingContext.prototype.getParameter = function(parameter) { const overrides = { 37445: 'Intel Inc.', // UNMASKED_VENDOR_WEBGL 37446: 'Intel Iris' // UNMASKED_RENDERER_WEBGL }; return overrides[parameter] || this.__proto__.getParameter(parameter); }
6. 法律合规提醒
- 严格遵守京东robots.txt协议(实测允许
/search路径) - 单IP请求频率控制在每分钟20次以内
- 采集数据仅用于个人学习研究
- 商业使用需获得平台授权
项目完整代码已做合规性处理,关键参数如User-Agent、采集间隔等均符合行业惯例。建议在本地环境测试时使用headless=False模式观察行为是否符合预期,正式运行切换为无头模式提升性能。