淘宝、天猫等电商爬虫问题与总结(一)

#淘宝、天猫等电商爬虫问题与总结(一)

此次电商数据采集器(爬虫)共采集10个电商平台(淘宝、天猫、京东、国美、苏宁、拼多多、亚马逊、1688、一号店、慧聪)的数据,这里将公司的业务需求全部去除掉,基本的电商数据是全的。下面简单说一下整个的思路:
采集器使用activemq作为消息队列,采用生产者和消费者的模式,用来分发任务与接受任务,各平台之间采用redis做去重处理,爬虫框架使用webmagic,使用springboot用作项目的管理。前端可创建多个规则(平台名称+搜索关键字即组成一个规则),采集器作为消息的接收者接受到消息后,开始启动爬虫。1个规则对应1个爬虫。
中间坑是踩了无数个的,毕竟整个框架搭出来踩了一些坑,各个平台的爬取,反爬策略等等都有很多的陷阱。下面会就各个平台的坑分别分析,不定时更新。(所有的应对方法对应最新的都是当前更新的日期,以后会有改变的,就,自求多福吧。。。)

  • 淘宝、天猫
    淘宝天猫是一家了,分析方式都是差不多的。淘宝我是分3层页面来进行数据的抓取,列表页,详情页以及店铺页,每层之间使用mq进行消息的传递,列表页有一段JS包含了全部的列表的商品信息,在这里我是讲这段JS经过字符串的处理,然后转换成JSON格式,取出相应信息。
/**
     * @param crawlerRule 爬取规则
     * @param page        页面
     * @return 商品列表页数据
     */
    public Optional<List<GoodsInfo>> 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<Object> result = null;
            try {
                result = JsonPath.read(json, RESULT_JSON);
                List<GoodsInfo> goodsInfos =
                        result.stream()
                                .map(d -> getParsedGoodsInfo(crawlerRule, d)).collect(Collectors.toList());
                if (goodsInfos != null) {
                    return Optional.of(goodsInfos);
                }
            } catch (Exception e) {
                logger.error("parse json error : ", e.getMessage());
            }
            return Optional.empty();

        } else {
            try {
                String text = new String(page.getBytes(), "gbk");
                if (text.contains(PAGE_MATCH)) {
                    page.addTargetRequest(spiderUrl);
                }
            } catch (UnsupportedEncodingException e) {
                logger.error(e.getMessage());
            }
            return Optional.empty();
        }
    }
    /**
     * 解析商品列表页面
     *
     * @param crawlerRule 爬取规则
     * @param d
     * @return
     */
    private GoodsInfo getParsedGoodsInfo(CrawlerRule crawlerRule, Object d) {
        GoodsInfo crawlerInfo = new GoodsInfo();
        try {
            crawlerInfo.setGoodsId(JsonPath.read(d, "$.nid"));
            String goodsTitle = JsonPath.read(d, "$.raw_title");
            crawlerInfo.setGoodsTitle(goodsTitle);
            String goodsImage = "http:" + JsonPath.read(d, "$.pic_url");

            crawlerInfo.setGoodsImages(goodsImage);
            String goodsLink = "http:" + JsonPath.read(d, "$.detail_url");
            crawlerInfo.setGoodsLink(goodsLink);
            String goodsCurrentPrice = JsonPath.read(d, "$.view_price");
            crawlerInfo.setGoodsCurrentPrice(goodsCurrentPrice);
            if (StringUtils.isNotBlank(goodsCurrentPrice)) {
                crawlerInfo.setGoodsCalcPrice(Double.parseDouble(goodsCurrentPrice));
            }
            crawlerInfo.setShipAddress(JsonPath.read(d, "$.item_loc"));
            String goodsSalesCountStr = JsonPath.read(d, "$.view_sales");
            int goodsSalesCount = 0;
            if (goodsSalesCountStr.contains(CONTAINS_STR)) {
                goodsSalesCountStr = goodsSalesCountStr.replace(CONTAINS_STR, "").trim();
                goodsSalesCount = Integer.parseInt(goodsSalesCountStr);
            }

            crawlerInfo.setGoodsSaleCount(goodsSalesCount);
            crawlerInfo.setShopKeeper(JsonPath.read(d, "$.nick"));
            String shopLink = "http:" + JsonPath.read(d, "$.shopLink");
            crawlerInfo.setShopLink(shopLink);

            String shopId = shopLink.replace("http://store.taobao.com/shop/view_shop.htm?user_number_id=", "").trim();
            logger.info("\n商品链接==>{}\n商品名称==>{}\n商品现价==>{}", goodsLink, goodsTitle, goodsCurrentPrice);

            crawlerInfo.setShopId(shopId);
            crawlerInfo.setCrawlerTaskId(crawlerRule.getCrawlerTaskId());
            crawlerInfo.setCrawlerRuleId(crawlerRule.getId());
            crawlerInfo.setTaskNo(crawlerRule.getTaskNo());
            crawlerInfo.setRuleNo(crawlerRule.getRuleNo());
            crawlerInfo.setCreateBy(crawlerRule.getCreateBy());
            crawlerInfo.setCreateDate(crawlerRule.getCreateDate());

            crawlerInfo.setPlatformId(crawlerRule.getSpiderPlatformId());

            Date date = new Date();
            DateFormat format = new SimpleDateFormat("yyyyMMdd");
            String crawlerVersion = format.format(date);
            crawlerInfo.setCrawlerVersion(Integer.parseInt(crawlerVersion));

        } catch (Exception e) {
            logger.info("failed==>{} ", e.getMessage());
        }
        return crawlerInfo;
    }

