@PageProcessor、Scheduler、Downloader和Pipeline。
这四大组件对应爬虫生命周期中的处理、管理、下载和持久化等功能。
这四个组件都是Spider中的属性,爬虫框架通过Spider启动和管理。
WebMagic总体架构图
一,WebMagic的四大组件
PageProcessor负责解析页面,抽取有用信息,以及发现新的链接。需要自己定义。
Scheduler负责管理待抓取的URL,以及一些去重的工作。一般无需自己定制Scheduler。
Pipeline负责抽取结果的处理,包括计算、持久化到文件、数据库等。
Downloader负责从互联网上下载页面,以便后续处理。一般无需自己实现。
二,用于数据流转的对象
Request 是对URL地址的一层封装,一个Request对应一个URL地址。
Page代表了从Downloader下载到的一个页面——可能是HTML,也可能是JSON或者其他文本格式的内容。
ResultItems相当于一个Map,它保存PageProcessor处理的结果,供Pipeline使用。
三,项目开始前的热身(解析页面的方式)
项目maven添加了下面要求的maven坐标之后就可以写下面的测试代码了。
下面的测试很清楚的讲解了解析页面的方式,以及爬虫的执行。
page.putField()是把爬取到的数据添加到了ResultItems中,默认在控制台打印出来。
public class JobProcessor implements PageProcessor {
//解析页面
public void process(Page page) {
//解析返回的数据page,并且把解析的结果放在resultItems中
//css选择器解析
//page.putField("爬取内容",page.getHtml().css("span.service_txt").all());//all()是返回所有数据,get()是返回第一条数据
//XPath解析
//page.putField("xpath方法解析结果",page.getHtml().xpath("//div[@id=J_cate]/ul/li/a").all());
//正则表达式解析(筛选内容带“装”字的所有信息)
page.putField("正则",page.getHtml().css("div#J_cate ul li a").regex(".*装.*").all());
//获取链接
// page.addTargetRequests(page.getHtml().css("div#shortcut-2014 div.w ul.fl li#ttbar-home a").links().all());
// page.putField("url",page.getHtml().css("div#shortcut div ul li a span").all());
}
private Site site=Site.me()
.setCharset("utf8") //设置编码
.setTimeOut(10000) //设置超时时间 单位是ms毫秒
.setRetrySleepTime(3000) //设置重试的时间间隔
.setSleepTime(3); //设置重试次数
public Site getSite() {
return site;
}
/*设置request请求方式
Request requests=new Request("http://www.12371.cn/cxsm/gzbs/");
requests.setMethod(HttpConstant.Method.GET);
Spider.create(new JobProcessor())
//.addUrl(url)
.addRequest(requests)
.setScheduler(new QueueScheduler().setDuplicateRemover(new BloomFilterDuplicateRemover(100000)))
.thread(5)
.addPipeline(this.newsData)
.run();
*/
//主函数,执行爬虫
public static void main(String[] args) {
Spider spider=Spider.create(new JobProcessor()).addUrl("https://www.jd.com")//设置爬取数据的页面
// .addPipeline(new FilePipeline("D:\\result"))//使用Pipeline保存数据到指定文件夹中,自动生成文件
.thread(5) //多线程进行爬取,5个多线程,速度更快
.setScheduler(new QueueScheduler().setDuplicateRemover(new BloomFilterDuplicateRemover(10000000)));//设置布隆去重过滤器,指定最多对1000万数据进行去重操作
//默认HashSet去重
//Scheduler scheduler=spider.getScheduler();
spider.run();//run()执行爬虫
}
}
四,SpringBoot项目环境搭建
首先搭建好一个springboot项目,加入mysql,mybatis,webmagic的核心依赖以及扩展依赖,以及添加WebMagic对布隆过滤器的支持的依赖。
org.springframework.boot
spring-boot-starter-web
2.1.3.RELEASE
mysql
mysql-connector-java
8.0.13
org.mybatis
mybatis
3.4.6
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.0.1
us.codecraft
webmagic-core
0.7.3
org.slf4j
slf4j-log4j12
us.codecraft
webmagic-extension
0.7.3
com.google.guava
guava
16.0
org.apache.commons
commons-lang3
3.9
五,配置文件
在resources文件夹新建log4j.properties文件配置日志
log4j.rootLogger=INFO,A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n
在新建application.properties文件配置数据库
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=1234
六,Let's go WebMagic!
1,启动类
@SpringBootApplication
@MapperScan("com.qianlong.dao")//扫描mapper文件
@EnableScheduling //开启定时任务,定时抓取数据
@ComponentScan(value = "com.qianlong")//包扫描
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
2,实体类(存储到数据库表的字段)
public class JobInfo {
private Integer id;
private String companyName;
private String companyAddr;
private String companyInfo;
private String jobName;
private String jobAddr;
private String salary;
private String time;
}
3,爬虫类
package com.qianlong.task;
import com.qianlong.entity.JobInfo;
import org.jsoup.Jsoup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.scheduler.BloomFilterDuplicateRemover;
import us.codecraft.webmagic.scheduler.QueueScheduler;
import us.codecraft.webmagic.selector.Html;
import us.codecraft.webmagic.selector.Selectable;
import java.util.List;
@Component
public class JobProcessor implements PageProcessor {
//前程无忧网站的职位列表地址
private String url="https://search.51job.com/list/170200,000000,0000,00,9,99,%2B,2,1.html?lang=c&postchannel=0000&workyear=99&cotype=99°reefrom=99&jobterm=99&companysize=99&ord_field=0&dibiaoid=0&line=&welfare=";
@Override
public void process(Page page) {
//解析页面,获取招聘信息详情的url地址
List list = page.getHtml().css("div#resultList div.el").nodes();
//判断集合是否为空
if(list.size()==0){
//如果为空,表示这是招聘详情页,解析页面,获取招聘详情信息,保存数据
this.saveJobInfo(page);
}else {
//如果不为空,表示这是列表页,解析出详情页的url地址,放到任务队列中
for(Selectable selectable:list){
String jobInfoUrl = selectable.links().toString();
//把获取到的详情页的url地址放到任务队列中
page.addTargetRequest(jobInfoUrl);
}
//获取下一页按钮的url
String bkUrl=page.getHtml().css("div.p_in li.bk").nodes().get(1).links().toString();//get(1)拿到第二个
//把下一页的url放到任务队列中
page.addTargetRequest(bkUrl);
}
}
//解析页面,获取招聘详情信息,保存数据
private void saveJobInfo(Page page) {
//创建招聘详情对象
JobInfo jobInfo=new JobInfo();
//拿到解析的页面
Html html = page.getHtml();
//获取数据,封装到对象中
//两种获取的方法,一种是直接html.css,另一种是使用Jsoup.parse解析html字符串
jobInfo.setCompanyName(html.css("div.cn p.cname a","text").toString());
String addrStr = Jsoup.parse(html.css("div.cn p.msg").toString()).text();
String addr=addrStr.substring(0,addrStr.indexOf("|"));
jobInfo.setCompanyAddr(addr);
jobInfo.setCompanyInfo(html.css("div.tmsg","text").toString());
jobInfo.setUrl(page.getUrl().toString());
jobInfo.setJobName(Jsoup.parse(html.css("div.cn h1","title").toString()).text());
jobInfo.setJobAddr(addr);
jobInfo.setSalary(Jsoup.parse(html.css("div.cn strong").toString()).text());
//把结果保存起来
page.putField("jobInfo",jobInfo);
}
private Site site=Site.me()
.setCharset("gbk")//设置编码(页面是什么编码就设置成什么编码格式的)
.setTimeOut(10*1000)//设置超时时间
.setRetrySleepTime(3000)//设置重试的间隔时间
.setRetryTimes(3);//设置重试的次数
@Override
public Site getSite() {
return site;
}
//这里注入SaveData
@Autowired
private SaveData saveData;
//initialDelay当任务启动后,等多久执行方法
//fixedDelay每个多久执行方法
@Scheduled(initialDelay = 1000,fixedDelay = 100*1000)
public void process(){
Spider.create(new JobProcessor())
.addUrl(url)
.setScheduler(new QueueScheduler().setDuplicateRemover(new BloomFilterDuplicateRemover(100000)))
.thread(10)
.addPipeline(this.saveData)//指定把爬取的数据保存到SaveData类的ResultItems中
.run();
}
}
4,获取爬到的数据并保存到数据库
前面爬取的数据(封装到了实体类)都保存在了ResultItems对象中
这里取出前面保存的数据(实体类),然后把数据存到数据库
package com.qianlong.task;
import com.qianlong.entity.JobInfo;
import com.qianlong.service.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.Pipeline;
@Component
public class SaveData implements Pipeline {
@Autowired
Service service;
@Override
public void process(ResultItems resultItems, Task task) {
//获取封装好的招聘详情对象
JobInfo jobInfo=resultItems.get("jobInfo");
if(jobInfo!=null){
//保存数据到数据库中
service.saveJobInfo(jobInfo);
}
}
}
5,dao和service
public interface Dao {
@Insert(value = "insert into jobinfo(companyName,companyAddr,companyInfo,jobName,jobAddr,salary,url) values(#{companyName},#{companyAddr},#{companyInfo},#{jobName},#{jobAddr},#{salary},#{url});")
int saveJobInfo(JobInfo jobInfo);
}
public interface Service {
int saveJobInfo(JobInfo jobInfo);
}
@Service
public class ServiceImpl implements Service {
@Autowired
private Dao dao;
@Override
public int saveJobInfo(JobInfo jobInfo) {
int i = dao.saveJobInfo(jobInfo);
return i;
}
}
然后运行启动类,控制台出现下图就是爬取成功了
爬取的数据保存到数据库成功
七,后话
至于WebMagic的完整使用,还需要涉及到代理服务器,因为有一些网站是禁止爬取的,它会查到你的ip地址并给你禁掉,所以这时就需要代理服务器。以及爬取数据的去重问题,还要借助一些其他的工具平台进行处理整合,所以,有待完善。
每天进步一点点,有问题留言兄弟盟!