背景介绍
- 公司在各电商平台(亚马逊/沃尔玛/Ebay/京东国际/速卖通)有大量商品,但是没有一个能聚合各商品的排行的工具或功能。现需开发一个每月统计商品排行的功能,供公司数据决策做参考。
- 对上述5个平台了解后发现只有亚马逊有提供精准排行数据,其他平台如必须则只能通过在指定商品分类的列表页去搜索该商品 才能得知排行,商量后觉得这种方式不可取,所以暂时只抓取亚马逊的排行数据,星级/评论数为附带抓取数据。
方案选择
F12查看内容发现数据是绑定到dom上的 没有直接的ajax数据接口可用(一般有直接的数据接口优选接口),所以只能取dom解析得到数据。
得到dom可用通过以下方案 (https://站点host/dp/asin)
- 方案一 jsoup(解释1)直接访问listing页面
优点:只访问单页 不加载静态资源(js/css/img…) 给代理省流量;速度快
缺点:如果对方服务器通过静态资源做页面事件监听并以此为风控鉴定(解释2),则很快会被限流或直接禁止访问 - 方案二 selenium(解释3)访问listing页面,并将dom转换为jsoup的dom(jsoup对dom解析的api更好用),然后解析数据
优点:基本可以直接忽略接口加密、页面行为上传的导致的风控
缺点:速度慢;打开一个链接附带的所有静态资源都会占用代理带宽
出现的问题及解决方案
问题一 验证码
先方案一在本地运行
- 一段时间后发现dom里没有相关数据了,debug发现http403直接跳转到了验证码页面,好在验证码都是比较简单的;jsoup不能直接操作dom,要破解验证码+发送验证码接口,这样比较麻烦,为了省时,直接使用方案二,将识别好的验证码通过webdriver发送过去。
解决方案
- 切换方案二
- 使用python的easyocr+flask 搭建了一个验证码识别服务;easyocr在linux下安装可能有点麻烦 按教程多试几次。easyocr验证码识别率做不到100%,但是整体还是可以接受的。
# coding=utf-8
import base64
import urllib
import uuid
from flask import Flask
from flask import request, json
import easyocr
import os
app = Flask(__name__)
reader = easyocr.Reader(['ch_sim', 'en'], gpu=False)
# 接口的入参是验证码图片的url
@app.route('/ocrUrl', methods=['POST'])
def ocrCaptchaUrl():
try:
data = request.get_data()
print(data)
json_data = json.loads(data.decode("UTF-8"), strict=False)
allow = json_data.get("allow")
url = json_data.get("url")
rsp = urllib.request.urlopen(url)
file_name = str(uuid.uuid1()) + ".jpg"
file = open(file_name, "wb")
file.write(rsp.read())
file.close()
res = reader.readtext(file_name, detail=0, allowlist=allow)
os.remove(file_name)
return '{"status":"200", "result":"' + res[0] + '"}'
except Exception as e:
print(e)
return '{"status":"500"}'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8181)
问题二 代理带宽不够
linux下运行一段时间后 日志经常报错浏览器超时崩溃,经排查发现是代理带宽不够用,因为selenium加载一个链接时会加载附带的所有静态资源,对代理带宽消耗大。
解决方案
- 修改chromeDriver配置 不加载img/css等资源
// 禁用图片 & 样式 & flash
Map<String, Object> prefs = new HashMap<>();
prefs.put("profile.managed_default_content_settings.images", 2);
prefs.put("profile.managed_default_content_settings.stylesheet", 2);
prefs.put("dom.ipc.plugins.enabled.libflashplayer.so", "false");
chromeOptions.setExperimentalOption("prefs", prefs);
- linux服务器配置比本地机器高,访问速度快,控制selenium访问速度,单条记录最少3200毫秒
问题三 防止过快被服务器标记
一直在抓取,容易被标记ip导致长时间不能访问,这点是防范于未然。
解决方案
- 程序每开启一个chrome,都随机配置一个UserAgent,爬取n条记录后销毁chrome 下次需要再重新生成。
- 线程id和chrome绑定,控制并发 最多使用N(线程个数)个chrome实例。
- 代理和非代理轮番使用。因为代理资源不够,所以使用代理时效率低,但是又不能完全使用服务器ip去抓取,所以这里轮番去使用,且定时任务执行了10次后休息一段时间
问题四
其他业务细节问题以及平时粗心容易犯的错误就不记录了
解释
解释1:jsoup 爬虫工具库,主要包含http的请求操作、Dom解析操作。提供的dom api操作起来和jQuery一样非常方便
解释2:页面风控 部分大厂的产品会过度收集用户信息,包括用户设备信息、在页面的操作轨迹,以此对用户进行分析,并将分析结果作为风控或用户画像的分析,字节系/阿里系产品都有这类骚操作。web端的收集一般会把数据当成参数放在一个静态资源的请求链接上(如xx.gif?data=xxx) 其中xx.gif并不是一个实际的图片 而是服务器收集数据的一个接口
解释2:selenium
自动化测试工具包,提供了程序控制浏览器行为的api;liunx下需安装对应浏览器服务 可headless模式运行;
总结
需求虽然挺简单,但是从代码完成 到问题修补 最后到数据抓取完成陆续花了不少时间,其中不少问题是细心一点可以避免的。总结下次做事细心点~