在这里我获取商品数据总页数的时候,采用的方法是每次都获取一次数据,因为淘宝(天猫)的页码在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浏览器)方法,是无法杀死进程,我至今还是没寻找到其他好的方法,目前解决方法就是写个脚本,在凌晨统一杀死。。。
平时使用的话,我觉得以上数据内容应该足够了,所以店铺信息这方面,我在代码里面去除掉了,写的也比较笨重。。。

  • 4
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
爬虫(Web Crawler)是一种自动化程序,用于从互联网上收集信息。其主要功能是访问网页、提取数据并存储,以便后续分析或展示。爬虫通常由搜索引擎、数据挖掘工具、监测系统等应用于网络数据抓取的场景。 爬虫的工作流程包括以下几个关键步骤: URL收集: 爬虫从一个或多个初始URL开始,递归或迭代地发现新的URL,构建一个URL队列。这些URL可以通过链接分析、站点地图、搜索引擎等方式获取。 请求网页: 爬虫使用HTTP或其他协议向目标URL发起请求,获取网页的HTML内容。这通常通过HTTP请求库实现,如Python中的Requests库。 解析内容: 爬虫对获取的HTML进行解析,提取有用的信息。常用的解析工具有正则表达式、XPath、Beautiful Soup等。这些工具帮助爬虫定位和提取目标数据,如文本、图片、链接等。 数据存储: 爬虫将提取的数据存储到数据库、文件或其他存储介质中,以备后续分析或展示。常用的存储形式包括关系型数据库、NoSQL数据库、JSON文件等。 遵守规则: 为避免对网站造成过大负担或触发反爬虫机制,爬虫需要遵守网站的robots.txt协议,限制访问频率和深度,并模拟人类访问行为,如设置User-Agent。 反爬虫应对: 由于爬虫的存在,一些网站采取了反爬虫措施,如验证码、IP封锁等。爬虫工程师需要设计相应的策略来应对这些挑战。 爬虫在各个领域都有广泛的应用,包括搜索引擎索引、数据挖掘、价格监测、新闻聚合等。然而,使用爬虫需要遵守法律和伦理规范,尊重网站的使用政策,并确保对被访问网站的服务器负责。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值