温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Java 中怎么实现一个多线程爬虫

发布时间:2021-06-30 18:11:39 来源:亿速云 阅读:351 作者:Leah 栏目:开发技术

本篇文章为大家展示了Java 中怎么实现一个多线程爬虫,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。

维护待采集的 URL

多线程爬虫程序就不能像单线程那样,每个线程独自维护这自己的待采集  URL,如果这样的话,那么每个线程采集的网页将是一样的,你这就不是多线程采集啦,你这是将一个页面采集的多次。基于这个原因我们就需要将待采集的 URL  统一维护,每个线程从统一 URL 维护处领取采集 URL ,完成采集任务,如果在页面上发现新的 URL 链接则添加到 统一 URL  维护的容器中。下面是几种适合用作统一 URL 维护的容器:

  • JDK 的安全队列,例如 LinkedBlockingQueue

  • 高性能的 NoSQL,比如 RedisMongodb

  • MQ 消息中间件

URL 的去重

URL 的去重也是多线程采集的关键一步,因为如果不去重的话,那么我们将采集到大量重复的  URL,这样并没有提升我们的采集效率,比如一个分页的新闻列表,我们在采集第一页的时候可以得到 2、3、4、5 页的链接,在采集第二页的时候又会得到  1、3、4、5 页的链接,待采集的 URL 队列中将存在大量的列表页链接,这样就会重复采集甚至进入到一个死循环当中,所以就需要 URL 去重。URL  去重的方法就非常多啦,下面是几种常用的 URL 去重方式:

  • 将 URL 保存到数据库进行去重,比如 redis、MongoDB

  • 将 URL 放到哈希表中去重,例如 hashset

  • 将 URL 经过 MD5 之后保存到哈希表中去重,相比于上面一种,能够节约空间

  • 使用 布隆过滤器(Bloom Filter)去重,这种方式能够节约大量的空间,就是不那么准确。

关于多线程爬虫的两个核心知识点我们都知道啦,下面我画了一个简单的多线程爬虫架构图,如下图所示: 

Java 中怎么实现一个多线程爬虫

多线程爬虫架构图

上面我们主要了解了多线程爬虫的架构设计,接下来我们不妨来试试 Java 多线程爬虫,我们以采集虎扑新闻为例来实战一下 Java 多线程爬虫,Java  多线程爬虫中设计到了 待采集 URL 的维护和 URL 去重,由于我们这里只是演示,所以我们就使用 JDK 内置的容器来完成,我们使用  LinkedBlockingQueue 作为待采集 URL 维护容器,HashSet 作为 URL 去重容器。下面是 Java  多线程爬虫核心代码,详细代码以上传 GitHub,地址在文末:

/**  * 多线程爬虫  */ public class ThreadCrawler implements Runnable {     // 采集的文章数     private final AtomicLong pageCount = new AtomicLong(0);     // 列表页链接正则表达式     public static final String URL_LIST = "https://voice.hupu.com/nba";     protected Logger logger = LoggerFactory.getLogger(getClass());     // 待采集的队列     LinkedBlockingQueue<String> taskQueue;     // 采集过的链接列表     HashSet<String> visited;     // 线程池     CountableThreadPool threadPool;     /**      *      * @param url 起始页      * @param threadNum 线程数      * @throws InterruptedException      */     public ThreadCrawler(String url, int threadNum) throws InterruptedException {         this.taskQueue = new LinkedBlockingQueue<>();         this.threadPool = new CountableThreadPool(threadNum);         this.visited = new HashSet<>();         // 将起始页添加到待采集队列中         this.taskQueue.put(url);     }      @Override     public void run() {         logger.info("Spider started!");         while (!Thread.currentThread().isInterrupted()) {             // 从队列中获取待采集 URL             final String request = taskQueue.poll();             // 如果获取 request 为空,并且当前的线程采已经没有线程在运行             if (request == null) {                 if (threadPool.getThreadAlive() == 0) {                     break;                 }             } else {                 // 执行采集任务                 threadPool.execute(new Runnable() {                     @Override                     public void run() {                         try {                             processRequest(request);                         } catch (Exception e) {                             logger.error("process request " + request + " error", e);                         } finally {                             // 采集页面 +1                             pageCount.incrementAndGet();                         }                     }                 });             }         }         threadPool.shutdown();         logger.info("Spider closed! {} pages downloaded.", pageCount.get());     }      /**      * 处理采集请求      * @param url      */     protected void processRequest(String url) {         // 判断是否为列表页         if (url.matches(URL_LIST)) {             // 列表页解析出详情页链接添加到待采集URL队列中             processTaskQueue(url);         } else {             // 解析网页             processPage(url);         }     }     /**      * 处理链接采集      * 处理列表页,将 url 添加到队列中      *      * @param url      */     protected void processTaskQueue(String url) {         try {             Document doc = Jsoup.connect(url).get();             // 详情页链接             Elements elements = doc.select(" div.news-list > ul > li > div.list-hd > h5 > a");             elements.stream().forEach((element -> {                 String request = element.attr("href");                 // 判断该链接是否存在队列或者已采集的 set 中,不存在则添加到队列中                 if (!visited.contains(request) && !taskQueue.contains(request)) {                     try {                         taskQueue.put(request);                     } catch (InterruptedException e) {                         e.printStackTrace();                     }                 }             }));             // 列表页链接             Elements list_urls = doc.select("div.voice-paging > a");             list_urls.stream().forEach((element -> {                 String request = element.absUrl("href");                 // 判断是否符合要提取的列表链接要求                 if (request.matches(URL_LIST)) {                     // 判断该链接是否存在队列或者已采集的 set 中,不存在则添加到队列中                     if (!visited.contains(request) && !taskQueue.contains(request)) {                         try {                             taskQueue.put(request);                         } catch (InterruptedException e) {                             e.printStackTrace();                         }                     }                 }             }));          } catch (Exception e) {             e.printStackTrace();         }     }     /**      * 解析页面      *      * @param url      */     protected void processPage(String url) {         try {             Document doc = Jsoup.connect(url).get();             String title = doc.select("body > div.hp-wrap > div.voice-main > div.artical-title > h2").first().ownText();              System.out.println(Thread.currentThread().getName() + " 在 " + new Date() + " 采集了虎扑新闻 " + title);             // 将采集完的 url 存入到已经采集的 set 中             visited.add(url);          } catch (IOException e) {             e.printStackTrace();         }     }      public static void main(String[] args) {          try {             new ThreadCrawler("https://voice.hupu.com/nba", 5).run();         } catch (InterruptedException e) {             e.printStackTrace();         }     } }

我们用 5 个线程去采集虎扑新闻列表页看看效果如果?运行该程序,得到如下结果:

Java 中怎么实现一个多线程爬虫


多线程采集结果

结果中可以看出,我们启动了 5 个线程采集了 61 页页面,一共耗时 2  秒钟,可以说效果还是不错的,我们来跟单线程对比一下,看看差距有多大?我们将线程数设置为 1 ,再次启动程序,得到如下结果:

Java 中怎么实现一个多线程爬虫

单线程运行结果

可以看出单线程采集虎扑 61 条新闻花费了 7 秒钟,耗时差不多是多线程的 4 倍,你想想这可只是 61  个页面,页面更多的话,差距会越来越大,所以多线程爬虫效率还是非常高的。

上述内容就是Java 中怎么实现一个多线程爬虫,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注亿速云行业资讯频道。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI