手摸手学会使用webmagic爬虫框架

前言

最近正在做一个小说项目,所有的小说资源都是从笔趣阁中爬取,一开始使用的原生jsoup来解析爬取网页内容,但是代码杂乱冗余.

不经意间发现了webmagic这个爬虫框架,琢磨了两天发现确实简单好用,于是整理一篇博客跟大家一起分享学习

如果不了解Jsoup的同学可以先去附录看看jsoup案例讲解,这能更好的帮助我们入门webmagic

webmagic

概念

webmagic是什么?

webmagic是一个开源的Java垂直爬虫框架,目标是简化爬虫的开发流程,让开发者专注于逻辑功能的开发。webmagic的核心非常简单,但是覆盖爬虫的整个流程,也是很好的学习爬虫开发的材料。

webmagic的主要特色

  • 完全模块化的设计,强大的可扩展性。
  • 核心简单但是涵盖爬虫的全部流程,灵活而强大,也是学习爬虫入门的好材料。
  • 提供丰富的抽取页面API。
  • 无配置,但是可通过POJO+注解形式实现一个爬虫。
  • 支持多线程。
  • 支持分布式。
  • 支持爬取js动态渲染的页面。
  • 无框架依赖,可以灵活的嵌入到项目中去。

官网地址:http://webmagic.io/

官网讲解

webmagic的结构

在这里插入图片描述

官网描述:
WebMagic的结构分为Downloader、PageProcessor、Scheduler、Pipeline四大组件,并由Spider将它们彼此组织起来。

整个爬虫框架分为四大组件,最后由Spider进行协调管理,而这里的Spider可以具象为一句代码:

// 此处的代码看不懂没有关系,后面我们会进行深入探讨和了解 我们只需要知道
// 最后我们定义的WebMagic组件 都会在这一句代码进行统一注入和管理
public static void main(String[] args) {
    
    
    Spider.create(new GithubRepoPageProcessor())
            //从https://github.com/code4craft开始抓    
            .addUrl("https://github.com/code4craft")
            //设置Scheduler,使用Redis来管理URL队列
            .setScheduler(new RedisScheduler("localhost"))
            //设置Pipeline,将结果以json方式保存到文件
            .addPipeline(new JsonFilePipeline("D:\\data\\webmagic"))
            //开启5个线程同时执行
            .thread(5)
            //启动爬虫
            .run();
}

然后我们在来看看四大组件:
Downloader:

官网描述:
Downloader负责从互联网上下载页面,以便后续处理。WebMagic默认使用了Apache HttpClient作为下载工具。

扫描二维码关注公众号,回复: 13354127 查看本文章

PageProcessor

官网描述:
PageProcessor负责解析页面,抽取有用信息,以及发现新的链接。WebMagic使用Jsoup作为HTML解析工具,并基于其开发了解析XPath的工具Xsoup。

在这四个组件中,PageProcessor对于每个站点每个页面都不一样,是需要使用者定制的部分。

PageProcessor是用来解析页面的,我们如何定位到想要爬取的元素,如何获取下一页面地址,如何停止爬取,都统一在这个PageProcessor中定义

PageProcessor在代码中是一个接口,我们需要重写它

// 实现PageProcessor
public class TitleProcessor implements PageProcessor {
    
    
// 重写process方法
  @Override
  public void process(Page page) {
    
    
  }
}

Scheduler

官网描述;
Scheduler负责管理待抓取的URL,以及一些去重的工作。WebMagic默认提供了JDK的内存队列来管理URL,并用集合来进行去重。也支持使用Redis进行分布式管理。

除非项目有一些特殊的分布式需求,否则无需自己定制Scheduler。

Pipeline

官网描述:
Pipeline负责抽取结果的处理,包括计算、持久化到文件、数据库等。WebMagic默认提供了“输出到控制台”和“保存到文件”两种结果处理方案。

Pipeline定义了结果保存的方式,如果你要保存到指定数据库,则需要编写对应的Pipeline。对于一类需求一般只需编写一个Pipeline。

Pipeline定义的是我们爬取到的数据如何进行保存,它在代码中同样是一个我们需要去重写的接口

// 实现Pipeline接口
public class TitleProcessor implements Pipeline{
    
    
// 重写process方法
  @Override
  public void process(ResultItems resultItems, Task task) {
    
    
  
  }
}

