#淘宝、天猫等电商爬虫问题与总结(一)
此次电商数据采集器(爬虫)共采集10个电商平台(淘宝、天猫、京东、国美、苏宁、拼多多、亚马逊、1688、一号店、慧聪)的数据,这里将公司的业务需求全部去除掉,基本的电商数据是全的。下面简单说一下整个的思路:
采集器使用activemq作为消息队列,采用生产者和消费者的模式,用来分发任务与接受任务,各平台之间采用redis做去重处理,爬虫框架使用webmagic,使用springboot用作项目的管理。前端可创建多个规则(平台名称+搜索关键字即组成一个规则),采集器作为消息的接收者接受到消息后,开始启动爬虫。1个规则对应1个爬虫。
中间坑是踩了无数个的,毕竟整个框架搭出来踩了一些坑,各个平台的爬取,反爬策略等等都有很多的陷阱。下面会就各个平台的坑分别分析,不定时更新。(所有的应对方法对应最新的都是当前更新的日期,以后会有改变的,就,自求多福吧。。。)
/**
* @param crawlerRule 爬取规则
* @param page 页面
* @return 商品列表页数据
*/
public Optional> parse(CrawlerRule crawlerRule, Page page) {
Matcher matcher = pattern.matcher(page.getHtml().get());
String spiderUrl = page.getUrl().get();
if (matcher.find()) {
String json = matcher.group().replace("g_page_config =", "").replace("};", "}");
String recommendText = null;
/**
* 判断是否出现相似数据 若出现则不进行下一页数据抓取
*/
try {
recommendText = JsonPath.read(json, RESULT_TEXT_JSON);
} catch (Exception e) {
logger.info("未匹配到recommendText, {}", recommendText);
}
if (StringUtils.isBlank(recommendText)){
// 分页数据
HashMap tbPager = JsonPath.read(json, RESULT_PAGE_JSON);
Integer totalPage = (Integer) tbPager.get("totalPage");
Integer currentPage = (Integer) tbPager.get("currentPage");
// 如果还有下一页数据,添加下一页的任务
if (++pageNum < totalPage) {
String[] urls = spiderUrl.split("&s=");
String nextPageUrl = urls[0] + "&s=" + currentPage * 44;
page.addTargetRequest(nextPageUrl);
logger.info("当前页 {} , 总页数 {} ", currentPage, totalPage);
}
}
/**
* 获取数据结果
*/
List
在这里我获取商品数据总页数的时候,采用的方法是每次都获取一次数据,因为淘宝(天猫)的页码在web页面上显示是有缓存的,所有第一页获取的页码通常是不准的,所以我是每一页都去获取一次页码,直到当前页与总页数相等时,以防其他平台也有这种问题,所以这11个平台我都采取的这种方法,这里淘宝天猫的列表页解析方式是相同的,所以我写在了一个方法里面。这里我踩过的一次坑是,知道页码不准的时候,我采用的方法是每次获取页面内容,当解析不到数据的时候,则判定为商品数据全部获取完整。这种方法是不准确的,因为可能页面会在最后的页码为总页数时,返回欺骗的数据或者错误数据,却仍旧进入下一页。
淘宝(天猫)的详情页数据我是通过接口的去拿的,这个接口还是比较好找的,去network找一下都能找到,只不过淘宝(天猫)的详情页反爬比较严重,需要设置请求头(User-Agent、Referer),还需要不断换IP。这里获取商品的销量、库存、原价、现价等。
现更新一则,目前淘宝PC端获取详情页数据的接口,不仅需要设置请求头,referer,换IP之外,还需要携带cookie,但是这个cookie是有时效的,所以这个就比较麻烦了。
我这两天寻找到了淘宝的移动端接口,目前单机单线程测试,休眠3s,是不反爬的,但是还没有在并发量高的情况下测试过。
/**
* 根据移动端接口获取淘宝详情页数据
*
* @param goodsId 商品ID
* @return
*/
public static String getGoodsDetails(String ip, int port, String goodsId) {
String content = null;
try {
Thread.sleep(10000);
CloseableHttpClient client = HttpClients.custom().build();
HttpGet get = new HttpGet("https://h5api.m.taobao.com/h5/mtop.taobao.detail.getdetail/6.0/?data=" + URLEncoder.encode("\"itemNumId\":\"" + goodsId + "\"", "utf-8"));
get.setHeader("referer", "https://m.intl.taobao.com/detail/detail.html?id=" + goodsId);
get.setHeader("user-agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Mobile Safari/537.36");
HttpHost proxy = new HttpHost(ip, port);
RequestConfig requestConfig = RequestConfig.custom().setProxy(proxy)
.setConnectTimeout(3000)
.setSocketTimeout(3000)
.setConnectionRequestTimeout(3000).build();
get.setConfig(requestConfig);
int times = 0;
while (times < 3) {
try (CloseableHttpResponse response = client.execute(get)) {
HttpEntity entity = response.getEntity();
content = EntityUtils.toString(entity);
if (StringUtils.isBlank(content)) {
times += 1;
} else {
break;
}
}catch (Exception ex) {
Thread.sleep(6000);
times += 1;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return content;
}
淘宝(天猫)的店铺页是不反爬的,但是如果想要获取店铺的信誉分、好评率(淘宝有,天猫店铺无信誉分等),我使用的是chromedriver来获取的,这个比较麻烦,因为chromedriver非常吃内存,并且API自带的close()(关闭tab页面)以及quit()(关闭整个chrome浏览器)方法,是无法杀死进程,我至今还是没寻找到其他好的方法,目前解决方法就是写个脚本,在凌晨统一杀死。。。
平时使用的话,我觉得以上数据内容应该足够了,所以店铺信息这方面,我在代码里面去除掉了,写的也比较笨重。。。