java抓取网页内容(接上简易爬虫配置池线程池(ThreadPoolTaskExecutor)抓取任务的方法)

优采云 发布时间: 2021-12-25 16:02

  java抓取网页内容(接上简易爬虫配置池线程池(ThreadPoolTaskExecutor)抓取任务的方法)

  继续上一节(爬虫系列(0):项目建设)

  网络爬虫是通过多线程和多任务逻辑实现的。线程池(ThreadPoolTask​​Executor)已经封装在springboot框架中,我们只需要使用它即可。

  在本节中,我们主要实现网页连接信息的多线程捕获,并将信息存储在队列中。

  介绍一个新的包

  在pom中引入新的包,如下:

  

org.apache.commons

commons-lang3

org.jsoup

jsoup

1.8.3

org.projectlombok

lombok

provided

  为了简化编码,这里引入lombok。IDE使用时需要安装lombok插件,否则会提示编译错误。

  配置管理

  springboot的配置文件统一管理在application.properties(.yml)中。这里我们也通过@ConfigurationProperties注解实现爬虫相关的配置。直接上传代码:

  package mobi.huanyuan.spider.config;

import lombok.Data;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**

* 爬虫配置.

*

* @author Jonathan L.(xingbing.lai@gmail.com)

* @version 1.0.0 -- Datetime: 2020/2/18 11:10

*/

@Data

@ConfigurationProperties(prefix = "huanyuan.spider")

public class SpiderConfig {

/**

* 爬取页面最大深度

*/

public int maxDepth = 2;

/**

* 下载页面线程数

*/

public int minerHtmlThreadNum = 2;

//=================================================

// 线程池配置

//=================================================

/**

* 核心线程池大小

*/

private int corePoolSize = 4;

/**

* 最大可创建的线程数

*/

private int maxPoolSize = 100;

/**

* 队列最大长度

*/

private int queueCapacity = 1000;

/**

* 线程池维护线程所允许的空闲时间

*/

private int keepAliveSeconds = 300;

}

  然后,需要修改这些配置,只需要修改里面的application.properties(.yml):

  

  魔猿简单爬虫配置

  线程池

  线程池使用现有的springboot,配置也可以在上面的配置管理中。这里只需要初始化配置:

  package mobi.huanyuan.spider.config;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

/**

* 线程池配置.

*

* @author Jonathan L.(xingbing.lai@gmail.com)

* @version 1.0.0 -- Datetime: 2020/2/18 11:35

*/

@Configuration

public class ThreadPoolConfig {

@Autowired

private SpiderConfig spiderConfig;

@Bean(name = "threadPoolTaskExecutor")

public ThreadPoolTaskExecutor threadPoolTaskExecutor() {

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

executor.setMaxPoolSize(spiderConfig.getMaxPoolSize());

executor.setCorePoolSize(spiderConfig.getCorePoolSize());

executor.setQueueCapacity(spiderConfig.getQueueCapacity());

executor.setKeepAliveSeconds(spiderConfig.getKeepAliveSeconds());

// 线程池对拒绝任务(无线程可用)的处理策略

executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

return executor;

}

}

  队列管理

  本节我们主要是抓取URL并保存到队列中,所以涉及到的队列就是要抓取的队列和要分析的队列(下节分析用到,这里只做存储),另外,为了防止重复抓取同一个URL,这里还需要添加一个Set集合来记录访问过的地址。

  package mobi.huanyuan.spider;

import lombok.Getter;

import mobi.huanyuan.spider.bean.SpiderHtml;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import java.util.HashSet;

import java.util.LinkedList;

import java.util.Queue;

import java.util.Set;

/**

* 爬虫访问队列.

*

* @author Jonathan L.(xingbing.lai@gmail.com)

* @version 1.0.0 -- Datetime: 2020/2/18 10:54

*/

public class SpiderQueue {

private static Logger logger = LoggerFactory.getLogger(SpiderQueue.class);

/**

* Set集合 保证每一个URL只访问一次

*/

private static volatile Set urlSet = new HashSet();

/**

* 待访问队列

* 爬取页面线程从这里取数据

*/

private static volatile Queue unVisited = new LinkedList();

/**

* 等待提取URL的分析页面队列

* 解析页面线程从这里取数据

*/

private static volatile Queue waitingMine = new LinkedList();

/**

* 添加到URL队列

*

* @param url

*/

public synchronized static void addUrlSet(String url) {

urlSet.add(url);

}

/**

* 获得URL队列大小

*

* @return

*/

public static int getUrlSetSize() {

return urlSet.size();

}

/**

* 添加到待访问队列,每个URL只访问一次

*

* @param spiderHtml

*/

public synchronized static void addUnVisited(SpiderHtml spiderHtml) {

if (null != spiderHtml && !urlSet.contains(spiderHtml.getUrl())) {

logger.info("添加到待访问队列[{}] 当前第[{}]层 当前线程[{}]", spiderHtml.getUrl(), spiderHtml.getDepth(), Thread.currentThread().getName());

unVisited.add(spiderHtml);

}

}

/**

* 待访问出队列

*

* @return

*/

public synchronized static SpiderHtml unVisitedPoll() {

return unVisited.poll();

}

/**

* 添加到等待提取URL的分析页面队列

*

* @param html

*/

public synchronized static void addWaitingMine(SpiderHtml html) {

waitingMine.add(html);

}

/**

* 等待提取URL的分析页面出队列

*

* @return

*/

public synchronized static SpiderHtml waitingMinePoll() {

return waitingMine.poll();

}

/**

* 等待提取URL的分析页面队列大小

* @return

*/

public static int waitingMineSize() {

return waitingMine.size();

}

}

  抓取任务

  直接上传代码:

  package mobi.huanyuan.spider.runable;

