说到爬虫,大家可以想到Python中大名鼎鼎的Scrapy框架。在这篇文章中,我们参考了这个设计思路,使用Java语言实现了一个自己的爬虫框架(lun)框架(zi)。我们从起点开始一步步分析爬虫框架的诞生过程。这个爬虫框架的源码我放在了github上,里面有几个例子可以运行。关于爬虫的一切我们来介绍一下什么是爬虫?以及爬虫框架的设计和遇到的问题。什么是爬行动物?“爬行者”不是生活在土壤中的小虫子。网络爬虫(webcrawler),也叫网络蜘蛛(spider),是一种用来自动浏览互联网内容的机器人。爬虫访问网站的过程会消耗目标系统资源,很多网站是不允许??爬虫爬取的(这个就是你遇到过的robots.txt文件,这个文件可以要求robots只索引网站的一部分,也可以不做)完全处理)。所以爬虫在访问大量页面的时候需要兼顾规划、加载和“礼貌”(慢点,兄弟)。互联网上的页面太多了,即使是最好的爬虫系统也无法做出完整的索引。所以在公元2000年之前的万维网初期,搜索引擎往往找不到很多相关的结果。目前的搜索引擎在这方面已经有了很大的改进,可以立即给出高质量的结果。网络爬虫会遇到的问题既然有人要爬,就会有人要防御。网络爬虫在运行过程中会遇到一些障碍,业界称之为反爬虫策略。让我们列出一些常见的。访问频率限制Header头信息验证动态页面生成IP地址限制Cookie限制(或登录限制)验证码限制等...这些都是传统的反爬虫手段,当然以后会更先进,技术创新总会有的驱动多个行业的发展,毕竟AI时代已经到来,爬虫与反爬虫的斗争还在继续。爬虫框架在设计我们的框架时应该考虑什么我们想设计一个爬虫框架,它是基于Scrapy的设计思想。首先我们来看看在没有爬虫框架的情况下,我们是如何爬取页面信息的。一个常见的例子是使用HttpClient包或Jsoup,这对于一个简单的小爬虫来说已经足够了。下面演示一段代码,在没有爬虫框架的情况下抓取页面。这个就是publicclassReptile{publicstaticvoidmain(String[]args){//传入你要爬取的页面地址Stringurl1="";//创建一个读取流的输入流InputStreamis=null;//对流进行封装加快读取速度BufferedReaderbr=null;//用来保存读取页面的数据写入temp;Stringtemp="";try{//获取URL;URLurl2=newURL(url1);//打开流,准备开始读取数据;is=url2.openStream();//将流包装成字符流,调用br.readLine()提高读取效率,读取一个一次排队;br=newBufferedReader(newInputStreamReader(is));//读取数据,调用br.readLine()方法每次读取一行数据赋值给temp。如果没有数据,则value==null,跳出循环;while((temp=br.readLine())!=null){//将temp的值追加到html中。这里注意String和StringBuffere的区别在于前者不可变,后者可变;html.append(temp);}//下一步是关闭流,防止资源浪费;if(is!=null){is.close();is=null;}//将页面通过Jsoup传递生成文档对象;documentdoc=Jsoup.parse(html.toString());//通过类名(即XX)获取,一个数组对象Elements包含了我们要的数据,至于这个div的值,可以打开浏览器按F12知道;Elementselements=doc.getElementsByClass("XX");for(Elementelement:elements){//打印出每个节点的信息;你可以有选择地保留你想要的数据,通常得到一个固定的索引引用;System.out.println(element.text());}}catch(MalformedURLExceptione){e.printStackTrace();}catch(IOExceptione){e.printStackTrace();}}从如此丰富的评论中,我感受到了当作者的耐心到了,我们来分析一下这个爬虫到底在干什么?输入一个要爬取的URL地址,通过JDK原生API发送网络请求获取页面信息(这里没有使用HttpClient)使用Jsoup解析DOM处理你需要的数据并输出控制台,代码非常简洁。我们设计框架的目的就是统一这些流程,抽象出通用的功能,减少重复工作,加入一些没有考虑到的因素。那么设计一个爬虫框架应该由什么组成呢?URL管理器、网页下载器、爬虫调度器、网页解析器、数据处理器分别讲解各个组件的功能。URL管理器爬虫框架必须处理大量的URL。我们需要设计一个队列来存放所有需要处理的URL。这种先进先出的数据结构很适合这种需求。将所有要下载的url存放在pending队列中,每下载一次取出一个,队列中少一个。我们知道有些URL下载是有反爬虫策略的,所以需要对这些请求做一些特殊的设置,然后封装URL来提取Request。网页下载器从前面的简单例子可以看出,如果没有网页下载器,用户将不得不编写网络请求的处理代码,这无疑是对每个URL的相同动作。所以在框架设计中,我们直接添加即可。至于用什么库下载,可以用httpclient或者okhttp。在这篇文章中,我们使用了一个超轻量级的网络请求库oh-my-request(是的,这就是我接下来所做的)。一个优秀的框架设计会将这个下载组件设置为可替换的,并提供默认的。爬虫调度器调度器与我们在web应用中开发的控制器是一个类似的概念,用于下载器和解析器之间的流处理。解析器可以解析更多的URL发送给调度器,调度器再传给下载器,这样各个组件就可以有序的工作了。网页解析器我们知道,当一个页面被下载时,它是由一段HTMLDOM字符串表示的,但是仍然需要提取真正需要的数据。之前的方法是通过StringAPI或者正则表达式在DOM中进行搜索。很麻烦,框架应该提供一个合理的、通用的、方便的方式来帮助用户完成数据的提取。常用的方法是通过xpath或者css选择器从DOM中提取,学习这个技巧几乎适用于所有的爬虫框架。数据处理器常见的爬虫程序结合了网页解析器和数据处理器,解析后立即对数据进行处理。在一个标准化的爬虫程序中,他们应该各司其职。我们首先通过解析器解析需要的数据,这些数据可能会被封装成对象。然后将其传递给数据处理器。处理器收到数据后,可能存入数据库,也可能通过接口发送给老王。BasicFeatures上面说了这么多,我们设计的爬虫框架有以下几个特点,不是大而全,堪称轻量级和迷你型,非常好用。易于定制:许多网站的下载频率和浏览器要求各不相同。爬虫框架需要在这里提供扩展配置。多线程下载:当CPU核心数较多时,多线程下载可以更快的完成任务。支持XPath和CSS选择器解析整个架构图流程与Scrapy一致,但简化了一些运行引擎(Engine):用于处理整个系统的数据流处理,触发事务(框架核心)调度器(Scheduler):用于接受引擎的请求,推入队列,引擎再次请求时返回。可以想象成一个URL(抓取网页的URL或链接)的优先级队列,决定下一个抓取的URL是什么,同时RemoveduplicateURLDownloader(下载器):用于下载网页内容并返回将网页内容交给调度程序所谓的实体(Item)。用户也可以从中提取链接,让框架继续抓取下一个页面Item管道(Pipeline):负责处理爬虫从网页中提取的实体。主要功能是持久化实体,验证实体的合法性,清除不必要的信息。当页面被爬虫解析后,会被发送到项目管道,数据会按照几个特定的??顺序进行处理。执行流程图首先,引擎从调度器中取出一个链接(URL)供下一个爬虫引擎将URL封装成一个请求(Request)传给下载器,下载器下载资源并封装成一个响应包(Response)之后,如果爬虫解析Response,发现一个实体(Item),就会交给实体管道做进一步的处理。如果解析出链接(URL),将URL交给Scheduler,等待项目结构被抓取。该项目是使用Maven3和Java8构建的。代码结构如下:.└──elves├──Elves.java├──ElvesEngine。java├──config├──download├──event├──pipeline├──request├──response├──scheduler├──spider└──utils编码要点理解了前面的设计思路后,编程只是一个理所当然至于怎么写,要考虑程序员对编程语言的使用和架构思维的熟练程度。优秀的代码来自经验和优化。让我们看一下几个框架中的一些代码示例。利用观察者模式的思想实现事件驱动函数Config>consumer){List>)(item,request)->log.info("保存到文件:{}",item));}publicResultparse(Responseresponse){Result
>result=newResult<>();Elementselements=response.body().css("#contenttable.pl2a");List
