影刀RPA深度实战:京东商品数据批量采集与SKU价格抓取完整方案
作者:林焱
标签:#影刀RPA #京东采集 #SKU抓取 #价格监控 #电商数据 #爬虫替代
开篇:为什么京东采集比淘宝难三倍
去年我给一个3C卖家做竞品价格监控,用惯了淘宝思路去搞京东,连续踩了一周的坑才摸清门道。京东的难点不在于页面结构,而在于它的动态渲染机制和登录态强依赖——很多价格根本不是写在HTML里的,是JS执行后才渲染出来的。
更烦的是SKU切换:同一个商品,点不同的颜色/规格,URL不变但价格变了,传统爬虫根本感知不到这个变化。影刀胜在它能真实驱动浏览器,SKU切换、价格等待、登录保持都能处理。这篇我把完整方案拆给你看。
一、准备工作:环境配置与登录态保持
1.1 浏览器环境选择
京东对Selenium指纹检测很严,强烈建议用影刀内置的Chrome而不是系统Chrome。影刀的浏览器做了指纹混淆处理,实测检测率低得多。
配置步骤:
- 打开影刀 → 新建流程 → 选择"Web自动化"
- 浏览器设置里勾选"使用内置浏览器"
- 启动参数加上
--disable-blink-features=AutomationControlled(关键!不加这个京东登录会触发验证码)
1.2 登录态保持方案
这是我踩得最深的坑:京东每隔约2小时会刷新session,如果你的采集任务超过这个时长,中途会静默登出,数据开始出错但流程不报错。
解决方案——用Cookie持久化:
# 影刀Python代码块:保存并复用京东登录Cookie# 首次登录后执行此代码保存Cookie到本地文件importjsonimportosdefsave_jd_cookies(page):""" page: 影刀传入的页面对象 保存当前登录态Cookie到本地,供后续复用 """cookies=page.get_cookies()# 获取所有Cookie# 只保留京东主域Cookie,过滤掉无用的第三方Cookiejd_cookies=[cforcincookiesif'.jd.com'inc.get('domain','')]cookie_path=r'C:\RPA_Data\jd_cookies.json'os.makedirs(os.path.dirname(cookie_path),exist_ok=True)withopen(cookie_path,'w',encoding='utf-8')asf:json.dump(jd_cookies,f,ensure_ascii=False,indent=2)print(f"Cookie已保存,共{len(jd_cookies)}条")returnTruedefload_jd_cookies(page):""" 加载已保存的Cookie,实现免登录 注意:需要先访问jd.com任意页面后再注入Cookie """cookie_path=r'C:\RPA_Data\jd_cookies.json'ifnotos.path.exists(cookie_path):print("Cookie文件不存在,需要手动登录")returnFalsewithopen(cookie_path,'r',encoding='utf-8')asf:cookies=json.load(f)# 检查Cookie是否过期(京东的pt_pin和pt_key是关键登录标识)has_pt_pin=any(c.get('name')=='pt_pin'forcincookies)has_pt_key=any(c.get('name')=='pt_key'forcincookies)ifnot(has_pt_pinandhas_pt_key):print("关键登录Cookie缺失,需重新登录")returnFalsepage.add_cookies(cookies)# 注入Cookiepage.reload()# 刷新页面使Cookie生效returnTrue操作步骤:
- 影刀流程开头加一个"执行Python代码"节点
- 粘贴
load_jd_cookies函数并调用 - 加一个条件判断:如果返回False,走"手动登录"分支;如果True,跳过登录直接开始采集
1.3 验证登录状态
不要假设Cookie有效就真的有效,加一个验证步骤:
defverify_jd_login(page):""" 访问个人中心页面验证登录状态 返回True表示已登录,False表示未登录 """page.goto('https://home.jd.com/')# 等待页面加载完成page.wait_for_load_state('networkidle')# 检查是否存在登录后的用户名元素# XPath说明:.user-info 是京东个人中心页登录后显示用户名的容器try:username_elem=page.wait_for_selector('.user-info .nickname',timeout=5000# 5秒超时)username=username_elem.inner_text()print(f"当前登录用户:{username}")returnTrueexcept:print("未检测到登录状态")returnFalse二、核心采集:商品基础信息
2.1 商品列表页采集
以搜索结果页为例,影刀拖拽操作配合XPath:
场景:从京东搜索"蓝牙耳机",采集前100个商品的ID、名称、价格、店铺。
# 采集京东搜索结果列表# 目标页面:https://search.jd.com/Search?keyword=蓝牙耳机defcollect_jd_search_results(page,keyword,max_pages=5):""" 采集搜索结果页的商品列表 XPath说明: - //li[@class='gl-item'] : 商品列表每个条目的容器 - .//div[@class='p-img']/a : 商品图片链接,href包含商品ID - .//div[@class='p-name']/a/em : 商品标题 - .//div[@class='p-price']/strong/i : 价格数字部分 - .//div[@class='p-shop']/span/a : 店铺名称 """results=[]base_url=f'https://search.jd.com/Search?keyword={keyword}&enc=utf-8'forpage_numinrange(1,max_pages+1):# 京东搜索翻页:page参数,奇数翻页规则(1,3,5...)url=f'{base_url}&page={page_num*2-1}'page.goto(url)# 等待商品列表加载——关键!京东用懒加载,必须滚动才能触发page.wait_for_selector('#J_goodsList .gl-item',timeout=10000)# 滚动到底部触发懒加载(否则后半部分商品不显示)page.evaluate('window.scrollTo(0, document.body.scrollHeight)')page.wait_for_timeout(1500)# 等待懒加载完成page.evaluate('window.scrollTo(0, document.body.scrollHeight)')page.wait_for_timeout(1000)# 获取所有商品条目items=page.query_selector_all('#J_goodsList .gl-item')foriteminitems:try:# 提取商品ID(从data-sku属性获取,比从URL解析更可靠)sku_id=item.get_attribute('data-sku')# 商品名称name_elem=item.query_selector('.p-name a em')name=name_elem.inner_text()ifname_elemelse''# 价格——注意:价格是动态渲染的,需要等待price_elem=item.query_selector('.p-price strong i')price=price_elem.inner_text()ifprice_elemelse'0'# 店铺信息shop_elem=item.query_selector('.p-shop span a')shop=shop_elem.inner_text()ifshop_elemelse'京东自营'# 评论数comment_elem=item.query_selector('.p-commit strong a')comments=comment_elem.inner_text()ifcomment_elemelse'0'results.append({'sku_id':sku_id,'name':name.strip(),'price':float(price.replace(',','')),'shop':shop.strip(),'comments':comments,'page':page_num,'url':f'https://item.jd.com/{sku_id}.html'})exceptExceptionase:print(f"解析商品失败:{e}")continueprint(f"第{page_num}页采集完成,当前共{len(results)}条")# 翻页间隔,模拟人工操作,避免触发风控page.wait_for_timeout(2000+page_num*500)returnresults2.2 商品详情页采集(含PC/APP差异处理)
我之前做3C品类监控时遇到的坑:京东部分商品(特别是大件家电)强制跳转到APP,PC端只显示"请用APP查看",这种情况需要在URL后面加参数强制PC显示。
defcollect_jd_detail(page,sku_id):""" 采集商品详情页完整信息 注意事项: 1. 部分商品会重定向到 https://item.m.jd.com/ (移动版),需要强制PC访问 2. 价格通过API接口获取比页面抓取更稳定 3. SKU信息藏在页面内嵌的JavaScript变量里 """# 强制访问PC版商品详情(防止移动端重定向)detail_url=f'https://item.jd.com/{sku_id}.html'# 设置User-Agent为PC浏览器(影刀支持在请求头里设置)page.goto(detail_url)page.wait_for_load_state('domcontentloaded')# 检查是否被重定向到移动端current_url=page.urlif'item.m.jd.com'incurrent_url:# 强制回到PC端page.goto(detail_url+'?forcePC=1')page.wait_for_load_state('domcontentloaded')# 从页面内嵌JS变量提取SKU信息(比XPath更可靠)# 京东把商品详细信息存在window.pageConfig对象里sku_info=page.evaluate(''' () => { try { // window.pageConfig.product 包含商品基础信息 // window.pageConfig.product.skus 包含所有SKU变体 return { product_id: window.pageConfig.product.wareId, cat_id: window.pageConfig.product.cat.join(','), shop_id: window.pageConfig.shop.shopId, skus: window.pageConfig.product.skus || [] }; } catch(e) { return null; } } ''')# 获取商品基本信息title_elem=page.query_selector('.sku-name')title=title_elem.inner_text()iftitle_elemelse''return{'sku_id':sku_id,'title':title.strip(),'sku_info':sku_info,'url':page.url}三、SKU变体采集(最复杂部分)
3.1 SKU变体逻辑说明
京东SKU变体的核心难点:点击不同规格时,价格通过AJAX异步更新,URL不变。普通截图类方法完全抓不到变化。
正确方案:拦截网络请求
defcollect_all_skus(page,sku_id):""" 采集商品所有SKU变体的价格 原理:监听京东价格查询API的响应 京东价格API格式: https://p.3.cn/prices/mgets?skuIds=J_{sku_id}&type=1 返回格式:[{"id":"J_xxx","p":"价格","m":"原价","op":"..."}] """all_sku_prices={}# 先从页面JS变量获取所有SKU ID列表sku_list=page.evaluate(''' () => { // colorSize是京东存储颜色/尺码规格的JS变量 if (window.colorSize) { return window.colorSize.map(item => item.skuId); } // 备选方案:从商品规格选择器获取 const skuItems = document.querySelectorAll('[data-sku]'); return Array.from(skuItems).map(el => el.getAttribute('data-sku')); } ''')ifnotsku_list:print(f"SKU{sku_id}无变体规格")return{}print(f"共发现{len(sku_list)}个SKU变体:{sku_list}")# 批量查询价格(京东价格API支持批量查询,效率更高)# 每次最多查50个SKUbatch_size=50foriinrange(0,len(sku_list),batch_size):batch=sku_list[i:i+batch_size]sku_ids_param='&skuIds='.join([f'J_{s}'forsinbatch])price_url=f'https://p.3.cn/prices/mgets?skuIds=J_{sku_ids_param}&type=1'# 用影刀的HTTP请求节点发送(比页面导航更快)importurllib.requestimportjson req=urllib.request.Request(price_url,headers={'Referer':f'https://item.jd.com/{sku_id}.html','User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'})withurllib.request.urlopen(req,timeout=10)asresp:price_data=json.loads(resp.read().decode('utf-8'))foriteminprice_data:sku_key=item['id'].replace('J_','')all_sku_prices[sku_key]={'price':float(item.get('p',0)),# 现售价'market_price':float(item.get('m',0)),# 原价/划线价'op':item.get('op','')# 限时特价}page.wait_for_timeout(500)# 避免请求过于频繁returnall_sku_prices3.2 规格属性与SKU的对应关系
defmap_sku_attributes(page):""" 提取SKU变体的规格属性映射 例如:SKU 100012345678 对应 颜色=黑色, 内存=128G XPath说明: - .//div[@id='spec-list'] : 规格选择区域的容器 - .//div[@data-type='选择版本'] : 具体规格类型(颜色/内存等) - .//li[@data-sku='...'] : 每个规格选项,data-sku关联具体SKU """sku_attr_map=page.evaluate(''' () => { const result = {}; // 遍历所有规格选择区域 const specAreas = document.querySelectorAll('.J-skin-main .dd'); specAreas.forEach(area => { // 规格名称(颜色、内存等) const specName = area.previousElementSibling?.innerText?.trim() || ''; // 该规格下的所有选项 const items = area.querySelectorAll('li[data-sku]'); items.forEach(item => { const skuId = item.getAttribute('data-sku'); const attrValue = item.getAttribute('title') || item.innerText.trim(); if (!result[skuId]) result[skuId] = {}; result[skuId][specName] = attrValue; }); }); return result; } ''')returnsku_attr_map四、评价数据采集
4.1 评价翻页的陷阱
我之前遇到的坑:京东评价页显示"共500页",但实际只能翻到第100页,后面的请求返回空数据。更坑的是,第100页之后京东会悄悄返回第1页的数据,导致你以为还在采集新数据,实际上在重复。
defcollect_jd_reviews(page,sku_id,max_pages=100):""" 采集商品评价,通过API接口而非页面截图 京东评价API: https://club.jd.com/comment/productPageComments.action 参数说明: - productId: 商品ID(注意!不是SKU ID,有时不同) - pageSize: 每页评价数量,最大10 - pageNum: 页码,从0开始 - score: 评分筛选(0=全部,1-5=星级) - sortType: 排序(5=最新,6=最有用) """importurllib.requestimportjson reviews=[]page_num=0seen_review_ids=set()# 去重集合,防止京东返回重复数据whilepage_num<max_pages:api_url=(f'https://club.jd.com/comment/productPageComments.action'f'?productId={sku_id}&pageSize=10&pageNum={page_num}'f'&score=0&sortType=5&page=0&pageSize=10&isShadowSku=0')req=urllib.request.Request(api_url,headers={'Referer':f'https://item.jd.com/{sku_id}.html','User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36','Accept':'application/json'})try:withurllib.request.urlopen(req,timeout=10)asresp:data=json.loads(resp.read().decode('utf-8'))exceptExceptionase:print(f"评价API请求失败,页码{page_num}:{e}")breakcomment_list=data.get('comments',[])ifnotcomment_list:print(f"第{page_num}页无数据,停止采集")break# 检测是否在返回重复数据(京东翻页限制的标志)first_id=comment_list[0].get('id','')iffirst_idinseen_review_idsandpage_num>5:print(f"检测到重复数据,评价采集在第{page_num}页停止(京东翻页限制)")breakforcommentincomment_list:review_id=comment.get('id','')ifreview_idinseen_review_ids:continueseen_review_ids.add(review_id)reviews.append({'id':review_id,'score':comment.get('score',0),# 评分(1-5)'content':comment.get('content',''),# 评价内容'nickname':comment.get('nickname',''),'creation_time':comment.get('creationTime',''),'sku_name':comment.get('productColor',''),# 购买的规格'useful_vote':comment.get('usefulVoteCount',0),'reply_count':comment.get('replyCount',0)})page_num+=1# 评价采集间隔要更长,京东评价接口限频更严page.wait_for_timeout(1000+page_num*100)print(f"评价采集完成,共{len(reviews)}条")returnreviews五、数据存储与导出
importcsvimportosfromdatetimeimportdatetimedefsave_to_csv(data_list,filename=None):""" 将采集数据保存为CSV 支持自动生成带时间戳的文件名 """ifnotfilename:timestamp=datetime.now().strftime('%Y%m%d_%H%M%S')filename=f'C:/RPA_Data/jd_data_{timestamp}.csv'os.makedirs(os.path.dirname(filename),exist_ok=True)ifnotdata_list:print("无数据可保存")return# 自动从第一条数据推断字段名fieldnames=list(data_list[0].keys())withopen(filename,'w',newline='',encoding='utf-8-sig')asf:# utf-8-sig:带BOM的UTF-8,Excel直接打开不乱码writer=csv.DictWriter(f,fieldnames=fieldnames)writer.writeheader()writer.writerows(data_list)print(f"已保存{len(data_list)}条数据到:{filename}")returnfilename六、常见问题速查
| 现象 | 原因 | 解决方式 |
|---|---|---|
| 搜索结果只有60条,实际有几百条 | 京东懒加载未触发,后半部分商品未渲染 | 添加多次scrollTo操作后再提取,每次滚动后等待1.5秒 |
| 价格显示"–"或0 | 商品已下架,或登录后才显示会员价格 | 检查登录状态;对下架商品做标记跳过 |
| 采集100个商品后流程卡住 | 京东触发风控,弹出验证码弹窗 | 添加验证码检测节点:检测.verification-wrap是否存在,存在则暂停并告警 |
| SKU切换后价格不更新 | 等待时间不足,价格AJAX未完成 | 在点击SKU后添加wait_for_response监听p.3.cn域名的响应 |
| 评价采集到第50页全是重复数据 | 京东评价翻页上限约50-100页 | 用seen_review_ids集合去重并检测到重复时主动停止 |
| Cookie注入后仍需重新登录 | 注入Cookie前未访问jd.com使域名生效 | 先page.goto('https://www.jd.com')再注入Cookie再刷新 |
七、推荐资源
- 影刀官方文档→ 网页自动化 → 等待元素章节(学好等待是京东采集成功的关键)
- Chrome DevTools→ Network标签 → 筛选XHR → 找
p.3.cn(价格接口)和club.jd.com(评价接口) - JSON Viewer插件(Chrome扩展):格式化查看京东API返回的JSON,便于分析字段含义
- 影刀社区→ 搜索"京东":有不少同行分享的XPath库,可以直接复用
最后说一句:京东的反爬策略一直在更新,本文的XPath和API截至2024年底可用,建议收藏后定期回来看看评论区是否有更新。遇到问题欢迎评论,我尽量回复。