import mobi.huanyuan.spider.SpiderQueue;

import mobi.huanyuan.spider.bean.SpiderHtml;

import mobi.huanyuan.spider.config.SpiderConfig;

import org.apache.commons.lang3.StringUtils;

import org.jsoup.Connection;

import org.jsoup.Jsoup;

import org.jsoup.nodes.Document;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

/**

* 抓取页面任务.

*

* @author Jonathan L.(xingbing.lai@gmail.com)

* @version 1.0.0 -- Datetime: 2020/2/18 11:43

*/

public class SpiderHtmlRunnable implements Runnable {

private static final Logger logger = LoggerFactory.getLogger(SpiderHtmlRunnable.class);

private static boolean done = false;

private SpiderConfig config;

public SpiderHtmlRunnable(SpiderConfig config) {

this.config = config;

}

@Override

public void run() {

while (!SpiderHtmlRunnable.done) {

done = true;

minerHtml();

done = false;

}

}

public synchronized void minerHtml() {

SpiderHtml minerUrl = SpiderQueue.unVisitedPoll(); // 待访问出队列。

try {

//判断当前页面爬取深度

if (null == minerUrl || StringUtils.isBlank(minerUrl.getUrl()) || minerUrl.getDepth() > config.getMaxDepth()) {

return;

}

//判断爬取页面URL是否包含http

if (!minerUrl.getUrl().startsWith("http")) {

logger.info("当前爬取URL[{}]没有http", minerUrl.getUrl());

return;

}

logger.info("当前爬取页面[{}]爬取深度[{}] 当前线程 [{}]", minerUrl.getUrl(), minerUrl.getDepth(), Thread.currentThread().getName());

Connection conn = Jsoup.connect(minerUrl.getUrl());

conn.header("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.2.149.27 Safari/525.13");//配置模拟浏览器

Document doc = conn.get();

String page = doc.html();

SpiderHtml spiderHtml = new SpiderHtml();

spiderHtml.setUrl(minerUrl.getUrl());

spiderHtml.setHtml(page);

spiderHtml.setDepth(minerUrl.getDepth());

System.out.println(spiderHtml.getUrl());

// TODO: 添加到继续爬取队列

SpiderQueue.addWaitingMine(spiderHtml);

} catch (Exception e) {

logger.info("爬取页面失败 URL [{}]", minerUrl.getUrl());

logger.info("Error info [{}]", e.getMessage());

}

}

}

  这是一个 Runnable 任务。主要目的是拉取URL数据,然后将其封装成SpiderHtml对象,存放在待分析队列中。这里使用的是Jsoup——一个用于HTML分析操作的Java工具包。如果您不确定,您可以搜索它。后面的章节也会用到分析部分。

  其他页面信息包SpiderHtml

  package mobi.huanyuan.spider.bean;

import lombok.Data;

import java.io.Serializable;

/**

* 页面信息类.

*

* @author Jonathan L.(xingbing.lai@gmail.com)

* @version 1.0.0 -- Datetime: 2020/2/18 11:02

*/

@Data

public class SpiderHtml implements Serializable {

/**

* 页面URL

*/

private String url;

/**

* 页面信息

*/

private String html;

/**

* 爬取深度

*/

private int depth;

}

  爬行动物主类

  package mobi.huanyuan.spider;

import mobi.huanyuan.spider.bean.SpiderHtml;

import mobi.huanyuan.spider.config.SpiderConfig;

import mobi.huanyuan.spider.runable.SpiderHtmlRunnable;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**

* 爬虫.

*

* @author Jonathan L.(xingbing.lai@gmail.com)

* @version 1.0.0 -- Datetime: 2020/2/18 11:23

*/

@Component

public class Spider {

private static Logger logger = LoggerFactory.getLogger(Spider.class);

@Autowired

private ThreadPoolTaskExecutor threadPoolTaskExecutor;

@Autowired

private SpiderConfig spiderConfig;

public void start(SpiderHtml spiderHtml) {

//程序启动,将第一个起始页面放入待访问队列。

SpiderQueue.addUnVisited(spiderHtml);

//将URL 添加到URL队列 保证每个URL只访问一次

SpiderQueue.addUrlSet(spiderHtml.getUrl());

//download

for (int i = 0; i < spiderConfig.getMinerHtmlThreadNum(); i++) {

SpiderHtmlRunnable minerHtml = new SpiderHtmlRunnable(spiderConfig);

threadPoolTaskExecutor.execute(minerHtml);

}

// TODO: 监控爬取完毕之后停线程池,关闭程序

try {

TimeUnit.SECONDS.sleep(20);

logger.info("待分析URL队列大小: {}", SpiderQueue.waitingMineSize());

// 关闭线程池

threadPoolTaskExecutor.shutdown();

} catch (Exception e) {

e.printStackTrace();

}

}

}

  "// TODO:" 后面的代码逻辑在这里是暂时的。后续章节完成后,会慢慢移除。

  最后

  运行本节代码,需要在springboot项目的main方法中添加如下代码:

  ConfigurableApplicationContext context = SpringApplication.run(SpiderApplication.class, args);

Spider spider = context.getBean(Spider.class);

SpiderHtml startPage = new SpiderHtml();

startPage.setUrl("$URL");

startPage.setDepth(2);

spider.start(startPage);

  $URL 是需要抓取的网页地址。

  springboot项目启动后,需要手动停止。目前,没有在处理 fetch 后自动停止运行的逻辑。运行结果如下:

  

  Magic Ape Simple Crawler 运行结果

  最后,本章完成后,整个项目的结构如下:

  

  魔猿简单爬虫项目结构

  关于我

  

  编程界的老猿猴,自媒体界的新宠じ☆ve

  编程界的老猿猴,自媒体界的新宠じ☆ve

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线