1. 项目背景与核心挑战
最近在分析零售行业数据时,发现山姆会员店的商品数据具有独特的研究价值。作为仓储式超市的典型代表,其大包装商品和会员专属优惠策略对消费行为研究很有意义。但直接爬取面临两个技术难点:
- 会员墙拦截:非登录状态下仅展示部分商品,完整数据需会员身份
- 动态加载机制:页面采用无限滚动加载,传统爬虫难以获取完整商品列表
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型:Playwright的优势解析
经过多轮测试对比,最终选择Playwright作为核心工具,主要基于以下考量:
2.1 浏览器环境模拟能力
- 完整支持现代浏览器(Chromium/Firefox/WebKit)
- 可生成真实用户行为轨迹(鼠标移动、滚动等)
- 自动处理SSL证书和WebSocket连接
2.2 反检测特性对比
| 特性 | Playwright | Selenium | Puppeteer |
|---|---|---|---|
| 指纹伪装 | ✔️ | ❌ | ✔️ |
| 自动化特征隐藏 | ✔️ | ❌ | ❌ |
| 多语言支持 | ✔️ | ✔️ | ❌ |
实测发现Playwright的
stealth模式能有效绕过山姆的自动化检测
3. 核心实现方案
3.1 登录态维持方案
python复制async def login_samclub(context):
page = await context.new_page()
await page.goto('https://www.samsclub.cn/login')
# 采用环境变量存储凭证
await page.fill('#username', os.getenv('SAM_USER'))
await page.fill('#password', os.getenv('SAM_PWD'))
# 模拟人类操作间隔
await page.wait_for_timeout(random.randint(1000,3000))
await page.click('#login-btn')
# 验证登录状态
await page.wait_for_selector('.member-indicator', timeout=15000)
return context
3.2 滚动加载控制算法
- 初始滚动:触发首屏加载
python复制await page.evaluate("window.scrollTo(0, document.body.scrollHeight)") - 动态等待策略:
- 基于网络空闲检测(500ms无新请求)
- 结合DOM变化监控(MutationObserver)
- 终止条件判断:
python复制def should_continue(): return page.evaluate(""" () => { const loader = document.querySelector('.loading-indicator'); return loader && loader.style.display !== 'none'; } """)
4. 大包装商品识别模型
4.1 特征提取规则
- 商品标题包含"家庭装"、"量贩装"等关键词
- 净含量超过常规规格(如饮料>2L)
- 价格/重量比值低于品类均值
4.2 结构化数据示例
json复制{
"product_name": "Member's Mark 澳洲进口牛肉粒 1kg*3包",
"unit_price": 89.9,
"spec": "3kg/组",
"tag": ["大包装", "会员专享"],
"price_per_kg": 29.97
}
5. 反反爬策略实录
5.1 行为指纹混淆方案
python复制async def human_like_movement(page):
# 随机移动轨迹生成
for _ in range(random.randint(3,7)):
x = random.randint(0, 1200)
y = random.randint(0, 800)
await page.mouse.move(x, y)
await page.wait_for_timeout(random.randint(200,800))
# 随机滚动模式
scroll_patterns = [
{"behavior": "smooth", "iterations": 3},
{"behavior": "auto", "iterations": 5}
]
pattern = random.choice(scroll_patterns)
for _ in range(pattern["iterations"]):
await page.mouse.wheel(0, random.randint(200,500))
await page.wait_for_timeout(random.randint(300,1500))
5.2 请求特征优化配置
python复制context = await browser.new_context(
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
extra_http_headers={
'Accept-Language': 'zh-CN,zh;q=0.9',
'Sec-Ch-Ua': '"Not/A)Brand";v="99"'
},
locale='zh-CN',
timezone_id='Asia/Shanghai'
)
6. 数据存储与分析方案
6.1 存储架构设计
code复制data/
├── raw/ # 原始HTML快照
│ └── 20240515/
├── processed/ # 解析后结构化数据
│ └── grocery.json
└── analytics/ # 分析结果
├── price_trend.png
└── package_size.csv
6.2 关键分析维度
- 大包装商品占比分析
- 会员专享优惠力度统计
- 品类价格密度分布
7. 实战踩坑记录
7.1 典型异常处理
python复制try:
await page.wait_for_selector('.product-card', timeout=10000)
except TimeoutError:
if await page.is_visible('.anti-bot-challenge'):
await solve_captcha(page)
elif await page.is_visible('.login-wall'):
await relogin(context)
7.2 性能优化要点
- 并行控制:每个品类分配独立browser context
- 内存管理:定期清理无用的page对象
- 请求去重:使用Bloom过滤器避免重复抓取
8. 合规边界说明
- 严格遵守robots.txt协议
- 请求频率控制在30req/min以下
- 数据仅用于学术研究
- 不绕过付费内容权限
实际部署时建议添加--proxy-server参数,配合住宅IP轮换策略。对于需要大规模采集的场景,可以采用分布式架构部署多个worker节点,通过Redis实现任务队列管理。
