WebMagic总体架构
WebMagic的结构分为Downloader、PageProcessor、Scheduler、Pipeline四大组件,并由Spider将它们彼此组织起来。这四大组件对应爬虫生命周期中的下载、处理、管理和持久化等功能。
- Downloader
Downloader负责从互联网上下载页面,以便后续处理。WebMagic默认使用了Apache HttpClient作为下载工具。
- PageProcessor
PageProcessor负责解析页面,抽取有用信息,以及发现新的链接。WebMagic使用Jsoup作为HTML解析工具,并基于其开发了解析XPath的工具Xsoup。
在这四个组件中,PageProcessor对于每个站点每个页面都不一样,是需要使用者定制的部分。
- Scheduler
Scheduler负责管理待抓取的URL,以及一些去重的工作。WebMagic默认提供了JDK的内存队列来管理URL,并用集合来进行去重。也支持使用Redis进行分布式管理。
除非项目有一些特殊的分布式需求,否则无需自己定制Scheduler。
- Pipeline
Pipeline负责抽取结果的处理,包括计算、持久化到文件、数据库等。WebMagic默认提供了“输出到控制台”和“保存到文件”两种结果处理方案。
Pipeline定义了结果保存的方式,如果你要保存到指定数据库,则需要编写对应的Pipeline。对于一类需求一般只需编写一个Pipeline。
爬取人民网新闻
- 目标抓取http://bj.people.com.cn/所有分类的新闻
233088
新闻类别码index1
数字为页码每个内容界面的链接如
http://bj.people.com.cn/n2/2016/1213/c82840-29459169.html2016/1213/c82840-29459169
年/日期/每个新闻的识别码
实现
使用STS构建环境
创建Spring Starter Project
加入JPA,MySQL,Web依赖
-
导入WebMagic依赖
注意 :- WebMagic 的核心包的log4j会与jpa的log4j冲突,需要排除WebMagic的jar
- 排除后会报
org.apache.commons.lang.StringUtils
找不到,需要导入commons-lang依赖
<dependency> <groupId>us.codecraft</groupId> <artifactId>webmagic-core</artifactId> <version>0.5.3</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>us.codecraft</groupId> <artifactId>webmagic-extension</artifactId> <version>0.5.3</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> </dependency>
代码
- PageProcessor 抽取内容
@Service
public class NewsProcessor implements PageProcessor {
// 入口url
public static final String URL_ENTER = "http://bj.people.com.cn/";
// 类别导航
private String URL_INDEX = "http://bj\\.people\\.com\\.cn/GB/\\d+/index\\.html";
// 列表
private String URL_LIST = "http://bj\\.people\\.com\\.cn/GB/\\d+/index\\d*\\.html";
// 文章
private String URL_POST = "http://bj\\.people\\.com\\.cn/n2/2016/\\d+/.+\\.html";
// 设置
private Site site = Site.me().setRetryTimes(10).setSleepTime(1000).setCycleRetryTimes(3);
@Override
public void process(Page page) {
if (page.getUrl().get().equals(URL_ENTER)) {
// 类别
page.addTargetRequests(page.getHtml().xpath("//div[@class='pd_nav w1000 white clear clearfix']").links().regex(URL_INDEX).all());
System.out.println("enter"+page.getUrl().get());
} else {
System.out.println("enter"+page.getUrl().get());
// 列表页
if (page.getUrl().regex(URL_LIST).match()) {
page.addTargetRequests(page.getHtml().xpath("//div[@class='ej_list_box clear']").links().regex(URL_POST).all());
page.addTargetRequests(page.getHtml().links().regex(URL_LIST).all());
} else {
// 匹配当前域名
String currentDomain = page.getUrl().regex("http.+\\.com\\.cn").toString();
String title = page.getHtml().xpath("//div[@class='clearfix w1000_320 text_title']/h1/text()").toString();
String contentTime = page.getHtml().xpath("//div[@class='box01']/div[@class='fl']/text()").regex("\\d{4}年\\d{2}月\\d{2}日\\s*\\d{2}:\\d{2}").toString();
String content = page.getHtml().xpath("//div[@class='box_con']").toString();
List<String> imgList = page.getHtml().xpath("//div[@class='box_con']//img/@src").all();
List<String> result = new ArrayList<>();
for (String img : imgList) {
// 匹配是否是绝对路劲
Pattern r = Pattern.compile("^http");
Matcher m = r.matcher(img);
if (!m.find()) {
// 不是绝对路径 拼接当前域名
result.add(currentDomain + img);
} else {
result.add(img);
}
}
News news = new News();
news.setSourceUrl(page.getUrl().regex(URL_POST).toString());
news.setContent(content);
news.setContentTime(contentTime);
news.setTitle(title);
news.setImage(new Gson().toJson(result));
if (news.getTitle() == null) {
page.setSkip(true);
} else {
page.putField("news", news);
}
}
}
}
@Override
public Site getSite() {
return site;
}
}
- Pipeline 持久化数据
@Service
public class NewsPipeline implements Pipeline {
@Autowired
private NewsService newsService;
@Override
public void process(ResultItems resultItems, Task task) {
News news = (News) resultItems.get("news");
if (!newsService.isExist(news)) {
newsService.create(news);
}
}
}
- 启动爬虫
@RequestMapping("/start")
private String start() {
Spider.create(newsProcessor)// 创建抽取内容类
.addUrl(NewsProcessor.URL_ENTER)// 添加入口url
.addPipeline(newsPipeline)// 添加持久化类
.thread(5)// 开启5个线程
.run();// 启动
return "success";
}
结果
-
保存的数据