朱延刚
(南京信息工程大学,江苏 南京 210044))
随着互联网技术的迅速发展,各类互联网平台大量涌现。 但这些平台在为网络用户提供丰富的媒体资源的同时,也增加了用户直接高效获取有用信息的难度,特别是具有行业性、专业性的信息,很容易淹没在信息的海洋中。 仅仅通过传统的搜索引擎获取的检索信息,已经无法满足更为专业的信息需求。 因此,如何对海量的信息进行归纳和提取变得十分重要。 目前,有很多针对特定场景设计的Java 爬虫系统,可以满足如图片下载[1]、特定技术主题或新闻媒体咨询整合等多种特定需求[2-4]。 这些爬虫系统的提出,都能针对特定的业务场景设计出针对性比较强的解决方案。 设计一种适用性和可扩展性更好的爬虫信息采集系统,不仅可以降低用户构建特定场景的爬虫系统设计难度,还能提高设计者的开发效率。 本文基于WebMagic 框架,提出了一种适用性和可扩展性更好的开发框架。
HttpClient 是Apache HttpComponentsTM项目负责创建和维护的一个基于HTTP 协议的Java 组件开发包。 HttpClient 与一般的浏览器不同,它不提供UI 界面,但是可以完成和浏览器相同的功能。 此外,还可以用来发送请求,接收服务器响应数据。 因此,HttpClient 通常被开发人员作为API 来调用。 Jsoup是一个用于处理HTML 的Java 库,可以提供一个非常便捷的API。 在网页爬虫中,Jsoup 主要用来从URL、文件或字符串中抓取和解析HTML,使用DOM 遍历或CSS 选择器查找和提取数据,操作 HTML 元素、属性和文本。 除此之外,它还能根据安全列表清理用户提交的内容,以防止 XSS 攻击。
SpringData 是被广泛使用的Web 开发框架Spring的重要组成部分,用于简化数据库的访问。 本文之所以介绍SpringData,是因为它具有强大的数据持久层的支持开发能力。 不仅支持关系型数据库的数据存储,比如JDBC,JPA 等,也支持非关系型的数据存储,比如Redis[5],ElasticSearch[6],Neo4j[7]等。 SpringData框架很好地提升了数据存储层面的适用性和可扩展性。
目前,主流的Java 爬虫框架主要有Nutch,Crawler4j,WebMagic,WebCollector 等。 这些爬虫框架都有各自的优缺点。 本文选择WebMagic 作为基本的爬虫框架,主要基于以下3 点:(1)WebMagic 的设计参考了业界比较成熟的爬虫框架 Scrapy;(2)WebMagic 支持多线程任务,能充分利用硬件资源,提高数据的处理速率;(3)WebMagic 的4 大组件Downloader,PageProcessor,Scheduler,Pipeline 对应了爬虫生命周期中的下载、处理、管理和持久化4 个过程。 对系统开发者来说,主要的业务代码通常在PageProcessor 完 成 即 可。 其 他 的3 大 组 件, 即Downloader,Scheduler,Pipeline,也具有定制性。 系统设计者可以根据自己的需求,来修改完善组件的代码。 因此,选择WebMagic 框架作为基础架构能够使整个系统具有更好的适应性和可用性。
本文提出的基于WebMagic 的爬虫框架,主要包含了3 个业务处理流程:页面数据获取、数据分析处理和数据存储持久化。 每个业务流程也会提交或者产生不同阶段的数据。 通过分析这些数据流,能促进系统设计者更好地理解系统结构,了解需要参与的实体,以及每个业务流程在每个阶段需要完成的明确任务和目标。 具体的结构如图1 所示。 本文将具体阐述每个框架结构部分,具体的设计思路。
对于页面数据获取而言,系统设计者在设计任何场景的网页爬虫系统时,都需要确定目标信息定位,明确将哪些站点平台作为信息来源,其选取的信息源的质量越高,得到的信息就越有价值。 因此系统设计者是否能选取合适的站点,对于获取数据页面信息是至关重要的。 一般网页获取分为开放式和注册登录式。 前者的浏览权限比较低,使用者通过访客模式就可以正常浏览站点信息,这种类型的页面数据获取比较容易。 相比之下,登录注册式的站点更加复杂,信息获取的流程更为烦琐。 本文针对登录注册式站点的页面数据获取流程做了简要阐述。
对于登录注册式的页面,用户可以进行常规的注册,然后获取用户名和密码,再通过框架的API 接口,模拟浏览器登录。 对于带有图片验证的模拟登录,其登录过程也是目前的一个难点。 对于站点的分类分页页面数据,系统设计者还要通过浏览器的开发者工具,分析HTTP 报文的请求头和响应头以及响应参数的特点。 设计者对请求头的请求参数进行分析可以帮助其确定需要提交哪些请求参数。 设计者通过点击分页发起请求时,对应的请求参数需要携带Cookie中的JSESSIONID,而当设计者对这一请求所产生的响应数据进行分析时,有时候可以得到一些JSON 字符串,也就是设计者需要提取的信息文本。
系统设计者获取了页面的数据之后,需要进一步地对页面数据进行分析处理。 这里的页面数据的主要形式就是服务器响应给请求端的HTML 标签,CSS样式表等字符源码。 为了更好地定位到需要的页面标签数据,系统设计者同样可以借助浏览器的开发者工具,通过点击元素检查按钮,迅速定位标签,提升数据分析效率。 在确定了具体的元素标签之后,系统设计者就可以借助WebMagic 3 种数据抽取技术(即XPath、CSS 选择器和正则表达式)对需要的页面数据进行分割提取,或者选择相对独立的Jsoup 开发库作为数据抽取工具。
基于WebMagic 的爬虫框架有一个比较大的优点,就是支持多线程任务。 系统设计者可以充分利用开发平台的硬件性能,减少数据抓取处理的时间,但是需要注意线程的安全性问题。 WebMagic 会自动提取页面出现的HTTP 的请求连接,然后把它们放到请求的队列中去。 如果有多个分类,每个分类还有多个页面,而且每个分类页面数据返回的时间也具有不确定性。 如果系统设计者在同一个PageProcessor 里处理业务逻辑,就要考虑多线程条件下的线程安全问题,否则将无法保证结果数据的准确可靠。
系统设计者在获得了目标数据之后,为了便于后期进一步对数据进行分析提取,就要解决数据的存储问题。 目前,数据存储主要有两种方案:关系型数据持久方案和非关系型数据持久方案。 系统设计者最终选取何种数据存储方案更合适,需要根据自身的业务需求和特点并结合下一步对数据处理需要采取的具体策略来确定。 关系型数据持久方案的优点是易于维护,支持通用的SQL 查询语句,且支持事务处理。但它也有缺点,即读写较慢,不适合对I/O 要求高的操作。 非关系型数据持久方案存储数据的结构灵活,还支持内存存储,所以读写性能较好,查询速度快,但它的缺点是不支持通用的SQL 查询,基本也不支持事务处理,且对于初学者来说,其学习成本也更高。
基于WebMagic 的爬虫框架使用了Pipeline 数据持久化组件,并提供了3 个实现类:用于向控制台输出的ConsolePipeline 类,用于向磁盘输出文件的FilePipeline 类,还有用于保存Json 格式文件的JsonFilePipeline 类。 系统设计者可以根据需要定制合适的Pipeline,以实现对数据的持久化存储。 系统设计者可以把Pipeline 作为一个可选的组件,因为WebMagic 作为一种Java 开发平台本身就拥有很好的灵活性,设计者可以使用个性化的数据持久化组件,例如,设计者可以把本文技术介绍部分提到的SpringData 框架作为组件,从而发挥该框架支持的多种数据类型的持久化功能。 系统设计者可以进一步提升系统的适用性和可扩展性以及对抓取数据后期的可用性。 这些系统提升有利于对数据进行更深层次的处理。
基于WebMagic 爬虫框架的系统设计中,一般不可缺少的组件是PageProcessor。 通过这个组件,系统设计者可以对页面数据分析处理逻辑进行设计,而具体的逻辑设计需要根据具体的业务需求来确定。PageProcessor 组件也定义实现了一些具有HttpClient功能的对象,比如Site 对象。 站点本身的一些配置信息,例如编码、HTTP 头、超时时间、重试策略和代理等,都可以通过设置Site 对象来进行配置,增强框架的功能。 下面展示了一个带分页的爬虫数据分析提取的例子,部分核心代码如下:
class DemoProcessor implements PageProcessor {
/ /站点对象参数设置
private Site site = Site.me()
.setCharset("UTF-8")/ /设置编码
.setTimeOut(10000)/ /超时时间
.setRetrySleepTime(3000)/ /重试时间
.setSleepTime(10);/ /重试次数
public Site getSite() {return site;}
public void process(Page page) {
/ /编写页面数据分析处理代码
/ /获取页面上的HTTP 链接
String pageUrl = page.getUrl().toString();
/ /将页面的HTML 标签通过Jsoup 生成一个可解析的文档
Document doc = Jsoup. parse(page. getHtml().toString());
/ /借助开发者工具定位标签元素解析数据
String div = doc.select("div[module-name=icbupc-productListPc]").attr("module-data");
/ /对数据进行URLDecode 解码
String decode = URLDecoder.decode(div);
/ /将字符串转化成JSON 对象进行解析
JSONObject jsonObject =JSON. parseObject(decode);
/ /获得当前分类是数据总数目
Integer totalLines = jsonObject. getJSONObject("mds")
. getJSONObject(" pageNavView"). getInteger("totalLines");
/ /获取当前分页的页码
Integer pageLines = jsonObject. getJSONObject("mds")
. getJSONObject(" pageNavView"). getInteger("pageLines");
/ /获取当前分类的总页数
int totalPage = (totalLines + pageLines - 1) /pageLines;
}}
Spider 对象是爬虫启动的入口对象。 在这个入口对象create 方法的内部,系统设计者可以传入完成的页面数据爬取业务实现类DemoProcessor,然后再调用run 方法进行启动。 此外,另一个在Spider 对象上比较常用的方法是addUrl,系统设计者可以用这个方法来添加初始的URL 地址参数。 该地址参数一般就是站点的入口地址。 Spider 对象的部分核心代码如下:
Spider. create (newDemoProcessor ( ))/ /传入PageProcessor 参数
.addUrl(URL)/ /初始的URL
.thread(10)/ /开启的线程数
.run();
Spider 对象还提供一个addPipeline 方法,顾名思义,这个方法主要是用于传入设计人员自定义的Pipeline 组件对象的。
关于如何把获取的提取数据进行持久化的问题,本文在框架结构设计部分已经做了阐述。 如果系统设计者将得到的目标数据保存成文件的形式,则不利于大数据量的管理和查询。 对此,本文认为,系统设计者需要对数据进行数据库的持久化设计。
3.3.1 通过定制Pipeline 的方式
通过Pipeline 有两种导入数据库的方式:注解方式和常规的代码方式。 系统设计者可以定义一个名为AnnotationObjectDaoPipeline 的注解类,具体代码如下:
@Component("AnnotationObjectDaoPipeline")
public class AnnotationObjectDaoPipeline implements PageModelPipeline
注 解 的 方 式 需 要 实 现 WebMagic 的PageModelPipeline 接口。 系统设计者通过泛型传入定义好的数据模型,最后在内部实现process 方法即可。
另一种是常规代码的实现方式。 系统设计者需要定义一个实现类CommonCodePipeline,具体代码如下:
public class CommonCodePipelineimplements Pipeline {}
这种方式也需要系统设计者重写内部的process方法。 两种方式可以根据设计者自身的需求,灵活应用。 Pipeline 其实就是将PageProcessor 的抽取结果,进行独立的处理。 之所以设计者可以考虑使用Pipeline 组件,主要是因为其具有两大优点:(1)这种方式实现了模块分离,代码结构比较清晰,而且解析抽取和数据保存各自占用独立的线程,互不干扰;(2)Pipeline 的功能比较固定,更容易做成通用组件。
3.3.2 自定义数据持久化接口
如果系统设计者认为使用Pipeline 组件比较烦琐,学习成本较高,也可以设计定义常规的数据接口层。 具体而言,设计者可以根据业务需求设计Service层和Dao 层,然后直接在PageProcessor 组件内部使用process 方法,增加数据保存的业务代码。 这种方式的优势是降低了学习成本,提高了系统设计开发的效率,但其缺点是增加了系统模块的耦合性,不利于后期的代码维护。
针对一般网页爬虫系统设计具有的特殊性和场景单一性,本文提出了一种基于WebMagic 框架的具有可适用性和可扩展性的系统设计。 系统设计人员若采用这一系统的设计方案,可以更好地搭建自己的爬虫项目,构建可用性和健壮性更好的数据系统。 本文提出的系统仍存在较大的可完善空间,比如设计者可以继续优化增量爬虫数据的备份以及解决去重问题等,以进一步提升系统功能的完善性。