基于 WebMagic 的 Java 小说爬虫

代码地址:https://github.com/her-cat/novel-spider

WebMagic 简介

WebMagic 是一个无须配置、便于二次开发的爬虫框架,它提供简单灵活的API,只需少量代码即可实现一个爬虫。使用的时候不需要你关心一些细节,比如网络请求,是使用 Apache HttpClient 还是 Java 原生的 HttpURLConnection,提取页面上的数据也不需要你手写正则,你可以选择使用 XPath、CSS选择器、正则表达式、JsonPath 提取页面上的数据。

【WebMagic 架构】

WebMagic 组件

说了那么多 WebMagic 优点,接下来就介绍一下 WebMagic 的核心组件。

Downloader

Downloader 负责从互联网上下载页面(默认使用Apache HttpClient),以便后续处理。

重点:下载页面

PageProcessor

PageProcessor 是爬虫的核心部分,用于解析页面,提取出需要的数据,发现新的链接。

重点:提取数据、发现新链接

Scheduler

Scheduler 负责管理待抓取的URL,以及一些去重的工作。

重点:管理URL

PipeLine

PipeLine 负责处理 PageProcessor 提取出来的数据,包括计算、持久化到文件、数据库等。框架自带了 输出到控制台保存到文件 两种结果处理方案。

重点:处理数据

核心代码

采集一本小说涉及到的页面有 小说简介、章节目录、章节内容,如果需要采集多本的话,那么还需要一个能够获取小说列表的地方,这里用的是分类下的小说页面。

WebMagic 相关API说明:

  • getHtml() 获取 Html 对象
  • addTargetRequest() 放入新链接(单个url)
  • addTargetRequests() 放入新链接(多个url)
  • putField() 放入处理结果供 Pipeline 使用
  • xpath() 使用 XPath 语法查找元素
  • regex() 使用正则表达式查找元素
public class X23usPageProcessor implements PageProcessor {

    private Site site = Site.me().addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36").setRetryTimes(3).setSleepTime(1000);

    /**
     * 分类URL
     */
    private static final String CATEGORY_PAGE_URL = "htt(p|ps)://www\\.x23us\\.com/class/\\d+_\\d+.html";

    /**
     * 简介URL
     */
    private static final String SUMMARY_PAGE_URL = "htt(p|ps)://www\\.x23us\\.com/book/\\d+";

    /**
     * 目录URL
     */
    private static final String CATELOG_PAGE_URL = "htt(p|ps)://www\\.x23us\\.com/html/\\d+/\\d+/$";

    /**
     * 章节URL
     */
    private static final String CHAPTER_PAGE_URL = "htt(p|ps)://www\\.x23us\\.com/html/\\d+/\\d+/\\d+.html";

    public Site getSite() {
        return site;
    }

    public void process(Page page) {
        Selectable url = page.getUrl();
        if (url.regex(CATEGORY_PAGE_URL).match()) {
            processCategoryPage(page);
        } else if (url.regex(SUMMARY_PAGE_URL).match()) {
            processSummaryPage(page);
        } else if (url.regex(CATELOG_PAGE_URL).match()) {
            processCatelogPage(page);
        } else if (url.regex(CHAPTER_PAGE_URL).match()) {
            processChapterPage(page);
        } else {
            page.setSkip(true);
        }
    }

    /**
     * 处理分类页面
     *
     * @param page
     */
    public void processCategoryPage(Page page) {
        Selectable html = page.getHtml().xpath("//*[@id=\"content\"]/dd[1]/table/tbody");
        page.putField("type", "category");
        // 小说简介页面url
        page.addTargetRequests(html.regex(String.format("(%s)", SUMMARY_PAGE_URL)).all());
        // 小说章节目录页面url
        page.addTargetRequests(html.regex("(htt(p|ps)://www\\.x23us\\.com/html/\\d+/\\d+/)").all());
        // 获取下一页url
        String nextPageUrl = page.getHtml().xpath("//div[@id='pageLink']/a[@class='next']/@href").toString();
        if (nextPageUrl != null) {
            page.addTargetRequest(nextPageUrl);
        }
    }

