本文是WebMagic文档的一部分。系列文章写完后,会整合到WebMagic新版文档中。css
在WebMagic里,实现一个基本的爬虫只须要编写一个类,实现PageProcessor
接口便可。这个类基本上包含了抓取一个网站,你须要写的全部代码。html
以以前的GithubRepoPageProcessor
为例,我将PageProcessor的定制分为三个部分,分别是爬虫的配置、页面元素的抽取和连接的发现。前端
public class GithubRepoPageProcessor implements PageProcessor { // 部分一:抓取网站的相关配置,包括编码、抓取间隔、重试次数等 private Site site = Site.me().setRetryTimes(3).setSleepTime(1000); @Override // process是定制爬虫逻辑的核心接口,在这里编写抽取逻辑 public void process(Page page) { // 部分二:定义如何抽取页面信息,并保存下来 page.putField("author", page.getUrl().regex("https://github\\.com/(\\w+)/.*").toString()); page.putField("name", page.getHtml().xpath("//h1[@class='entry-title public']/strong/a/text()").toString()); if (page.getResultItems().get("name") == null) { //skip this page page.setSkip(true); } page.putField("readme", page.getHtml().xpath("//div[@id='readme']/tidyText()")); // 部分三:从页面发现后续的url地址来抓取 page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all()); } @Override public Site getSite() { return site; } public static void main(String[] args) { Spider.create(new GithubRepoPageProcessor()) //从"https://github.com/code4craft"开始抓 .addUrl("https://github.com/code4craft") //开启5个线程抓取 .thread(5) //启动爬虫 .run(); } }
第一部分关于爬虫的配置,包括编码、抓取间隔、超时时间、重试次数等,也包括一些模拟的参数,例如User Agent、cookie,以及代理的设置,咱们会在第5章-“爬虫的配置”里进行介绍。在这里咱们先简单设置一下:重试次数为3次,抓取间隔为一秒。java
第二部分是爬虫的核心部分:对于下载到的Html页面,你如何从中抽取到你想要的信息?WebMagic里主要使用了三种抽取技术:XPath、正则表达式和CSS选择器。git
XPathgithub
XPath原本是用于XML中获取元素的一种查询语言,可是用于Html也是比较方便的。例如:web
page.getHtml().xpath("//h1[@class='entry-title public']/strong/a/text()")
这段代码使用了XPath,它的意思是“查找全部class属性为'entry-title public'的h1元素,并找到他的strong子节点的a子节点,并提取a节点的文本信息”。 对应的Html是这样子的:正则表达式
CSS选择器数据库
CSS选择器是与XPath相似的语言。若是你们作过前端开发,确定知道$('h1.entry-title')这种写法的含义。客观的说,它比XPath写起来要简单一些,可是若是写复杂一点的抽取规则,就相对要麻烦一点。cookie
正则表达式
正则表达式则是一种通用的文本抽取语言。
page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all());
这段代码就用到了正则表达式,它表示匹配全部"https://github.com/code4craft/webmagic"这样的连接。
XPath、CSS选择器和正则表达式的具体用法会在第4章“抽取工具详解”中讲到。
有了处理页面的逻辑,咱们的爬虫就接近完工了!
可是如今还有一个问题:一个站点的页面是不少的,一开始咱们不可能所有列举出来,因而如何发现后续的连接,是一个爬虫不可缺乏的一部分。
page.addTargetRequests(page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all());
这段代码的分为两部分,page.getHtml().links().regex("(https://github\\.com/\\w+/\\w+)").all()
用于获取全部知足"(https://github\.com/\w+/\w+)"这个正则表达式的连接,page.addTargetRequests()
则将这些连接加入到待抓取的队列中去。
Selectable
相关的链式API是WebMagic的一个核心功能。使用Selectable接口,你能够直接完成页面元素的链式抽取,也无需去关心抽取的细节。
在刚才的例子中能够看到,page.getHtml()返回的是一个Html
对象,它实现了Selectable
接口。这个接口包含一些重要的方法,我将它分为两类:抽取部分和获取结果部分。
方法 | 说明 | 示例 |
---|---|---|
xpath(String xpath) | 使用XPath选择 | html.xpath("//div[@class='title']") |
$(String selector) | 使用Css选择器选择 | html.$("div.title") |
$(String selector,String attr) | 使用Css选择器选择 | html.$("div.title","text") |
css(String selector) | 功能同$(),使用Css选择器选择 | html.css("div.title") |
links() | 选择全部连接 | html.links() |
regex(String regex) | 使用正则表达式抽取 | html.regex("<div>(.*?)</div>") |
regex(String regex,int group) | 使用正则表达式抽取,并指定捕获组 | html.regex("<div>(.*?)</div>",1) |
replace(String regex, String replacement) | 替换内容 | html.replace("<script>.*</script>","") |
这部分抽取API返回的都是一个Selectable
接口,意思是说,抽取是支持链式调用的。下面我用一个实例来说解链式API的使用。
例如,我如今要抓取github上全部的Java项目,这些项目能够在https://github.com/search?l=Java&p=1&q=stars%3A%3E1&s=stars&type=Repositories搜索结果中看到。
为了不抓取范围太宽,我指定只从分页部分抓取连接。这个抓取规则是比较复杂的,我会要怎么写呢?
首先看到页面的html结构是这个样子的:
那么我能够先用CSS选择器提取出这个div,而后在取到全部的连接。为了保险起见,我再使用正则表达式限定一下提取出的URL的格式,那么最终的写法是这样子的:
List<String> urls = page.getHtml().css("div.pagination").links().regex(".*/search/\?l=java.*").all();
而后,咱们能够把这些URL加到抓取列表中去:
List<String> urls = page.getHtml().css("div.pagination").links().regex(".*/search/\?l=java.*").all(); page.addTargetRequests(urls);
是否是比较简单?除了发现连接,Selectable的链式抽取还能够完成不少工做。咱们会在第9章示例中再讲到。
当链式调用结束时,咱们通常都想要拿到一个字符串类型的结果。这时候就须要用到获取结果的API了。咱们知道,一条抽取规则,不管是XPath、CSS选择器或者正则表达式,总有可能抽取到多条元素。WebMagic对这些进行了统一,你能够经过不一样的API获取到一个或者多个元素。
方法 | 说明 | 示例 |
---|---|---|
get() | 返回一条String类型的结果 | String link= html.links().get() |
toString() | 功能同get(),返回一条String类型的结果 | String link= html.links().toString() |
all() | 返回全部抽取结果 | List<String> links= html.links().all() |
match() | 是否有匹配结果 | if (html.links().match()){ xxx; } |
例如,咱们知道页面只会有一条结果,那么能够使用selectable.get()或者selectable.toString()拿到这条结果。
这里selectable.toString()采用了toString()这个接口,是为了在输出以及和一些框架结合的时候,更加方便。由于通常状况下,咱们都只须要选择一个元素!
selectable.all()则会获取到全部元素。
好了,到如今为止,在回过头看看3.1中的GithubRepoPageProcessor,可能就以为更加清晰了吧?指定main方法,已经能够看到抓取结果在控制台输出了。
好了,爬虫编写完成,如今咱们可能还有一个问题:我若是想把抓取的结果保存下来,要怎么作呢?WebMagic用于保存结果的组件叫作Pipeline
。例如咱们经过“控制台输出结果”这件事也是经过一个内置的Pipeline完成的,它叫作ConsolePipeline
。那么,我如今想要把结果用Json的格式保存下来,怎么作呢?我只须要将Pipeline的实现换成"JsonFilePipeline"就能够了。
public static void main(String[] args) { Spider.create(new GithubRepoPageProcessor()) //从"https://github.com/code4craft"开始抓 .addUrl("https://github.com/code4craft") .addPipeline(new JsonFilePipeline("D:\webmagic\")) //开启5个线程抓取 .thread(5) //启动爬虫 .run(); }
这样子下载下来的文件就会保存在D盘的webmagic目录中了。
经过定制Pipeline,咱们还能够实现保存结果到文件、数据库等一系列功能。这个会在第7章“抽取结果的处理”中介绍。
至此为止,咱们已经完成了一个基本爬虫的编写,也具备了一些定制功能。