java 爬虫大型教程(二)

java 爬虫大型教程(二)

编写基本的爬虫

1. 实现PageProcessor

这部分我们直接通过CdnRepoPageProcessor这个例子来介绍PageProcessor的编写方式。PageProcessor定制分为三个部分,分别是爬虫的配置、页面元素的抽取和链接的发现。

import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;



public class CdnRepoPageProcessor implements PageProcessor {

    // 抓取网站的相关配置,包括编码、抓取间隔、重试次数等
    private Site site = Site.me().setRetryTimes(3).setSleepTime(100);
    private static int count =0;

    @Override
    public Site getSite() {
        return site;
    }

    @Override
    // process是定制爬虫逻辑的核心接口,在这里编写抽取逻辑
    public void process(Page page) {


        page.putField("articleURL",page.getUrl().toString());
        page.putField("articleTitle",page.getHtml().xpath("//a[@id=\"cb_post_title_url\"]/text()").get());

        if(page.getResultItems().get("articleTitle")== null){
            //skip this page
            page.setSkip(true);
        }else {
            System.out.println("抓取的内容:"+ page.getResultItems().get("articleTitle"));
            count ++;
        }

        //加入满足条件的链接
        page.addTargetRequests(
                //判断链接是否符合http://www.cnblogs.com/任意个数字字母-/p/任意字符.html格式
                page.getHtml().xpath("//div[@id=\"post_list\"]").links().regex("https://www.cnblogs.com/[a-z A-Z 0-9 -]+/p/.+.html").all()
        );

    }

    public static void main(String[] args) {
        long startTime, endTime;
        System.out.println("开始爬取...");
        startTime = System.currentTimeMillis();
        Spider.create(new CdnRepoPageProcessor())
                //从https://www.cnblogs.com开始抓
                .addUrl("https://www.cnblogs.com/")
                //开启5个线程抓取
                .thread(5)
                //运行爬虫
                .run();
        endTime = System.currentTimeMillis();
        System.out.println("爬取结束,耗时约" + ((endTime - startTime) / 1000) + "秒,抓取了"+count+"条记录");
    }

}

1.1 爬虫的配置

第一部分关于爬虫的配置,包括编码、抓取间隔、超时时间、重试次数等,也包括一些模拟的参数,例如User Agent、cookie,以及代理的设置,会在后续教程中进行介绍。在这里我们先简单设置一下:重试次数为3次,抓取间隔为一秒。

1.2 页面元素的抽取

第二部分是爬虫的核心部分:对于下载到的Html页面,你如何从中抽取到你想要的信息?WebMagic里主要使用了三种抽取技术:XPath、正则表达式和CSS选择器。另外,对于JSON格式的内容,可使用JsonPath进行解析。CSS选择器我一般不使用,就不介绍了。

  1. XPath

    XPath本来是用于XML中获取元素的一种查询语言,但是用于Html也是比较方便的。例如:

    page.getHtml().xpath("//a[@id=\"cb_post_title_url\"]/text()").get()
    

    这段代码使用了XPath,它的意思是“查找所有class属性为’postTitle’的h1元素,并找到他的a子节点,并提取a节点的文本信息”。 对应的Html是这样子的:
    java 爬虫大型教程(二)

  2. 正则表达式

    正则表达式则是一种通用的文本抽取语言。

    page.addTargetRequests(
        //判断链接是否符合http://www.cnblogs.com/任意个数字字母-/p/任意多个字符.html格式
      page.getHtml().links().regex("https://www.cnblogs.com/[a-z A-Z 0-9 -]+/p/.+.html").all() );
    

    这段代码就用到了正则表达式,它表示匹配所有类似于https://www.cnblogs.com/fonxian/p/101.html这样的链接。

  3. JsonPath

    JsonPath是于XPath很类似的一个语言,它用于从Json中快速定位一条内容。WebMagic中使用的JsonPath格式可以参考这里:https://code.google.com/p/json-path/

1.3 链接的发现

有了处理页面的逻辑,我们的爬虫就接近完工了!

但是现在还有一个问题:一个站点的页面是很多的,一开始我们不可能全部列举出来,于是如何发现后续的链接,是一个爬虫不可缺少的一部分。

page.addTargetRequests(page.getHtml().links().regex("https://www.cnblogs.com/[a-z A-Z 0-9 -]+/p/.+.html").all() );

这段代码的分为两部分,page.getHtml().links().regex("https://www.cnblogs.com/[a-z A-Z 0-9 -]+/p/.+.html").all()用于获取所有满足"https:/ /www.cnblogs.com/[a-z A-Z 0-9 -]+/p/.+.html"这个正则表达式的链接,page.addTargetRequests()则将这些链接加入到待抓取的队列中去。

2. 使用Selectable抽取元素

Selectable相关的抽取元素链式API是WebMagic的一个核心功能。使用Selectable接口,你可以直接完成页面元素的链式抽取,也无需去关心抽取的细节。

在刚才的例子中可以看到,page.getHtml()返回的是一个Html对象,它实现了Selectable接口。这个接口包含一些重要的方法,它分为两类:抽取部分和获取结果部分。

2.1 抽取部分API:

方法 说明 示例
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("(.*?)")
regex(String regex,int group) 使用正则表达式抽取,并指定捕获组 html.regex("(.*?)",1)
replace(String regex, String replacement) 替换内容 html.replace("","")

这部分抽取API返回的都是一个Selectable接口,意思是说,抽取是支持链式调用的。下面我用一个实例来讲解链式API的使用。

例如:我现在要抓博客园cnblogs最新博客,直接访问https://www.cnblogs.com/就可以看到我需要的是中间部分,但是右侧也有推荐博客也有相同的结构。
java 爬虫大型教程(二)
为了避免抓取范围太宽,我指定只从分页部分即我需要的部分抓取链接。这个抓取规则是比较复杂的,我会要怎么写呢?首先看看我需要的页面html结构如下:
java 爬虫大型教程(二)
那么我可以先用xpath提取出这个div,然后在取到所有的链接。为了保险起见,我再使用正则表达式限定一下提取出的URL的格式,那么最终的写法是这样子的:

page.addTargetRequests( page.getHtml().xpath("//div[@id=\"post_list\"]").links().regex("https://www.cnblogs.com/[a-z A-Z 0-9 -]+/p/.+.html").all());

和上面的写法进行比较,其实就加了一个限定条件,并且是链式的,很方便。

2.2 获取结果的API:

当链式调用结束时,我们一般都想要拿到一个字符串类型的结果。这时候就需要用到获取结果的API了。我们知道,一条抽取规则,无论是XPath、CSS选择器或者正则表达式,总有可能抽取到多条元素。WebMagic对这些进行了统一,你可以通过不同的API获取到一个或者多个元素。

方法 说明 示例
get() 返回一条String类型的结果 String link= html.links().get()
toString() 功能同get(),返回一条String类型的结果 String link= html.links().toString()
all() 返回所有抽取结果 List links= html.links().all()
match() 是否有匹配结果 if (html.links().match()){ xxx; }

例如,我们知道页面只会有一条结果,那么可以使用selectable.get()或者selectable.toString()拿到这条结果。

这里selectable.toString()采用了toString()这个接口,是为了在输出以及和一些框架结合的时候,更加方便。因为一般情况下,我们都只需要选择一个元素!

selectable.all()则会获取到所有元素。

好了,到现在为止,在回过头看看CdnRepoPageProcessor,可能就觉得更加清晰了吧?指定main方法,已经可以看到抓取结果在控制台输出了。
java 爬虫大型教程(二)