这是Java爬虫系列博文的第五篇。上一篇Java爬虫服务器被封,不用慌,我们换个服务器,简单说一下反爬虫的策略和反爬虫的方法,主要是针对封IP以及相应的措施。在之前的文章中,我们已经讲了爬虫相关的基础知识。在这篇文章中,我们来谈谈与爬虫架构相关的内容。在前面的章节中,我们的爬虫程序都是单线程的。我们在调试爬虫程序的时候,单线程的爬虫是没有问题的,但是当我们在线上环境使用单线程的爬虫程序采集网页时,单线程暴露了两个致命的问题:采集效率是极慢,单线程都是串行的。下一个执行动作需要等待上一个动作执行完毕后才能执行。服务器CPU使用率不高。想一想,我们的服务器都是8个,核心16G和32G只跑一个线程是不是太浪费了?线上环境不能像我们本地测试。它不关心采集效率,只要能正确提取结果即可。在这个时间就是金钱的时代,不可能给你时间慢慢采集,所以单线程的爬虫程序是行不通的。我们需要将单线程模式改为多线程模式,以提高采集效率和计算机利用率。.多线程爬虫程序设计比单线程复杂很多,但与其他业务必须保证高并发下的数据安全不同,多线程爬虫对数据安全没有那么高的要求,因为每个页面访问都可以看作是一个独立的实体。做好多线程爬虫,必须做好两件事:第一是要采集的URL统一维护,第二是URL去重。下面就这两点简单说一下。维护要采集的网址多线程的爬虫程序不能像单线程一样,每个线程独立维护自己要采集的网址,如果这样的话,那么每个线程采集的网页都是一样的,你不是多线程采集,你多次采集一个页面。为此,我们需要统一维护要采集的URL。每个线程从统一的URL维护办公室接收收集到的URL,完成收集任务。如果在页面上发现新的URL链接,它将被添加到统一的URL维护容器中。以下是几个适合做URL统一维护的容器:JDK的安全队列,比如LinkedBlockingQueue高性能的NoSQL,比如Redis、MongodbMQ消息中间件URL去重URL去重也是多线程收集的关键步骤,因为如果我们不'去重,那么我们会收集到大量重复的url,这并不能提高我们的收集效率。比如一个分页的新闻列表,我们在采集第一页的时候可以得到2、3、4、5页,当采集到第二页的时候,就会得到1、3、4、5页的链接。会有一个待采集的URL队列中存在大量列表页链接,会造成重复采集甚至进入死循环,因此需要进行URL重复数据删除。有很多方法可以删除重复的URL。下面是几种常用的URL去重方法:将URL保存到数据库去重,如redis、MongoDB将URL放入哈希表去重,如hashsetURL通过MD5保存到哈希表去重后,与上面比较一、可以节省空间,使用布隆过滤器(BloomFilter)去重。这种方法可以节省很多空间,但不是那么准确。关于多线程爬虫的两个核心知识点我们都知道。下面我画了一个简单的多线程爬虫架构图,如下图所示:多线程爬虫架构图以上我们主要了解了多线程爬虫的架构设计。接下来,我们不妨试试Java多线程爬虫。下面以采集虎扑新闻为例,来实践一下Java多线程爬虫。Java多线程爬虫的设计目的是维护要采集的URL和移除URL,因为我们这里只是演示,所以我们使用JDK自带的容器来完成。我们使用LinkedBlockingQueue作为待收集的URL维护容器,HashSet作为URL去重容器。以下是Java多线程爬虫的核心代码,详细代码可以上传到github,地址在文末:/***多线程爬虫*/publicclassThreadCrawlerimplementsRunnable{//Numberof文章收集privatefinalAtomicLongpageCount=newAtomicLong(0);//列表页链接正则表达式公式publicstaticfinalStringURL_LIST="https://voice.hupu.com/nba";protectedLoggerlogger=LoggerFactory.getLogger(getClass());//待队列收集到的LinkedBlockingQueuetaskQueue;//收集到的链表HashSetvisited;//线程池CountableThreadPoolthreadPool;/****@paramurl起始页*@paramthreadNum线程数*@throwsInterruptedException*/publicThreadCrawler(Stringurl,intthreadNum)throwsInterruptedException{this.taskQueue=newLinkedBlockingQueue<>();this.threadPool=newCountableThreadPool(threadNum);this.visited=newHashSet<>();//将起始页加入待采集队列this.taskQueue.put(url);}@Overridepublicvoidrun(){logger.info("蜘蛛开始了!");while(!Thread.currentThread().isInterrupted()){//从队列中获取要采集的URLfinalStringrequest=taskQueue.poll();//如果获取到的请求为空,没有线程在当前线程集合中运行if(request==null){if(threadPool.getThreadAlive()==0){break;}}else{//执行采集任务threadPool.execute(newRunnable(){@Overridepublicvoidrun(){try{processRequest(request);}catch(Exceptione){logger.error("processrequest"+request+"error",e);}finally{//collectionpage+1pageCount.incrementAndGet();}}});}}threadPool.shutdown();logger.info("Spiderclosed!{}pagesdownloaded.",pageCount.get());}/***处理采集请求*@paramurl*/protectedvoidprocessRequest(Stringurl){//判断是否为列表页if(url.matches(URL_LIST)){//解析listpagefordetails添加页面链接到待采集的url队列processTaskQueue(url);}else{//分析网页processPage(url);}}/***处理链接采集*处理list页面,添加url到队列**@paramurl*/protectedvoidprocessTaskQueue(Stringurl){try{Documentdoc=Jsoup.connect(url).get();//详情页面链接Elementselements=doc.select("div.news-list>ul>li>div.list-hd>h4>a");elements.stream().forEach((element->{Stringrequest=element.attr("href");//判断是否r该链接存在于队列或收集集合中,如果不存在,将其添加到队列中if(!visited.contains(request)&&!taskQueue.contains(request)){try{taskQueue.put(request);}catch(InterruptedExceptione){e.printStackTrace();}}}));//列表页面链接Elementslist_urls=doc.select("div.voice-paging>a");list_urls.stream().forEach((element->{Stringrequest=element.absUrl("href");//判断是否满足要提取的列表链接的要求if(request.matches(URL_LIST)){//判断链接是否有aqueue或已被收集如果集合中不存在,则加入队列if(!visited.contains(request)&&!taskQueue.contains(request)){try{taskQueue.put(request);}catch(InterruptedExceptione){e.printStackTrace();}}}}));}catch(Exceptione){e.printStackTrace();}}/***解析页面**@paramurl*/protectedvoidprocessPage(Stringurl){try{Documentdoc=Jsoup.connect(url).get();Stringtitle=doc.select("body>div.hp-wrap>div.voice-main>div.artical-title>h1").first().ownText();System.out.println(Thread.currentThread().getName()+"At"+newDate()+"采集虎扑新闻"+title);//将采集到的url保存到采集集中visited.add(网址);}h(IOExceptione){e.printStackTrace();}}publicstaticvoidmain(String[]args){try{newThreadCrawler("https://voice.hupu.com/nba",5).run();}catch(InterruptedExceptione){e.printStackTrace();}}}我们用5个线程采集虎扑新闻列表页面看看效果如果运行程序,会得到如下结果:从多线程采集的结果可以看出,我们启动了5个线程采集了61页,总共耗时2秒。可以说效果还是不错的,跟单线程对比一下,看看差距有多大?我们把线程数设置为1,再次启动程序,得到如下结果:单线程运行结果可以看出单线程从虎扑搜集61条新闻用了7秒,几乎是多线程的4倍,你想想,这才61页,页数多了,差距会越来越大,所以多线程爬虫的效率还是很高的分布式??爬虫架构分布式爬虫架构是一种架构只需要s将被大型采集程序使用。一般来说,单机多线程就可以解决业务需求。反正我没有分布式爬虫项目的经验,所以这个我也无话可说,但是作为技术人员,我们还是有必要保持对技术的热情的。虽然没有必要,但了解一下也无妨。查阅了很多资料,得出的结论是:分布式爬虫架构和我们的多线程爬虫架构是一样的。在线程的基础上稍加改进就可以成为一个简单的分布式爬虫架构。由于分布式爬虫架构中的爬虫程序部署在不同的机器上,爬虫中无法存储待采集的URL和采集到的URL。在程序机器的内存中,我们需要维护在某台机器上,比如存储在Redis或者MongoDB中,每台机器从中获取集合链接,而不是像LinkedBlockingQueue那样从内存队列中取链接嘛,这样一个简单的分布式爬虫架构就出现了。当然这里面还有很多细节,因为我没有分布式架构的经验,所以不好说。如果您有兴趣,欢迎交流。