使用Selectable抽取元素

在此之前,我们简单的了解完webmagic四大组件,知道了PageProcessor是用来解析页面的,那么当我们解析完页面如何进行抽取我们想要的内容呢?

这里我们可以使用Selectable

public class GithubRepoPageProcessor implements PageProcessor {
    
    

    @Override
    public void process(Page page) {
    
    
    // 从url中 获取author   regex() 代表可以使用正则表达式进行模糊匹配
        String author = page.getUrl().regex("https://github\\.com/(\\w+)/.*").toString();
        page.putField("author", author );
        // 从网页html中获取name
        page.putField("name", page.getHtml().xpath("//h1[@class='entry-title public']/strong/a/text()").toString());
        // 当我们获取网页的name 等于 null 时 则不继续爬取
        if (page.getResultItems().get("name")==null){
    
    
            //skip this page
            page.setSkip(true);
        }
        page.putField("readme", page.getHtml().xpath("//div[@id='readme']/tidyText()"));
    }

}

除了使用webmagic的方式从抽取元素,我们还可以使用jsoup的方式来进行抽取

它应该是这样的

public class GithubRepoPageProcessor implements PageProcessor {
    
    

   @Override
   public void process(Page page) {
    
    
    // 获取document 文档树对象
     Document document = page.getHtml().getDocument();
     // CSS选择器 获取值
     String title = document.select("#main > div.novelslist2 > ul").text();
     page.putField("title", title);
     if (page.getResultItems().get("title")==null){
    
    
            //skip this page
            page.setSkip(true);
        }
    }
}

熟悉Jsoup的同学看到可以使用Document的方式来抽取元素,是不是感到亲切些呢?

官网解释:
Selectable相关的抽取元素链式API是WebMagic的一个核心功能。使用Selectable接口,你可以直接完成页面元素的链式抽取,也无需去关心抽取的细节。
在刚才的例子中可以看到,page.getHtml()返回的是一个Html对象,它实现了Selectable接口。这个接口包含一些重要的方法,我将它分为两类:抽取部分和获取结果部分。

抽取部分API:

方法 说明 示例
xpath(String xpath) 使用XPath选择 html.xpath("//div[@class=‘title’]")
$(String selector) 使用Css选择器选择 html.$(“div.title”)
$(String selector,String attr) 使用Css选择器选择 html.$(“div.title”,“text”)
css(String selector) 功能同$(), 使用Css选择器选择 html.css(“div.title”)
links() 选择所有链接 html.links()
regex(String regex) 使用正则表达式抽取 html.regex("(.*?)")
regex(String regex,int group) 使用正则表达式抽取,并指定捕获组 html.regex("(.*?)",1)
replace(String regex, String replacement) 替换内容 html.replace("","")

使用Pipeline保存结果

在使用Selectable抽取到元素以后,我们就可以将我们的数据保存到数据库中了

public class GithubRepoPageProcessor implements PageProcessor, Pipeline {
    
    

  /**
  * 抽取数据
  */
   @Override
   public void process(Page page) {
    
    
    // 获取document 文档树对象
     Document document = page.getHtml().getDocument();
     // CSS选择器 获取值
     String title = document.select("#main > div.novelslist2 > ul").text();
     page.putField("title", title);
     if (page.getResultItems().get("title")==null){
    
    
            //skip this page
            page.setSkip(true);
        }
    }

  /**
   * 存储数据
   *
   * @param resultItems
   * @param task
   */
  @Override
  public void process(ResultItems resultItems, Task task) {
    
    
  String title = resultItems.get("title");
   log.info("获取到数据:"+title);
   // 这里可以写入存数据库的逻辑
  }
}

ResultItems 是可以map类型的key,value键值对

在我们抽取元素的时候,已经使用page.putField("title", title);方法存入完数据了!

所以我们可以使用resultItems.get("title");将它取出来

爬虫的配置、启动和终止

Spider是爬虫启动的入口。在启动爬虫之前,我们需要使用一个PageProcessor创建一个Spider对象,然后使用run()进行启动。同时Spider的其他组件(Downloader、Scheduler、Pipeline)都可以通过set方法来进行设置。

public static void main(String[] args) {
    
    
// 设置 PageProcessor()
    Spider.create(new PageProcessor())
            //从https://github.com/code4craft开始抓    
            .addUrl("https://github.com/code4craft")
            //设置Pipeline
            .addPipeline(new Pipeline())
            //开启5个线程同时执行
            .thread(5)
            //启动爬虫 run()方式启动	会阻塞当前线程执行 如果异步爬取 选择 start()启动
            .run();
}
方法 说明 示例
create(PageProcessor) 创建Spider Spider.create(new GithubRepoProcessor())
addUrl(String…) 添加初始的URL spider .addUrl(“http://webmagic.io/docs/”)
addRequest(Request…) 添加初始的Request spider .addRequest(“http://webmagic.io/docs/”)
thread(n) 开启n个线程 spider.thread(5)
run() 启动,会阻塞当前线程执行 spider.run()
start()/runAsync() 异步启动,当前线程继续执行 spider.start()
stop() 停止爬虫 spider.stop()
test(String) 抓取一个页面进行测试 spider .test(“http://webmagic.io/docs/”)
addPipeline(Pipeline) 添加一个Pipeline,一个Spider可以有多个Pipeline spider .addPipeline(new ConsolePipeline())
setScheduler(Scheduler) 设置Scheduler,一个Spider只能有个一个Scheduler spider.setScheduler(new RedisScheduler())
setDownloader(Downloader) 设置Downloader,一个Spider只能有个一个Downloader spider .setDownloader(new SeleniumDownloader())
get(String) 同步调用,并直接取得结果 ResultItems result = spider .get(“http://webmagic.io/docs/”)
getAll(String…) 同步调用,并直接取得一堆结果 List results = spider .getAll(“http://webmagic.io/docs/”, “http://webmagic.io/xxx”)

快速开始

项目前准备与说明

此demo项目,用爬取笔趣阁小说为案例进行讲解

在开始前我们需要准备:

  1. 笔趣阁网址https://www.bequgexs.com/search.html
  2. 定位与获取爬取内容的CSS选择器(如何获取CSS选择器,参考本文附录Jsoup)

在这里插入图片描述

文章列表页CSS选择器:

标题 CSS选择器
文章图片 #fmimg > img
标题 #info > h1
作者 #info > p:nth-child(2) > a
最新章节 #info > p:nth-child(5) > a
摘要 #intro
正文第一章地址 #list > dl > dd:nth-child(15) > a

在这里插入图片描述
文章页CSS选择器:

标题 CSS选择器
章节标题 #main > div > div > div.bookname > h1
下一章 #main > div > div > div.bookname > div.bottem1 > a.next
文章内容 #content

Maven导入依赖

WebMagic主要包含两个jar包:webmagic-core-{version}.jarwebmagic-extension-{version}.jar。在项目中添加这两个包的依赖,即可使用WebMagic。

如果整合springboot 建议排除log4j的依赖,否则会出现冲突

     <dependency>
            <groupId>us.codecraft</groupId>
            <artifactId>webmagic-core</artifactId>
            <version>0.7.3</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>us.codecraft</groupId>
            <artifactId>webmagic-extension</artifactId>
            <version>0.7.3</version>
        </dependency>

准备CSS选择器

提前准备好CSS选择器,由于这里使用了Lombok记得在maven依赖中进行注入

文章列表页 Css选择器

package com.tuxc.entity;

import lombok.Data;

/**
 * 文章列表页 Css选择器
 * @author tuxuchen
 * @date 2021/11/22 15:14
 */
@Data
public class TitleEntity {
    
    

  /**
   * 封面CSS选择器
   */
  private String imgSelector = "#fmimg > img";

  /**
   * 标题CSS选择器
   */
  private String articleNameSelector = "#info > h1";

  /**
   * 作者CSS选择器
   */
  private String authorSelector = "#info > p:nth-child(2) > a";

  /**
   * 摘要CSS选择器
   */
  private String abstractSelector = "#info > p:nth-child(5) > a";

  /**
   * 最新章节CSS选择器
   */
  private String latestChapterSelector = "#intro";

  /**
   * 第一章节CSS选择器
   */
  private String oneChapterSelector = "#list > dl > dd:nth-child(15) > a";

}

正文CSS选择器

package com.tuxc.entity;

import lombok.Data;

/**
 * 正文CSS选择器
 * @author tuxuchen
 * @date 2021/11/22 15:18
 */
@Data
public class ContentEntity {
    
    

  /**
   * 标题选择器
   */
  private String titleSelector = "#main > div > div > div.bookname > h1";

  /**
   * 正文选择器
   */
  private String contentSelector = "#content";

  /**
   * 链接选择器
   */
  private String nextChapterSelector = "#main > div > div > div.bookname > div.bottem1 > a.next";
}

PageProcessor组件编写

文章列表页爬取

/**
 * 文章列表页爬取
 *
 * @author tuxuchen
 * @date 2021/11/22 15:28
 */
public class TitleProcessor implements PageProcessor {
    
    

  /**
   * 文章列表页CSS选择器
   */
  private TitleEntity titleEntity;

  /**
   * 构造方法赋值
   *
   * @param titleEntity
   */
  public TitleProcessor(TitleEntity titleEntity){
    
    
    this.titleEntity = titleEntity;
  }

  @Override
  public void process(Page page) {
    
    
    Document document = page.getHtml().getDocument();
    // 获取封面
    page.putField("img", document.select(titleEntity.getImgSelector()).attr("abs:src"));
    // 获取标题
    page.putField("title", document.select(titleEntity.getArticleNameSelector()).text());
    // 获取作者
    page.putField("author", document.select(titleEntity.getAuthorSelector()).text());
    // 获取摘要
    page.putField("abstract", document.select(titleEntity.getAbstractSelector()).text());
    // 获取最新章节
    page.putField("latestChapter", document.select(titleEntity.getLatestChapterSelector()).text());
  }

  /**
   * 抓取配置
   * 固定参数 详细配置参考官网
   * @return
   */
  @Override
  public Site getSite() {
    
    
    /**
     * 抓取网站的相关配置,包括编码、抓取间隔、重试次数等
     */
    return Site
        .me()
        .setRetryTimes(3)
        .setSleepTime(500)
        .setCharset("UTF-8");
  }
}

正文内容爬取

/**
 * 正文内容爬取
 *
 * @author tuxuchen
 * @date 2021/11/22 15:28
 */
public class ContentProcessor implements PageProcessor {
    
    

  /**
   * 正文CSS选择器
   */
  private ContentEntity contentEntity;

  /**
   * 构造方法赋值
   *
   * @param contentEntity
   */
  public ContentProcessor(ContentEntity contentEntity){
    
    
    this.contentEntity = contentEntity;
  }

  @Override
  public void process(Page page) {
    
    
    Document document = page.getHtml().getDocument();
    // 文章标题
    page.putField("title", document.select(contentEntity.getTitleSelector()).text());
    // 如果爬取内容为空则停止爬取(说明小说已经被爬取完了)
    if (StringUtils.isBlank(page.getResultItems().get("title"))){
    
    
      page.setSkip(true);
    }
    // 正文 这里可以直接获取富文本
    page.putField("content", document.select(contentEntity.getContentSelector()).html());
    // 获取当前爬取url
    page.putField("url", page.getUrl().toString());
    //如何爬取下一链接
    page.addTargetRequests(page.getHtml().css(contentEntity.getNextChapterSelector()).links().all());
  }

  /**
   * 抓取配置
   * 固定参数 详细配置参考官网
   * @return
   */
  @Override
  public Site getSite() {
    
    
    /**
     * 抓取网站的相关配置,包括编码、抓取间隔、重试次数等
     */
    return Site
        .me()
        .setRetryTimes(3)
        .setSleepTime(500)
        .setCharset("UTF-8");
  }
}

Pipeline组件编写

文章列表页数据处理
/**
 * 文章列表页 数据处理
 * 
 * @author tuxuchen
 * @date 2021/11/22 15:45
 */
public class TitlePipeline implements Pipeline {
    
    

  /**
   * 数据处理
   * 我这里只是将数据取出来打印看一下 如果需要存入数据库 则注入mybatis的接口即可
   *
   * @param resultItems
   * @param task
   */
  @Override
  public void process(ResultItems resultItems, Task task) {
    
    
    // 获取标题
    String img = resultItems.get("img");
    System.out.println("获取到封面:" + img);
    System.out.println("获取到标题:" + resultItems.get("title"));
    System.out.println("获取到作者:" + resultItems.get("author"));
    System.out.println("获取到摘要:" + resultItems.get("abstract"));
    System.out.println("获取到最新章节:" + resultItems.get("latestChapter"));
  }
}
正文内容数据处理
/**
 * 正文数据处理
 *
 * @author tuxuchen
 * @date 2021/11/22 15:50
 */
public class ContentPipeline implements Pipeline {
    
    

  /**
   * 数据处理
   * 我这里只是将数据取出来打印看一下 如果需要存入数据库 则注入mybatis的接口即可
   *
   * @param resultItems
   * @param task
   */
  @Override
  public void process(ResultItems resultItems, Task task) {
    
    
    System.out.println("获取到正文标题:" + resultItems.get("title"));
    System.out.println("获取到正文;" + resultItems.get("content"));
    System.out.println("获取到当前url:" + resultItems.get("url"));
  }
}

Service接口编写

/**
 * 爬虫业务实现
 *
 * @author tuxuchen
 * @date 2021/11/22 15:26
 */
@Slf4j
public class CrawlerService {
    
    

  /**
   * 开启爬虫
   *
   * @param url 从什么地址开始爬取
   */
  public void enableCrawler(String url){
    
    
    /**
     * 1.先把列表页的数据
     * 2.列表页数据爬取完成以后 获取第一章链接
     * 3.爬取正文数据
     */
    TitleEntity title = new TitleEntity();
    Spider.create(new TitleProcessor(title))
        .addUrl(url)
        .addPipeline(new TitlePipeline())
        // 一个线程
        .thread(1)
        // 阻塞爬取
        .run();

    // 获取第一章链接地址
    Document document = getDocument(url);
    url = document.select(title.getOneChapterSelector()).get(0).attr("abs:href");

    Spider.create(new ContentProcessor(new ContentEntity()))
        .addUrl(url)
        .addPipeline(new ContentPipeline())
        // 5个线程
        .thread(5)
        // 异步爬取
        .start();
  }

  /**
   * 从url上获取文档,为了防止反爬虫,这是一些头字段
   * 如果失败,会重试10次
   *
   * @param url 爬取地址
   * @return document
   */
  private Document getDocument(String url) {
    
    
    // 重试次数
    int count = 10;
    boolean flag = true;
    Document document = null;
    while (flag) {
    
    
      try {
    
    
        document = Jsoup.connect(url)
            .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36")
            .get();
        flag = false;
      } catch (IOException e) {
    
    
        if (count-- != 0) {
    
    
          System.out.println("网页获取失败,原因:" + e.getMessage());
          System.out.println("开始第" + (10 - count) + "次重试");
        } else {
    
    
          System.out.println("获取文档未知异常:" + e.getMessage());
        }
      }
    }
    return document;
  }

}

测试运行

整体项目结构
在这里插入图片描述
编写启动类

/**
 * 爬虫启动类
 * @author tuxuchen
 * @date 2021/11/22 16:00
 */
public class CrawlerMain {
    
    

  public static void main(String[] args) {
    
    
    CrawlerService crawlerService = new CrawlerService();
    // 从什么路径开始爬取
    crawlerService.enableCrawler("https://www.bequgexs.com/0/2/");
  }

}

在这里插入图片描述

关于如何启动与停止爬虫

可以 将Spider对象在Map集合中进行存储起来
以便于进行后续操作

/**
 * 爬虫业务实现
 *
 * @author tuxuchen
 * @date 2021/11/22 15:26
 */
public class CrawlerService {
    
    

  /**
   * 同步开关
   */
  private Map<String, Spider> onOff;

  {
    
    
    onOff = new HashMap<>();
  }

  /**
   * 开启爬虫
   *
   * @param url 从什么地址开始爬取
   */
  public void enableCrawler(String url){
    
    
    /**
     * 1.先把列表页的数据
     * 2.列表页数据爬取完成以后 获取第一章链接
     * 3.爬取正文数据
     */
    TitleEntity title = new TitleEntity();
    Spider.create(new TitleProcessor(title))
        .addUrl(url)
        .addPipeline(new TitlePipeline())
        // 一个线程
        .thread(1)
        // 阻塞爬取
        .run();

    // 获取第一章链接地址
    Document document = getDocument(url);
    url = document.select(title.getOneChapterSelector()).get(0).attr("abs:href");

    // 将Spider对象在Map集合中进行存储起来
    Spider spider = Spider.create(new ContentProcessor(new ContentEntity()))
        .addUrl(url)
        .addPipeline(new ContentPipeline());
    onOff.put("spider01", spider);
    spider.thread(5).start();
  }

  // 传入Map中的key就可以停止了
  public void stopCrawler(String name){
    
    
    Spider spider = onOff.get(name);
    spider.stop();
  }

附录

Jsoup

前言

webmagic是对jsoup进一步封装及扩展,为了能够更好的学习webmagic,我们先了解完原生使用Jsoup是如何爬取网页的,再去学习webmagic.两者相互对比,效率更高

Jsoup简介

jsoup 是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。

Jsoup的主要功能
1)从一个URL,文件或字符串中解析HTML
2)使用DOM或CSS选择器来查找、取出数据
3)可操作HTML元素、属性、文本
注意:jsoup是基于MIT协议发布的,可放心使用于商业项目。

案例讲解

我们用搜索笔趣阁的文章为案例来进一步探讨jsoup

笔趣阁搜索网址:https://www.bequgexs.com/search.html?name=圣墟

页面分析

爬虫最重要的是html页面分析,我们只有了解完页面的结构,才能够定位到我们需要的数据,并且把它给取出来

在这里插入图片描述
1.当我们输入了想要搜索的小说名称以后,在列表中会查询出来我们想要的数据
在这里插入图片描述
2.当我们定位到,我们想要的数据,发现它是一个<a>标签来包裹的
3.同理 作者,最新章节等等,我们都能够在html文档节点中找到它们相应的位置
在这里插入图片描述
4.而整个搜索的内容是一个<ul>标签来包裹的

在这里插入图片描述
5.我们复制出<ul>的CSS选择器,以便定位的时候使用 示例:#main > div.novelslist2 > ul

创建项目导入依赖

创建Maven简单项目,导入jsoup依赖包

        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.12.1</version>
        </dependency>

demo编写

/**
 * jsoup测试案例
 * @author tuxuchen
 * @date 2021/11/18 18:17
 */
public class JsoupTest {
    
    

  public static void main(String[] args) {
    
    
    JsoupTest test = new JsoupTest();
    test.findSearch("圣墟");
  }

  /**
   * 搜索功能
   *
   * @param name
   * @return
   */
  public void findSearch(String name) {
    
    
    // 获取搜索页文档树
    Document document = getDocument("https://www.bequgexs.com/search.html?name=" + name);
    if (Objects.isNull(document)) {
    
    
      System.out.println("文档树获取失败");
      return;
    }
    // 取出 <ul> 标签内
    Element ul = document.select("#main > div.novelslist2 > ul").get(0);
    if (ul.isBlock()) {
    
     // 如果获取成功
      // 从<ul> 标签 取出 <li> 标签
      Elements li = ul.getElementsByTag("li");
      // 从1开始 遍历  为什么从1开始 是因为我们不需要第一个li标签 第一个li标签内是序号
      for (int i = 1; i < li.size(); i++) {
    
    
        Element e = li.get(i);
        // 取出 li 标签内的 <a> 标签 <a>内就是我们需要的内容
        Elements a = e.getElementsByTag("a");
        String book = "";
        // 遍历取出每个 <a> 标签
        for (int r = 0; r < a.size(); r++){
    
    
          String text = a.get(r).text();
          book = book + text + ":";
        }
        System.out.println(book);
      }
    }
  }

  /**
   * document 是浏览器对象 是文档树 这跟前端document是一样的 
   *
   * 从url上获取文档数,为了防止反爬虫,这是一些头字段
   * 如果失败,会重试10次
   *
   * @param url 爬取地址
   * @return document
   */
  private Document getDocument(String url) {
    
    
    // 重试次数
    int count = 10;
    boolean flag = true;
    Document document = null;
    while (flag) {
    
    
      try {
    
    
        document = Jsoup.connect(url)
            .userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36")
            .get();
        flag = false;
      } catch (IOException e) {
    
    
        if (count-- != 0) {
    
    
          System.out.println(("网页获取失败,原因:" + e.getMessage()));
          System.out.println("开始第" + (10 - count) + "次重试");
        } else {
    
    
          System.out.println("获取文档未知异常:" + e.getMessage());
        }
      }
    }
    return document;
  }

}

测试运行
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_46684099/article/details/121399635
今日推荐