WebMagic之爬取暴走漫画
WebMagic之爬取暴走漫画引言曾经在知乎上看到过爬取网易云音乐评论的提问,而且在QQ空间和微信朋友圈也曾见过一首歌N评论的长截图,可以说是一首歌牵扯着数人数年的情感。做一个初出茅庐的Java程序猿,对爬虫充满了兴趣。在网上不断的搜索,发现大部分是通过python写的爬虫,也发现了一些Java框架写的爬虫(知乎:GitHub 上有哪些优秀的 Java 爬虫项目?)。我在上述知乎问...
WebMagic之爬取暴走漫画
引言
曾经在知乎上看到过爬取网易云音乐评论的提问,而且在QQ空间和微信朋友圈也曾见过一首歌N评论的长截图,可以说是一首歌牵扯着数人数年的情感。做一个初出茅庐的Java程序猿,对爬虫充满了兴趣。在网上不断的搜索,发现大部分是通过python写的爬虫,也发现了一些Java框架写的爬虫(知乎:GitHub 上有哪些优秀的 Java 爬虫项目?)。
我在上述知乎问答中发现了WebMagic,一个爬虫框架,以下是官方文档中的设计思想。
一个好的框架必然凝聚了领域知识。WebMagic的设计参考了业界最优秀的爬虫Scrapy,而实现则应用了HttpClient、Jsoup等Java世界最成熟的工具,目标就是做一个Java语言Web爬虫的教科书般的实现。
如果你是爬虫开发老手,那么WebMagic会非常容易上手,它几乎使用Java原生的开发方式,只不过提供了一些模块化的约束,封装一些繁琐的操作,并且提供了一些便捷的功能。
如果你是爬虫开发新手,那么使用并了解WebMagic会让你了解爬虫开发的常用模式、工具链、以及一些问题的处理方式。熟练使用之后,相信自己从头开发一个爬虫也不是什么难事。
因为这个目标,WebMagic的核心非常简单——在这里,功能性是要给简单性让步的 ————WebMagic官方文档
HttpClient是Apache Jakarta Common下的子项目,用来提供高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新的版本和建议。HttpClient已经应用在很多的项目中,比如Apache Jakarta上很著名的另外两个开源项目Cactus和HTMLUnit都使用了HttpClient。
Jsoup是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。
想要爬取网易云音乐,可能对于刚开始学习爬虫的人来说直接上有点难度。我在CSDN看到了(使用WebMagic爬虫框架爬取暴走漫画)这篇文章,但是文章介绍存在一些细节问题不符合结果预期。所以我先作为练手爬取了暴走漫画。
所需知识:XPath教程,CSS教程直通车。
观察网页
我们可以清晰的看到这三个有意义的信息,作者,时间,内容。
创建实体
将信息构建为类
public class BaoZouItems {
// 作者
private String author;
// 提交时间
private String time;
// 内容
private String content;
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "BaozouNews [author=" + author + ", time=" + time + ", content=" + content + "]";
}
}
决定爬取逻辑
第一步:确定要加入待爬取队列的超链接
我们可以通过F12或者右键检查 查看页面元素。
我们可以清晰的看到class=”pager”的div元素,其中包含了1,2,3,1000和下一页的链接/text?page= * &sv= ***。
page.addTargetRequests(page.getHtml().css("div.pager-content").links().all());
这段代码将获取到div中所有链接,此时可能眼尖的童鞋发现有重复url(第二页和下一页一样),没事,WebMagic去重。当然也可以更精细的实现,因人而异。
第二步:确定待爬取数据
如果我按照我所学习的文章来写的话,我将会得到每页中第一条数据。我们分析一下原因(当时打断点分析出来的)。
public class BaoZouProcessor implements PageProcessor {
private Site site = Site.me().setRetryTimes(3).setSleepTime(100);
@Override
public void process(Page page) {
//获取链接,这话没错
page.addTargetRequests(page.getHtml().css("div.pager-content").links().all());
//这里逻辑开始有问题,一个页面有好多条笑话,但创建一个实体对象。
BaozouNews news = new BaozouNews();
//下面三句话对对象赋值,经过断点测试,发现page.getHtml().xpath("//a[@class='article-author-name']/text()")可返回好几个值
//而作者通过toString(看了一下源码,不为空只返回第一个get(0),然后只保存了第一条,下面语句类似
news.setAuthor(page.getHtml().xpath("//a[@class='article-author-name']/text()").toString());
news.setContent(page.getHtml().xpath("//div[@class='article article-text']/@data-text").toString());
news.setTime(page.getHtml().xpath("//span[@class='article-date']/text()").toString());
//将需要提取的内容放入page对象
page.putField("news", news);
}
@Override
public Site getSite() {
return site;
}
}
最后导致结果集为1000页的第一条数据。
public class BaoZouMainSplider implements PageProcessor {
private Site site = Site.me().setRetryTimes(3).setSleepTime(100);
@Override
public void process(Page page) {
Html html = page.getHtml();
page.addTargetRequests(html.css("div.pager-content").links().all());
//获取所有div块(每个div块有个一想要的信息)
Selectable xpath = html.xpath("//div[@class='article article-text']");
List<String> all = xpath.all();
List<BaoZouItems> baozouNews = new ArrayList<BaoZouItems>();
//遍历div块
for (String string : all) {
BaoZouItems news = new BaoZouItems();
//转换为html
Html partHtml = new Html(string);
//解析数据
String author = partHtml.xpath("//a[@class='article-author-name']/text()").toString();
//特殊处理,当用户为匿名时,所在元素为span而不是a
if(author == null) {
author = partHtml.xpath("//span[@class='article-author-name']/text()").toString();
}
news.setAuthor(author);
Selectable content = partHtml.xpath("//div[@class='article article-text']/@data-text");
news.setContent(content.toString());
news.setTime(html.xpath("//span[@class='article-date']/text()").toString());
//当时间为null,代表为垃圾数据
if(news.getTime() != null) {
baozouNews.add(news);
}
}
if(baozouNews.size() == 0) {
page.setSkip(true);
}else {
page.putField("news", baozouNews);
}
}
@Override
public Site getSite() {
return site;
}
public static void main(String[] args) {
Spider.create(new BaoZouMainSplider()).addUrl("http://baozoumanhua.com/text").addPipeline(new UserDefineFilePipeline("E:\\baozou.txt")).thread(5).run();
}
}
保存数据
在上述main方法中存在一个new UserDefineFilePipeline(“E:\baozou.txt”),意思是输出到E盘baozou.txt中。
public class UserDefineFilePipeline implements Pipeline{
private Logger logger = LoggerFactory.getLogger(getClass());
File baozou = null;
public UserDefineFilePipeline(String path) {
baozou = new File(path);
}
@Override
public void process(ResultItems resultItems, Task task) {
//获取每一页中的数据
List<BaoZouItems> news = resultItems.get("news");
try (FileWriter fw = new FileWriter(baozou,true);BufferedWriter bw = new BufferedWriter(fw)){
for (BaoZouItems baozouNews : news) {
bw.write("作者:\t"+baozouNews.getAuthor()+"\r\n");
bw.write("时间:\t"+baozouNews.getTime()+"\r\n");
bw.write("内容:\t"+baozouNews.getContent()+"\r\n");
bw.write("\r\n");
bw.write("-----------------------------------------------------------"
+ "----------------------------------------------");
bw.write("\r\n");
}
} catch (IOException e) {
logger.warn("write file error", e);
}
}
}
查看结果
程序运行约3分钟,就将所有页面爬取下来了,数据为8000余条,
已经加入写入数据库功能,可GitHub上查看。
结语
这篇文章是通过https://blog.csdn.net/kingsonyoung/article/details/51752146文章学习感悟而来,初学之作,程序定有可改进之处,望各位大佬提出意见和建议。
所有程序在GitHub上,满意的话请添加个star。
更多推荐
所有评论(0)