学习用java基于webMagic+selenium+phantomjs实现爬虫Demo爬取淘宝搜索页面

 团队的技术栈以java为主,并且我的主语言是Java,研究时间不到一周。基于以上原因固放弃python,选择java为语言来进行开发。等之后有时间再尝试python来实现一个。

https://www.cnblogs.com/null-qige/p/7844381.html


       本次爬虫选用了webMagic+selenium+phantomjs,选用他们的原因如下:

  1.  webMagic(v:0.73),一个轻量级的Java爬虫框架(git地址:https://github.com/code4craft/webmagic,主页地址:http://webmagic.io/):

    • 时间和个人水平关系,放弃重复造轮子实现一套,固上网找一个框架(技术不好限制了我的风骚想法)
    • webMagic是一款国人实现的轻量级爬虫框架,注释肯定是中文(英文不好限制了我的选择范围)
    • 文档不少,而且用的人多,有问题网上好找解答
  2. selenium,一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中,就像真正的用户在操作一样,配合chromeDriver就可以通过代码对谷歌浏览器为所欲为了,阶段测试时候需要驱动谷歌浏览器的chromeDriver(附上下载地址:http://npm.taobao.org/mirrors/chromedriver/)

    • 因为本次爬取目标是淘宝网,淘宝使用的Js渲染加大了爬取的难度,如果根据接口去爬取,需要搞清楚各种接口参数在哪里,非常麻烦,而且可能某些参数实在找不到,所以选用selenium直接用浏览器渲染,降低了找不到参数的引起尴尬场面的风险
    • 但是用浏览器爬取必然会大大降低爬取效率,如果简单网站还是不建议使用这种方式
  3. phantomjs,一个基于webkit内核的无头浏览器,即没有UI界面,即它就是一个浏览器,只是其内的点击、翻页等人为相关操作需要程序设计实现。(附上下载地址:http://phantomjs.org/):

    • 如果每次爬取都要开启一个chrome浏览器,那简直就是内存噩梦,所以采用phantomjs来充当浏览器

   说完框架的选择, 我们先贴上设计图:

 然后我们再贴上使用的jar包:

按 Ctrl+C 复制代码
按 Ctrl+C 复制代码

  1、先让我们用chromeDriver测试一波selenium:

按 Ctrl+C 复制代码
按 Ctrl+C 复制代码

看结果:

经过上网查询,测试是chrome驱动器与我本地的chrome浏览器版本对不上,chromDriver和浏览器的对应版本如下:

chromedriver版本 支持的Chrome版本
v2.33 v60-62
v2.32 v59-61
v2.31 v58-60
v2.30 v58-60
v2.29 v56-58
v2.28 v55-57
v2.27 v54-56
v2.26 v53-55
v2.25 v53-55
v2.24 v52-54
v2.23 v51-53
v2.22 v49-52
v2.21 v46-50
v2.20 v43-48
v2.19 v43-47
v2.18 v43-46
v2.17 v42-43
v2.13 v42-45
v2.15 v40-43
v2.14 v39-42
v2.13 v38-41
v2.12 v36-40
v2.11 v36-40
v2.10 v33-36
v2.9 v31-34
v2.8 v30-33
v2.7 v30-33
v2.6 v29-32
v2.5 v29-32
v2.4 v29-32

重新下载chromeDriver后:浏览器按照预想开启查询关闭:ide控制台打印如下:

  2、然后我们来测试一波phantomJs:

  (其实中间有一段测试失败,但是忘了是什么原因了,就不去重现了)

按 Ctrl+C 复制代码
按 Ctrl+C 复制代码

看结果:

3、我们用WebMagic把他们整合:

controller:

复制代码
 @RequestMapping("/testTaoBaoSearch")
    public RestResultVo testTaoBaoSearch(String keyWord){
        Spider spider=Spider.create(new TestTaoBaoPageProcessor(keyWord));
        HttpClientDownloader httpClientDownloader = new HttpClientDownloader();
        //免费代理不稳定老挂
//        httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(new Proxy("0.0.0.0",0000)));
        spider.setDownloader(httpClientDownloader);
        spider.addUrl("https://s.taobao.com/search?q="+keyWord).thread(1).run();
        return null;
    }
复制代码

 TestTaoBaoPageProcessor:

复制代码
package com.chinaredstar.jc.crawler.biz.test.taobao;

import com.chinaredstar.jc.crawler.biz.test.chrome.TestChromeDriver;
import com.chinaredstar.jc.crawler.biz.test.phantomjs.TestPhantomJsDriver;
import org.apache.commons.lang.StringUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.selector.Html;
import us.codecraft.webmagic.selector.Selectable;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

/**
 *
 * @author zhuangj
 * @date 2017/11/13
 */
public class TestTaoBaoPageProcessor implements PageProcessor {

    private String keyWord;

    private static final String TAO_BAO_DETAIL_URL_START="https://item.taobao.com/item.htm";
    
    private static final String TIAN_MAO_DETAIL_URL_START="https://detail.tmall.com/item.htm";

    private Site site = Site
            .me()
            .setCharset("UTF-8")
            .setCycleRetryTimes(3)
            .setSleepTime(3 * 1000)
            .addHeader("Connection", "keep-alive")
            .addHeader("Cache-Control", "max-age=0")
            .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0");


    public String getKeyWord() {
        return keyWord;
    }

    public void setKeyWord(String keyWord) {
        this.keyWord = keyWord;
    }

    public TestTaoBaoPageProcessor() {
    }

    public TestTaoBaoPageProcessor(String keyWord) {
        this.keyWord = keyWord;
    }

    @Override
    public Site getSite() {
        return site;
    }




    @Override
    public void process(Page page) {
        WebDriver driver= TestPhantomJsDriver.getPhantomJSDriver();
//        WebDriver driver= null;
//        try {
//            driver = TestChromeDriver.getChromeDriver();
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
        driver.get(page.getRequest().getUrl());
        WebElement webElement = driver.findElement(By.id("page"));
        String str = webElement.getAttribute("outerHTML");

        Html html = new Html(str);
        if(isFirstPage(html)){
            analysisPagination(page,html);
        }else if(isListPage(html)){
            analysisListPage(page,html,driver);
        }else {
            analysisDetailPage(page,html,driver);
        }
    }

    private void analysisListPage(Page page, Html html, WebDriver driver) {
        List<String> detailPageList= html.xpath("//*[@id=\"mainsrp-itemlist\"]").$("a[id^='J_Itemlist_TLink_']").xpath("//a/@href").all();
        page.addTargetRequests(detailPageList);
    }

    /**
     * 分析分页规则
     * @param page
     * @param html
     */
    private void analysisPagination(Page page,Html html){
        List<String> pageList= html.xpath("//*[@id=\"mainsrp-pager\"]/div/div/div/ul/li/a/@data-value").all();
        pageList = new ArrayList(new HashSet(pageList));

        List<String> pageParameterList=new ArrayList<>();
        for(String  value:pageList){
            pageParameterList.add("https://s.taobao.com/search?q="+getKeyWord()+"&s="+value);
        }
        page.addTargetRequests(pageParameterList);
    }

    /**
     * 分析详情页
     * @param page
     * @param html
     * @param driver
     */
    private void analysisDetailPage(Page page,Html html,WebDriver driver){
       String url=page.getUrl().toString();
        if(url.startsWith(TAO_BAO_DETAIL_URL_START)){
            analysisTaoBaoDetailPage(page,html,driver);
        }else if(url.startsWith(TIAN_MAO_DETAIL_URL_START)){
            analysisTianMaoDetailPage(page,html,driver);
        }
    }

    /**
     * 分析淘宝详情页
     * @param page
     * @param html
     * @param driver
     */
    private void analysisTaoBaoDetailPage(Page page,Html html,WebDriver driver){
        page.putField("price", html.xpath("//[@id=\"J_StrPrice\"]/em[2]/text()").toString());
        page.putField("shopName", html.xpath("//*[@id=\"J_ShopInfo\"]/div/div[1]/div[1]/dl/dd/strong/a/text()").toString());
        page.putField("title", html.xpath("////*[@id=\"J_Title\"]/h3/text()").toString());

    }

    /**
     * 分析天猫详情页
     * @param page
     * @param html
     * @param driver
     */
    private void analysisTianMaoDetailPage(Page page,Html html,WebDriver driver){
        page.putField("price", html.xpath("//[@id=\"J_StrPriceModBox\"]/dd/span/text()").toString());
        page.putField("shopName", driver.findElement(By.name("seller_nickname")).getAttribute("value"));
        page.putField("name", html.xpath("//[@id=\"J_DetailMeta\"]/div[1]/div[1]/div/div[1]/h1/text()").toString());
    }



    /**
     * 是否为列表页
     * @param html
     * @return
     */
    private boolean isListPage(Html html) {
        String tmp = html.$("#mainsrp-pager").get();
        if (StringUtils.isNotBlank(tmp)) {
            return true;
        }
        return false;
    }

    /**
     * 列表页获取当前页码
     * @param html
     * @return
     */
    private String getCurrentPageNo(Html html){
        return html.xpath("//*[@id=\"mainsrp-pager\"]/div/div/div/ul/li[contains(@class,'active')]/span/text()").toString();
    }

    /**
     * 判断是否列表页的第一页
     * @param html
     * @return
     */
    private Boolean isFirstPage(Html html){
        return isListPage(html)&&getCurrentPageNo(html).equals("1");
    }





}
复制代码

 数据保存到数据库:

这里有几点:

    • WebMagic的抽取主要用到了Jsoup和webMagic作者开发的工具Xsoup,并不懂这个语法还自己学了下。xpath如果自己抓取绝对累死,可以用chrome浏览器自带功能
    • 使用chrome抓取时候数据正常,改为phantomJs时候数据错乱,一脸懵逼。为了查找原因,就到追溯爬虫爬取过程,修改了webMagic的Request类,给他添加parentUrl属性。

    • 复制代码
      package com.chinaredstar.jc.crawler.biz.test.extend;
      
      import us.codecraft.webmagic.Request;
      
      /**
       *
       * @author zhuangj
       * @date 2017/11/15
       */
      public class RequestExtend extends Request {
      
          private String parentUrl;
      
          public RequestExtend(String url,String parentUrl) {
              super(url);
              this.parentUrl = parentUrl;
          }
      
      
          public String getParentUrl() {
              return parentUrl;
          }
      
          public void setParentUrl(String parentUrl) {
              this.parentUrl = parentUrl;
          }
      }    
      复制代码
    •  分析方法的时候写上父级的URl,方便如果有数据问题进行追溯。
    • 复制代码
      private void analysisListPage(Page page, Html html, WebDriver driver) {
              RequestExtend requestExtend= (RequestExtend) page.getRequest();
              System.out.println("parentUrl:"+requestExtend.getParentUrl());
              System.out.println("nowUrl:"+page.getUrl());
              List<String> detailPageList= html.xpath("//*[@id=\"mainsrp-itemlist\"]").$("a[id^='J_Itemlist_TLink_']").xpath("//a/@href").all();
              for(String deetailPage:detailPageList){
                  RequestExtend extend=new RequestExtend("https:"+deetailPage,page.getUrl().toString());
                  page.addTargetRequest(extend);
              }
          }
      复制代码
    • 详情页获取父节点也可以完成。
    • 复制代码
      /**
           * 分析淘宝详情页
           * @param page
           * @param html
           * @param driver
           */
          private void analysisTaoBaoDetailPage(Page page,Html html,WebDriver driver){
              RequestExtend requestExtend= (RequestExtend) page.getRequest();
              System.out.println("parentUrl:"+requestExtend.getParentUrl());
              System.out.println("nowUrl:"+page.getUrl());
              page.putField("price", html.xpath("//[@id=\"J_StrPrice\"]/em[2]/text()").toString());
              String shopName=html.xpath("//*[@id=\"J_ShopInfo\"]/div/div[1]/div[1]/dl/dd/strong/a/text()").toString();
              if(StringUtils.isBlank(shopName)){
                  shopName=html.xpath("//*[@id=\"header-content\"]/div[2]/div[1]/div[1]/a/text()").toString();
              }
              page.putField("shopName", shopName);
              page.putField("title", html.xpath("////*[@id=\"J_Title\"]/h3/text()").toString());
      
              TaoBaoPo po=new TaoBaoPo();
              po.setParentUrl(requestExtend.getParentUrl());
              po.setPrice(page.getResultItems().get("price"));
              po.setShopName(page.getResultItems().get("shopName"));
              po.setTitle(page.getResultItems().get("title"));
              po.setUrl(page.getUrl().toString());
              taoBaoCrawlerService.saveProductDetail(po);
          }
      复制代码
    •  问题的排查结果是我查询时候keyWord是中文。chromeDriver可以处理而phantomJs不能识别,给加一个url转码:
    • 复制代码
      /**
           * 使用chromeDriver程序正常运行,转换成PhtanomJs后发现查询到的数据不是想要的数据,复制HTML查看页面后,
           * 发现搜索的数据是错乱的,搜索框上显示着???,猜测是转码的问题,经过URLEncode之后,程序正常运行。
           * @return
           */
          public String getKeyWord() {
              try {
                  return URLEncoder.encode(keyWord,"UTF-8");
              } catch (UnsupportedEncodingException e) {
                  e.printStackTrace();
              }
              return StringUtils.EMPTY;
          }
      复制代码
    • 测试的时候代码越跑越慢,打开进程发现一堆phantomJs,固在process结束之后调用driver.quit()关闭phantomJs。

结语:本次demo虽然能跑起来但是还有很多很多不足,每次爬取搜索页只爬了四页,搜索的第一页也没爬,但是懒得再弄,保存数据应该用webMagic的Pipeline来处理,而且频繁的单次保存可以想办法用批量保存来替换;频繁的创建销毁driverJs并不合理,应该使用池化技术做一个DriverPool来管理;demo没有使用IP代理,容易被封,应该写一个IP代理池来管理第三方ip,不过要钱所以就算了。毕竟只是一个demo,剩余问题就不一一列举。

猜你喜欢

转载自blog.csdn.net/weixin_39650971/article/details/79570038