最近有一些工作上的需要,需要接触到爬虫来爬取数据。之前有使用过Python实现一个很简单的爬虫Demo,这次由于公司使用的是Java爬虫,基于webmagic框架去实现的爬虫。于是就参考了资料自己学习搭载了一个Demo,爬取了博客园所有精品文章的数据。
首先稍微了解了一下webmagic框架,下图是webmagic的流程示意图。
功能覆盖整个爬虫的生命周期(链接提取、页面下载、内容抽取、持久化)。而其中PageProcesser就是整个爬虫的核心部分,也就是链接提取、内容抽取和持久化的具体逻辑设计。webmagic也简化了其他过程,它希望开发者可以专注于核心逻辑部分设计,而对于我这样的初学者也很容易就上手了。
模型类设计
public class CnBlogs {
// 标题
private String title;
// 作者
private String author;
// 发布日期
private String dateTime;
// 地址
private String url;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getDateTime() {
return dateTime;
}
public void setDateTime(String dateTime) {
this.dateTime = dateTime;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
@Override
public String toString() {
return "CnBlogs [title=" + title + ", author=" + author + ", dateTime=" + dateTime + ", url=" + url + "]";
}
}
PageProcessor类(核心逻辑)
/*
*@author HangDie
*@date 2018/07/17
*/
public class CnBlogProcessor implements PageProcessor {
// 抓取网站的相关配置,包括:编码、抓取间隔、重试次数等
private Site site = Site.me().setRetryTimes(3).setSleepTime(1000);
// 文章数量
private static int size = 0;
// 文章集合
private static List<CnBlogs> cnBlogses = new ArrayList<>();
// 抽取逻辑类
@Override
public void process(Page page) {
// 如果是精华文章的列表页
if (page.getUrl().regex("https://www.cnblogs.com/pick/.*").match()) {
List<String> list = page.getHtml().xpath("//div[@id='post_list']").links().regex("^(.*\\.html)$").all();
// 去重复URL
list = list.stream().distinct().collect(Collectors.toList());
// 添加到待爬取队列
page.addTargetRequests(list);
page.addTargetRequests(page.getHtml().xpath("//div[@class='pager']").links().all());
} else {
size++;
CnBlogs cnBlogs = new CnBlogs();
// 标题
cnBlogs.setTitle(page.getHtml().xpath("//div[@class='post']/h1[@class='postTitle']/a/text()").get());
// 作者
cnBlogs.setAuthor(page.getHtml().xpath("//a[@id='Header1_HeaderTitle']/text()").get());
// 发布日期
cnBlogs.setDateTime(page.getHtml().xpath("//div[@class='postDesc']/span[@id='post-date']/text()").get());
// URL
cnBlogs.setUrl(page.getUrl().toString());
synchronized (cnBlogses) {
cnBlogses.add(cnBlogs);
}
}
}
@Override
public Site getSite() {
return site;
}
}
通过POI写入Excel输出结果
public static void printExcel(List<CnBlogs> cnBlogses) {
System.out.println("正在写入Excel...");
// 定义表头
String[] title = { "序号", "标题", "作者", "发布日期", "链接" };
// 创建excel工作簿
HSSFWorkbook workbook = new HSSFWorkbook();
// 创建工作表sheet
HSSFSheet sheet = workbook.createSheet();
// 创建第一行
HSSFRow row = sheet.createRow(0);
HSSFCell cell = null;
// 插入第一行数据的表头
for (int i = 0; i < title.length; i++) {
cell = row.createCell(i);
cell.setCellValue(title[i]);
}
int idx = 1;
for (CnBlogs cnBlogs : cnBlogses) {
HSSFRow nrow = sheet.createRow(idx);
HSSFCell ncell = nrow.createCell(0);
ncell.setCellValue(idx++);
ncell = nrow.createCell(1);
ncell.setCellValue(cnBlogs.getTitle());
ncell = nrow.createCell(2);
ncell.setCellValue(cnBlogs.getAuthor());
ncell = nrow.createCell(3);
ncell.setCellValue(cnBlogs.getDateTime());
ncell = nrow.createCell(4);
ncell.setCellValue(cnBlogs.getUrl());
}
// 设置自动列宽
for (int i = 0; i < title.length; i++) {
sheet.autoSizeColumn(i);
sheet.setColumnWidth(i, sheet.getColumnWidth(i) * 16 / 10);
}
// 创建excel文件
File file = new File("C://Users/HangDie/Desktop/result.xls");
try {
file.createNewFile();
// 将excel写入
FileOutputStream stream = FileUtils.openOutputStream(file);
workbook.write(stream);
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("写入完成");
}
爬取结果(采用Excel存储)
发现有部分标题或者数据丢失,实际上是因为有一些文章已经过于老旧了,其页面元素也有所改动。后续可能会收集一下几种标签元素规则,做一个通配。