    /**
     * 处理简介页面
     *
     * @param page
     */
    public void processSummaryPage(Page page) {
        Html html = page.getHtml();
        Map<Object, Object> novel = new HashMap<Object, Object>();
        Selectable category = html.xpath("//*[@id=\"at\"]/tbody/tr[1]/td[1]/a");
        String process = html.xpath("//*[@id=\"at\"]/tbody/tr[1]/td[3]/text()").toString();
        if (process.contains("连载")) {
            novel.put("process", 1);
        } else if (process.contains("完成")) {
            novel.put("process", 2);
        } else {
            novel.put("process", 3);
        }

        novel.put("id", page.getUrl().regex(String.format("(%s)", SUMMARY_PAGE_URL)).toString());
        novel.put("name", html.xpath("//h1/text()").toString().split(" ")[0]);
        novel.put("cover_url", html.xpath("//*[@id=\"content\"]/dd[2]/div[1]/a/img/@src").toString());
        novel.put("introduction", html.xpath("//*[@id=\"content\"]/dd[4]/p[2]/text()").toString());
        novel.put("tags", html.xpath("//*[@id=\"content\"]/dd[4]/p[5]/u").all().toString().replace(",", ""));
        novel.put("latest_chapter", html.xpath("//*[@id=\"content\"]/dd[4]/p[6]/a/text()").toString());
        novel.put("author_name", html.xpath("//*[@id=\"at\"]/tbody/tr[1]/td[2]/text()").toString());
        novel.put("category_id", category.xpath("/a/@href").regex("/class/(\\d+)_\\d+\\.html").toString());
        novel.put("category_name", category.xpath("/a/text()").toString());
        novel.put("collection_number", html.xpath("//*[@id=\"at\"]/tbody/tr[2]/td[1]/text()").toString());
        novel.put("total_words_number", html.xpath("//*[@id=\"at\"]/tbody/tr[2]/td[2]/text()").toString().replace("字", ""));
        novel.put("latest_updated_at", html.xpath("//*[@id=\"at\"]/tbody/tr[2]/td[3]/text()").toString());
        novel.put("total_click_number", html.xpath("//*[@id=\"at\"]/tbody/tr[3]/td[1]/text()").toString());
        novel.put("month_click_number", html.xpath("//*[@id=\"at\"]/tbody/tr[3]/td[2]/text()").toString());
        novel.put("week_click_number", html.xpath("//*[@id=\"at\"]/tbody/tr[3]/td[3]/text()").toString());
        novel.put("total_recommend_number", html.xpath("//*[@id=\"at\"]/tbody/tr[4]/td[1]/text()").toString());
        novel.put("month_recommend_number", html.xpath("//*[@id=\"at\"]/tbody/tr[4]/td[2]/text()").toString());
        novel.put("week_recommend_number", html.xpath("//*[@id=\"at\"]/tbody/tr[4]/td[3]/text()").toString());

        page.putField("type", "summary");
        page.putField("novel", novel);
    }

    /**
     * 处理目录页面
     *
     * @param page
     */
    public void processCatelogPage(Page page) {
        page.putField("type", "catelog");
        page.addTargetRequests(page.getHtml().xpath("//table[@id='at']").links().all());
    }

    /**
     * 处理章节页面
     *
     * @param page
     */
    public void processChapterPage(Page page) {
        page.putField("type", "chapter");
        page.putField("title", page.getHtml().xpath("//h1/text()").toString());
        page.putField("content", page.getHtml().xpath("//dd[@id='contents']/text()").toString());
    }
}

site 是小说站点的配置信息,设置了 Header 头信息、重试次数、睡眠时间。

process 方法中通过页面url判断当前是哪个页面,并使用对应的方法提取页面数据。

processCategoryPageprocessCatelogPage 方法用于发现新链接。

processSummaryPageprocessChapterPage 方法用于提取小说数据。

在页面数据提取的时候,大量使用了 XPath 和正则,如果对 XPath 和正则有基础的话阅读代码会轻松很多。

推荐学习资料:

启动爬虫

public class Run {
    public static void main(String[] args) {
        Spider spider = Spider.create(new X23usPageProcessor());
        spider.addUrl("https://www.x23us.com/class/2_1.html");
        spider.addPipeline(new JsonFilePipeline());
        spider.thread(10).run();
    }
}

首先告诉爬虫需要用哪个 PageProcessor 处理页面,然后添加一个起始URL,再添加一个 Pipeline 处理结果,这里使用的是框架自带的 Pipeline,以 json 格式保存到文件,保存的文件就在 C:\data\webmagic 目录下面,然后使用 10 个线程启动爬虫。

如果你想将抓取到的数据存到数据库里面,就需要自己定义一个 Pipeline 了,详见官方文档:https://webmagic.io/docs/zh/posts/ch6-custom-componenet/pipeline.html

运行效果

在这里插入图片描述

在这里插入图片描述

小说信息:
在这里插入图片描述

章节内容:
在这里插入图片描述

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值