WebMagic的底层用到了HttpClient和Jsoup 能够更方便地开发爬虫
WebMagic项目代码分为核心和扩展两部分
其中:
WebMagic的设计目标是尽量的模块化 并体现爬虫的功能特点
提供了非常简单 灵活的API 可以在基本不改变开发模式的情况下编写一个爬虫
WebMagic的结构分为【Downloader】 【PageProcessor】 【Scheduler】 【Pipeline】四大组件
并由【Spider】将它们彼此组织起来
这四大组件对应了爬虫生命周期中的下载 处理 管理和持久化等功能
示意图:
是Spider将这几个组件组织起来 让它们可以互相交互 流程化的执行
可以认为Spider是一个大的容器 它也是WebMagic逻辑的核心
Request是对URL地址的一层封装
一个Request对应一个URL地址
是PageProcessor与Downloader交互的载体 也是PageProcessor控制Downloader的唯一方式
其有一个额外字段extra
可用于保存一些特殊的属性 然后在其它地方读取 以完成不同的功能 例如附加上页面信息
格式为key-value 键值对
Page代表了从Downloader下载到的一个页面(可能是HTML也可能是Json或者其它文本格式的内容)
是WebMagic抽取过程的核心对象 提供了一些方法可供抽取 结果保存等操作
ResultItems相当于一个Map 保存了PageProcessor处理的结果 供Pipeline使用
其API与Map很类似
有一个额外字段skip 若设为true的话 则代表不被Pipeline处理
首先 是引入依赖:
<dependency>
<groupId>us.codecraftgroupId>
<artifactId>webmagic-coreartifactId>
<version>0.7.3version>
dependency>
<dependency>
<groupId>us.codecraftgroupId>
<artifactId>webmagic-extensionartifactId>
<version>0.7.3version>
dependency>
注:0.7.3版本对SSL的支持并不完全 若直接从Maven中央仓库下载依赖
在爬取只支持SSL v1.2的网站会有SSL的异常抛出
解决方案:使用0.7.4版本 或 直接从github上下载最新代码安装到本地仓库
然后将github下载的webmagic-core放于本地一个目录下
选择从本地导入:
选择Maven:
点击该按钮:
安装:
然后是添加log4j.properties日志配置文件
(因为WebMagic的内部已整合了slf4j的依赖)
log4j.rootLogger=DEBUG,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
接下来 就可以很方便地使用了:
// 实现PageProcessor类 复写方法
public class JobProcessor implements PageProcessor {
// 解析页面
public void process(Page page)
{
将结果以键值对的形式放入ResultItems中
page.putField("ul",page.getHtml().css("div.right-1 ul").all());
}
private Site site=Site.me();
public Site getSite()
{
return site;
}
// 主函数 执行爬虫
public static void main(String[] args)
{
// Spider容器创建解析器 添加url地址 run执行爬虫
Spider.create(new JobProcessor()).addUrl("http://www.zjitc.net/xwzx/tztg.htm").run();
}
}
(返回的都是Selectable 因此可用链式方式编写)
XPath抽取
XPath是一门在XML文档中查找信息的语言
用来在XML文档中对元素和属性进行遍历
例1:
page.putField("ul",page.getHtml().xpath("//div[@class='right-1']/ul").all());
例2:
获取属性class=mt的div标签里面的h1标签的内容
page.getHtml().xpath("//div[@class=mt]/h1/text()")
正则表达式抽取
正则表达式是一种通用的文本抽取语言
在爬虫中通常用于获取url地址
例:
page.putField("ul",page.getHtml().css("div.right-1 ul h3").regex(".*关于.*").all());
CSS选择器抽取
例:
page.putField("ul",page.getHtml().css("div.right-1 ul").all());
或
page.putField("ul",page.getHtml().$("div.right-1 ul").all());
css()等价于$()
一条抽取规则 无论是XPath CSS选择器或是正则表达式 有可能抽取到多条元素
WebMagic可以通过不同的API获取到一个或多个元素
返回一条String类型的结果:
(默认返回第一条)
get()
例:String link=html.links().get()
toString()
例:String link=html.links().toString()
返回所有抽取结果:
all()
例:List links=html.links().all()
获取列表的超链接地址并通过该地址访问里面的网页
public void process(Page page)
{
// 将class为right-1的div中的ul中的所有a标签作为目标链接
page.addTargetRequests(page.getHtml().css("div.right-1 ul a").links().all());
// 将目标链接中的class为zz的div的内容作为value放入键值对中
page.putField("zz",page.getHtml().css("div.zz"));
}
可指定结果输出位置
WebMagic用于保存结果的组件是Pipeline
默认是通过控制台输出结果 也是通过Pipeline完成的 该Pipeline称作ConsolePipeline
若想将结果保存到文件中 只需将Pipeline的实现换成FilePipeline即可
可使用addPipeline()
来手动设置数据输出位置
public static void main(String[] args)
{
Spider.create(new JobProcessor())
.addUrl("http://www.zjitc.net/xwzx/tztg.htm")
.addPipeline(new FilePipeline("C:/Users/ABC/Desktop/Crawler"))//设置文件输出位置
.run();
}
保存的是Html格式 用ConsolePipeline时控制台打印的是什么 那么用FilePipeline输出到本地时里面的数据就是什么
使用.thread()来设置线程数
public static void main(String[] args)
{
Spider.create(new JobProcessor())
.addUrl("http://www.zjitc.net/xwzx/tztg.htm")
.addPipeline(new FilePipeline("C:/Users/A/Desktop/Crawler"))
.thread(5) 多线程
.run();
}
Spider是爬虫启动的入口
在启动爬虫之前 需要使用一个PageProcessor来创建一个Spider对象 然后使用run()进行启动
还可设置Spider的其它组件(Downloader Scheduler Pipeline)
Site.me()
可对爬虫进行一些配置 包括编码字符 抓取间隔 超时时间 重试次数等
private Site site=Site.me()
.setCharset("utf8") //设置编码
.setTimeOut(10000) //设置超时时间(单位:毫秒)
.setRetryTimes(3000) //设置重试的时间间隔(单位:毫秒)
.setSleepTime(3); //设置重试次数
public Site getSite() {
return site;
}
其它设置:
setUserAgent(String)
:设置代理
addCookie(String)
:添加Cookie
setDomain(String)
:设置域名
addHeader(String,String)
:添加请求头
setHttpProxy(HttpHost)
:设置Http代理
使用Spring内置的Spring Task来实现
这是Spring3.0加入的定时任务功能
使用@Scheduled
注解的方式定时启动爬虫进行数据爬取
属性:
cron:cron表达式 指定任务在特定时间执行
fixedDelay:上一次任务执行完后多久再执行 参数类型为long 单位毫秒
fixedDelayString:上一次任务执行完后多久再执行 参数类型为String 单位毫秒
fixedRate:按一定的频率执行任务 参数类型为long 单位毫秒
fixedRateString:按一定的频率执行任务 参数类型为String 单位毫秒
initialDelay:延迟多久后第一次执行任务 参数类型为long 单位毫秒
initialDelayString:延迟多久后第一次执行任务 参数类型为String 单位毫秒
zone:时区 默认为当前时区
某些业务要求较高 并不是定时定期处理 而是在特定的时间进行处理
此时需要使用cron表达式
cron表达式实际上是由七个子表达式描述个别细节的时间表
这些子表达式用空格进行分隔 每位分别代表:
其中:
/代表"每" 例如0/15代表每隔15分钟 从第0分钟开始执行
?代表每月的某一天或每周某一天
*代表整个时间段
L代表每月或每周的最后一天或每个月的最后一个星期几
例:6L代表每月的最后一个星期五
因此 0 0 12 ? * WED 就是代表在每星期三下午12:00执行
@Component
public class TaskTest {
@Scheduled(cron = "0/8 * * * * *")
public void test()
{
System.out.println("定时任务start");
}
}
8秒执行一次
部分网站不允许爬虫进行数据爬取 因为会加大服务器的压力
其中一种最有效的方式是通过ip+时间进行鉴别 因为常人不可能短时间开启太多页面发起太多请求
WebMagic可设置爬取数据的时间 但会大大降低爬取数据的效率
若ip被禁了则无法爬取数据 此时 有必要使用代理服务器爬取数据
代理(Proxy) 也称网络代理 是一种特殊的网络服务
允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接
提供代理服务的电脑系统或其他类型的网络终端称为代理服务器(Proxy Server)
一个完整的代理请求过程为:客户端首先与代理服务器创建连接
接着根据代理服务器所使用的代理协议 请求对目标服务器创建连接 或获得目标服务器的指定资源
需要知道代理服务器的ip和端口号才可使用
网上有很多代理服务器的提供商 但大多是免费的 不好用 付费的会较好用
免费代理服务器:
米扑代理 https://proxy.mimvp.com/free.php
西刺代理 http://www.xicidaili.com
WebMagic使用的是APIProxyProvider
相对于Site的配置 ProxyProvider的定位更多是一个组件
代理不再从Site设置 而是由HttpClientDownloader设置
ProxyProvider有一个默认实现类:SimpleProxyProvider
是一个基于简单Round-Robin的 没有失败检查的ProxyProvider
可配置任意数量候选代理 每次会按顺序挑选一个代理使用
若要自己根据实际使用对代理服务器进行管理 还可自己实现APIProxyProvider
作为一个爬虫类 首先该类必须实现PageProcessor接口
@Component
public class ProxyTest implements PageProcessor {
...
}
创建爬虫:
@Scheduled(fixedDelay = 1000)
public void Process()
{
// 创建下载器Downloader
HttpClientDownloader httpClientDownloader=new HttpClientDownloader();
// 给下载器设置代理服务器信息
httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(new Proxy("183.91.33.41",89)));
Spider.create(new ProxyTest())
.addUrl("http://ip.chinaz.com/")
// 设置下载器
.setDownloader(httpClientDownloader)
.run();
}
解析页面:
@Override
public void process(Page page) {
System.out.println(page.getHtml().css("dl.IpMRig-tit dd.fz24","text").toString());
}
返回Site:
private Site site=Site.me();
@Override
public Site getSite() {
return site;
}