代码地址:https://github.com/her-cat/novel-spider
WebMagic 简介
WebMagic 是一个无须配置、便于二次开发的爬虫框架,它提供简单灵活的API,只需少量代码即可实现一个爬虫。使用的时候不需要你关心一些细节,比如网络请求,是使用 Apache HttpClient 还是 Java 原生的 HttpURLConnection,提取页面上的数据也不需要你手写正则,你可以选择使用 XPath、CSS选择器、正则表达式、JsonPath 提取页面上的数据。
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判断当前是哪个页面,并使用对应的方法提取页面数据。
processCategoryPage
、processCatelogPage
方法用于发现新链接。
processSummaryPage
、processChapterPage
方法用于提取小说数据。
在页面数据提取的时候,大量使用了 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
运行效果
小说信息:
章节内容: