前面讲解了最基础的爬虫,我们已经可以爬取一些页面的文件或文本内容了。但是如果我们要爬取搜索引擎搜索某关键字后的结果,并且要知道某个结果来自哪一页,该怎么办?

我们最好的解决方式就是使用元数据保存一些自定义信息。
具体代码实现:

public class MyCrawlerBing extends RamCrawler {
    /**
     * @param keyword 搜索关键字
     * @param pageNum 爬取的页数
     * @throws Exception
     */
    public MyCrawlerBing(String keyword, int pageNum) throws Exception {
        for (int pageIndex = 1; pageIndex <= pageNum; pageIndex++) {
            //构造URL
            int first = pageIndex * 10 - 9;//当前页的第一条的索引,第一页为1,第二页为11,第三页为21
            keyword = URLEncoder.encode(keyword, StandardCharsets.UTF_8);
            String url = String.format("http://cn.bing.com/search?q=%s&first=%s", keyword, first);
            //添加元数据
            CrawlDatum datum = new CrawlDatum(url)
                    .type("searchEngine")
                    .meta("keyword", keyword)
                    .meta("pageIndex", pageIndex)
                    .meta("depth", 1);
            addSeed(datum);
        }
    }

    @Override
    public void visit(Page page, CrawlDatums next) {
        if (page.code() == 301 || page.code() == 302) {
            try {
                String redirectUrl = new URL(new URL(page.url()), page.location()).toExternalForm();
                next.addAndReturn(redirectUrl).meta(page.copyMeta());
            } catch (MalformedURLException e) {
                ExceptionUtils.fail(e);
            }
            return;
        }

        String keyword = page.meta("keyword");
        int pageIndex = page.metaAsInt("pageIndex");
        int depth = page.metaAsInt("depth");

        if (page.matchType("searchEngine")) {
            System.out.println("成功抓取关键词" + keyword + "的第" + pageIndex + "页搜索结果");
            Elements results = page.select("li.b_algo>h2>a");

            for (int rank = 0; rank < results.size(); rank++) {
                Element result = results.get(rank);

                /*
                我们希望继续爬取每条搜索结果指向的网页,这里统称为外链。
                我们希望在访问外链时仍然能够知道外链处于搜索引擎的第几页、第几条,
                所以将页号和排序信息放入后续的CrawlDatum中,为了能够区分外链和
                搜索引擎结果页面,type设置为outlink,这里的值完全由
                用户定义,可以设置一个任意的值
                */
                String href = result.attr("abs:href");
                next.addAndReturn(href)
                        .type("outlink")
                        .meta("keyword", keyword)
                        .meta("pageIndex", pageIndex)
                        .meta("rank", rank);
            }

        } else if (page.matchType("outlink")) {

            int rank = page.metaAsInt("rank");
            String referer = page.meta("referer");

            String line = String.format("第%s页第%s个结果:%s(%s字节)\tdepth=%s\treferer=%s",
                    pageIndex, rank + 1, page.doc().title(), page.content().length, depth, referer);
            System.out.println(line);

        }
        /*
        在经典爬虫中,每个网页都有一个referer信息,表示当前网页的链接来源。
        例如我们首先访问新浪首页,然后从新浪首页中解析出了新的新闻链接,
        则这些网页的referer值都是新浪首页。WebCollector不直接保存referer值,
        但我们可以通过下面的方式,将referer信息保存在metaData中,达到同样的效果。
        经典爬虫中锚文本的存储也可以通过下面方式实现。

        在一些需求中,希望得到当前页面在遍历树中的深度,利用metaData很容易实现
        这个功能,在将CrawlDatum添加到next中时,将其depth设置为当前访问页面
        的depth+1即可。
         */
        next.meta("depth", depth + 1).meta("referer", page.url());
    }

    public static void main(String[] args) throws Exception {
        MyCrawlerBing myCrawlerBing = new MyCrawlerBing("网络爬虫", 4);
        myCrawlerBing.start();
    }
}

注解版实现:(替换掉visit方法即可)

 @MatchCode(codes = {301, 302})
    public void visitRedirect(Page page, CrawlDatums next){
        System.out.println("301301301301301++++++++++++++");
        try {
            // page.location() may be relative url path
            // we have to construct an absolute url path
            String redirectUrl = new URL(new URL(page.url()), page.location()).toExternalForm();
            next.addAndReturn(redirectUrl).meta(page.copyMeta());
        } catch (MalformedURLException e) {
            //the way to handle exceptions in WebCollector
            ExceptionUtils.fail(e);
        }
    }

    @MatchType(types = "searchEngine")
    public void visitSearchEngine(Page page, CrawlDatums next) {
        System.out.println("searchEnginesearchEnginesearchEngine++++++++++++++");

        String keyword = page.meta("keyword");
        int pageIndex = page.metaAsInt("pageIndex");
        System.out.println("成功抓取关键词" + keyword + "的第" + pageIndex + "页搜索结果");
        Elements results = page.select("li.b_algo>h2>a");

        for (int rank = 0; rank < results.size(); rank++) {
            Element result = results.get(rank);
            String href = result.attr("abs:href");
            next.addAndReturn(href)
                    .type("outlink")
                    .meta("keyword", keyword)
                    .meta("pageIndex", pageIndex)
                    .meta("rank", rank);
        }
    }

    @MatchType(types = "outlink")
    public void visitOutlink(Page page, CrawlDatums next) {
        System.out.println("outlinkoutlinkoutlinkoutlink++++++++++++++");

        int depth = page.metaAsInt("depth");
        int pageIndex = page.metaAsInt("pageIndex");
        int rank = page.metaAsInt("rank");
        String referer=page.meta("referer");

        String line = String.format("第%s页第%s个结果:%s(%s字节)\tdepth=%s\treferer=%s",
                pageIndex, rank + 1, page.doc().title(),page.content().length, depth, referer);
        System.out.println(line);
    }

    @Override
    public void visit(Page page, CrawlDatums next) {
        
    }

    @AfterParse
    public void afterParse(Page page, CrawlDatums next){
        int depth = page.metaAsInt("depth");
        next.meta("depth", depth + 1).meta("referer", page.url());
    }

Q.E.D.


擅长前端的Java程序员