1、网络爬虫
1.1 抓取网页
1.1.1 什么是URI
URI(Universal Resource Identifier):通用资源标志符。Web上每种可用的资源,如HTML文档、图像、视频片段、程序等都由URI进行定位。
组成
- ① 访问资源的命名机制
- ② 存放资源的主机名
- ③ 资源自身的名称,由路径表示
例如:https://movie.douban.com/subject/25919408/
- 这是一个通过https下一访问的资源
- 位于主机movie.douban.com上
- 通过路径 /subject/25919408/ 进行访问
1.1.2 什么是URL
URL(Uniform Resource Locator)统一资源定位符:是URI的一个子集。通俗说来,URL是Internet上描述信息资源的字符串,主要用于在各种WWW客户程序和服务器程序上。
组成
采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。由三部分组成:
- ① 协议 / 服务方式
- ② 存有该资源的主机IP地址(包括端口号)
- ③ 主机资源的具体地址
【注】第一、二部分用 (冒号: + //) 隔开;第二、三部分使用 / 符号隔开
http://java.sun.com/index.html#chapter1
#表示附加的片段
1.1.3 通过URL抓取网页内容
网页抓取:指定是把URL地址中指定的网络资源从网络流中读取出来,保存到本地。
1、可以使用java.net.URL抓取网页,但是使用URL需要处理HTTP返回的状态码、设置HTTP代理、处理HTTPS协议等工作,代码量大。
2、使用Apache的HTTP客户端开源项目—HttpClient,它能够完全处理HTTP连接中的各种问题。需要引入HttpClient.jar包
GET请求和POST请求的区别
-
GET把参数包含在URL中,POST通过request body传递参数
-
GET产生一个TCP数据包;POST产生两个TCP数据包。
-
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
-
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
-
-
传送长度:get参数有长度限制(受限于url长度,最大1024字节),而post无限制
1.1.4 抓取实例
@Test
public void testSpider01() throws IOException {
//1. 打开一个浏览器客户端
CloseableHttpClient httpClient = HttpClients.createDefault();
//2. 输入网址,发起get/post请求
HttpGet httpGet = new HttpGet("https://blog.csdn.net/weixin_43889825?spm=1001.2101.3001.5343");
//3. 发起请求
CloseableHttpResponse response = httpClient.execute(httpGet);
//4. 解析相应,获取数据
if(response.getStatusLine().getStatusCode() == 200){ //200表示响应成过
HttpEntity entity = response.getEntity();
//将获取的页面转换为字符串
String res = EntityUtils.toString(entity, "utf8");
System.out.println(res);
}
}
【*】当响应的状态码为 5XX时,表示服务器异常,对结果进行简单抛弃即可。
1.1.5 Http状态码
什么是Http状态码??
HTTP状态码的英文为HTTP Status Code。当访问一个网页时,浏览器会向网页所在服务器发出请求,在浏览器接收并显示网页前,该服务器会返回一个包含HTTP状态码的信息头(server header)用以响应浏览器的请求。简单来说就是反映浏览器的请求状态或服务器的状态。
最常见的状态码
- 100 continue - 继续,服务器成功收到请求,需要请求者继续执行操作
- 200 - 请求成功,一般用于GET和POST请求
- 301 - 资源(网页等)被永久转移到其它URL,客户端需要使用新的URL
- 302 - 临时移动,客户端继续使用原来的URL
- 404 - 请求的资源(网页等)不存在
- 500 - 内部服务器错误
状态码分类
1** | **信息,**服务器收到请求,需要请求者继续执行操作 |
---|---|
2** | **成功,**操作被成功接收并处理 |
3** | **重定向,**需要进一步的操作以完成请求 |
4** | **客户端错误,**请求包含语法错误或无法完成请求 |
5** | 服务器错误,服务器在处理请求的过程中发生了错误 |
1.2 网络爬虫简介
网络爬虫(Web Crawler,又称为网页蜘蛛,网络机器人)是一种可以代替人工自动地在互联网中进行数据信息的采集与整理,并且按照一定的规则,自动地抓取万维网信息的程序或者脚本。
从功能上来讲,爬虫一般分为数据采集、处理、存储三个部分。爬虫从一个或若干个初始页面的URL开始,获取到初始页面上的URL,在抓取网页的过程中,不断从当前页面上抽取出新的URL放入到队列,直到满足系统的条件即可停止爬取。
2、HttpClient基本原理
官方文档:http://hc.apache.org/httpcomponents-client-ga/tutorial/html/
2.1 请求执行
HttpClient最基本的一个功能是执行Http方法。Http方法的执行涉及一个或多个Http请求/Http响应交互,通常由HttpClient内部处理。用户只需要提供一个请求对象,然后由HttpClient将请求传输到目标服务器,如果成功响应,则返回响应的响应体;若不成功,则抛出异常。
请求执行的基本格式如下所示:
//1. 打开一个浏览器客户端
CloseableHttpClient httpClient = HttpClients.createDefault();
//2. 输入网址,发起get请求
HttpGet httpGet = new HttpGet("https:baidu.com/");
//3. 发起请求
CloseableHttpResponse response = httpClient.execute(httpGet);
try{
} finally {
response.close();
}
2.1.1 HTTP请求
所有的HTTP请求都包括一个方法名、请求URI和HTTP协议版本号的请求行。HttpClient支持HTTP /1.1规范中定义的所有Http方法:GET、HEAD、POST、PUT、DELETE、TRACE和OPTIONS,存在对应的每种请求类:HttpGet,HttpHead,HttpPost,HttpPut, HttpDelete,HttpTrace,和HttpOptions。
请求的URI是一个统一资源标识符,用于标识要在其上应用请求的资源。HTTP请求URI由协议方案,主机名,可选端口,资源路径,可选查询和可选片段组成
https://book.douban.com/subject_search?search_text=米伊林
HttpClient提供了URIBuilder
实用程序类,以简化请求URI的创建和修改。
URI rui = new URIBuilder()
.setScheme("https")
.setHost("book.douban.com")
.setPath("/subject_search")
.setParameter("search_text","米伊林")
.build();
HttpGet httpGet = new HttpGet(rui);
2.1.2 HTTP响应
HTTP响应是服务器在收到浏览器发送的请求后解释请求并返回请求的结果信息。该信息的第一行包括协议版本号,数字状态码和关联的文本组成。
//发起请求
CloseableHttpResponse response = httpClient.execute(httpGet);
System.out.println("版本协议号:"+response.getProtocolVersion());
System.out.println("响应状态码:"+response.getStatusLine().getStatusCode());
System.out.println(response.getStatusLine().getProtocolVersion());
System.out.println(response.getStatusLine().getReasonPhrase()); //状态信息
System.out.println(response.getStatusLine().toString());
response.close();
版本协议号:HTTP/1.1
响应状态码:200
HTTP/1.1
OK
HTTP/1.1 200 OK
2.1.3 HTTP Entity(Http实体对象)
HTTP消息可以携带与请求或响应相关联的内容实体。实体只可以在一些请求或响应中找到,因为它们是可选的。使用实体的请求被称为实体封闭请求(entity enclosing requests)。
HTTP规范定义了两个实体封闭请求方法:POST和PUT。响应通常会携带消息体。此规则也有例外情况,例如对HEAD方法的响应和对204无内容、304未修改、205重置内容的响应。
- streamed:内容是从一个流接收,或者是随时产生的。具体来说,这个类别包括从HTTP响应中收到的实体。流派实体通常不可重复。
- self-contained:内容在内存中或通过独立于连接或其他实体的方式获得。该类实体通常可重复。这种类型的实体将主要用于包含HTTP请求的实体。
- wrapping:内容是从另一个Entity获得的。
2.2 连接管理
连接持久性
建立从一个主机到另一个主机的连接的过程相当复杂,并且涉及两个端点之间的多个分组交换,这可能相当耗时。连接握手的开销可能很大,特别是对于小型的HTTP消息。如果可以重新使用开放连接来执行多个请求,则可以实现更高的数据吞吐量。
HTTP / 1.1规定HTTP连接可以重复用于多个请求。符合HTTP / 1.0的端点还可以使用一种机制来显式传达它们的首选项,以保持连接的活动状态并将其用于多个请求。HTTP代理还可以保持空闲连接在一段时间内保持活动状态,以便后续请求连接到同一个目标主机。保持连接的能力通常被称为连接持久性。HttpClient完全支持连接持久性
2.2.1 HTTP连接管理器
- HTTP连接是复杂的,有状态的,线程不安全的对象,需要妥善管理才能正常工作。
- HTTP连接一次只能由一个执行线程使用。
- HttpClient使用一个特殊的实体来管理HTTP连接的访问,称为HTTP连接管理器,并由HttpClientConnectionManager接口表示。
- HTTP连接管理器的目的是作为新的HTTP连接的工厂,管理持久连接的生命周期,并同步对持久连接的访问,以确保一次只有一个线程可以访问连接。
- 内部HTTP连接管理器与ManagedHttpClientConnection实例一起工作,作为管理连接状态和控制I / O操作执行的真实连接的代理。如果托管连接被释放或被其使用者明确关闭,则底层连接从其代理分离,并返回给管理器。即使服务消费者仍然持有对代理实例的引用,它不再有意或无意地执行任何I / O操作或改变真实连接的状态。
简单连接管理 BasicHttpClientConnectionManager
BasicHttpClientConnectionManager是一个简单的连接管理器,一次只维护一个连接。即使这个类是线程安全的,它也只能被一个执行线程使用。BasicHttpClientConnectionManager将努力重复使用相同路由的后续请求的连接。但是,如果持续连接的路由与连接请求的路由不匹配,它将关闭现有连接并重新打开给定路由。如果连接已被分配,则引发java.lang.IllegalStateException。
连接池连接管理 PoolingHttpClientConnectionManager
PoolingHttpClientConnectionManager是一个更复杂的实现,它管理一个客户端连接池,并能够处理来自多个执行线程的连接请求。连接按照其路由进行汇集。对于管理器已经在池中具有持续连接的路由的请求将通过从池复用连接而不是创建全新的连接来进行服务。
PoolingHttpClientConnectionManager保持针对一个路由的连接和所有连接的最大限制。默认情况下,每个给定的路由创建不超过2个并发连接,总共不超过20个连接。
3、HttpClient
3.1 GET请求
不带参数的get请求
public void testSpider01() {
//1. 打开一个浏览器客户端
CloseableHttpClient httpClient = HttpClients.createDefault();
//2. 输入网址,发起get/post请求
HttpGet httpGet = new HttpGet("https://blog.csdn.net/weixin_43889825?spm=1001.2101.3001.5343");
//3. 发起请求
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpGet);
//4. 解析相应,获取数据
if(response.getStatusLine().getStatusCode() == 200){ //200表示响应成过
HttpEntity entity = response.getEntity();
//将获取的页面转换为字符串
String res = EntityUtils.toString(entity, "utf8");
System.out.println(res);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭response
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
//关闭客户端浏览器
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
带参数的Get请求
设置URIBuilder使用程序类。需要设置URI(通用资源标识符)
public void doGetWithParam() throws URISyntaxException {
//1、创建一个客户端浏览器
CloseableHttpClient httpClient = HttpClients.createDefault();
//http://yun.itheima.com/search?keys=Java
//2、设置需要请求的RUL
URI rui = new URIBuilder()
.setScheme("http")
.setHost("yun.itheima.com")
.setPath("/search")
.setParameter("keys","Java")
.build();
// 创建请求对象 Get方式 ==>> HttpGet
HttpGet httpGet = new HttpGet(rui);
System.out.println(httpGet.getURI());
//3、发起请求并获取响应信息
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpGet);
//4、解析响应
if(response.getStatusLine().getStatusCode() == 200){
String context = EntityUtils.toString(response.getEntity(), "utf8");
System.out.println(context);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//5、释放资源
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
也可以这样URL:
URI rui = new URIBuilder("http://yun.itheima.com/search");
uri.setParameter("keys","Java").setParameter("",""); //流式设置,有多个参数时,可以在后面直接添加 .setParameter(" "," ")
3.2 POST请求
不带参数的post请求
不带参数的post请求与不带参数的get请求类似,不同点在于创建请求的对象不同
- GET ===>> HttpGet
- POST ===>> HttpPost
public void doPost(){
//创建一个浏览器客户端
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建请求对象并输入待请求的URL
HttpPost httpPost = new HttpPost("http://www.baidu.com");
//创建响应对象
CloseableHttpResponse response = null;
try {
//执行请求
response = httpClient.execute(httpPost);
//解析响应
if(response.getStatusLine().getStatusCode() == 200){
String context = EntityUtils.toString(response.getEntity(), "utf8");
System.out.println(context.length());
}
} catch (IOException e) {
e.printStackTrace();
} finally{
//释放资源
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
带参数的POST请求
GET把参数包含在URL中,POST通过request body传递参数。POST请求的URL中不能携带参数,所以参数keys=value需要放在表单中进行提交。
- 使用List集合封装表单中的参数
- 创建表单的实体(Entity)对象
- 设置表单的Entity对象的请求到POST请求中
public interface NameValuePair {
String getName();
String getValue();
}
//NameValuePair是一个接口,需要使用其实现类:BasicNameValuePair
//创建一个浏览器客户端
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建请求对象并输入待请求的URL
HttpPost httpPost = new HttpPost("http://yun.itheima.com/search");
//声明List集合,封装表单中的参数
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("keys","Java"));
//创建表单的Entity对象,参数1为封装号的参数,第二个参数为编码
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(params,"utf8");
//设置表单的Entity对象到Post请求中
httpPost.setEntity(formEntity);
3.3 连接池
使用池的概念,复用HttpClient,节约资源。
public static void main(String[] args) {
//创建连接池管理器
PoolingHttpClientConnectionManager pccm = new PoolingHttpClientConnectionManager();
//设置最大连接数
pccm.setMaxTotal(100);
//设置每个主机的最大连接数
pccm.setDefaultMaxPerRoute(10);
//使用连接池管理器发起请求
doGet(pccm);
doGet(pccm);
}
private static void doGet(PoolingHttpClientConnectionManager pccm){
//创建连接池管理器
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(pccm).build();
}
3.4 请求参数
有时候因为网络,或者目标服务器的原因,请求需要更长的时间才能完成,这时候需要自定义相关时间优化请求。
使用RequestConfig类配置请求信息。
//设置请求参数
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(1000) //设置创建连接的超时时间,单位:毫秒
.setConnectionRequestTimeout(500) //设置从连接池管理器中获取连接的超时时间,毫秒
.setSocketTimeout(1000); //设置数据传输的超时时间, 毫秒
配置请求信息
//配置请求信息
RequestConfig config = RequestConfig.custom()
.setConnectionRequestTimeout(1000)
.setConnectionRequestTimeout(500)
.build();
//给请求设置配置信息
httpGet.setConfig(config);
4、Jsoup
抓取页面后需要对页面进行解析,可使用字符串处理工具解析页面,也可以使用正则表达式,但是这些都会带来很大的开发成本。
4.1 jsoup介绍
需要的jar包
jsoup
Apache commons-io
Apache commons-lang3
jsoup是一款Java的Html文本解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API,可通过DOM、css以及类似jQuery的操作方法来取出和操作数据。
主要功能:
- 从一个URL,文件或字符串中解析HTML;
- 使用DOM或CSS选择器来查找、取出数据;
- 可操作HTML元素、属性、文本
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.12.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
jsoup解析URL
@Test
public void jsoupUrl() throws Exception {
/* 参数1:表示需要请求的URL地址
参数2:表示超时时间,单位是毫秒
经Jsoup解析的结果为Document对象
*/
Document document = Jsoup.parse(new URL("https://www.cnblogs.com/hellokuangshen/"), 10000);
String text = document.getElementsByTag("title").first().text();
System.out.println(text);
}
jsoup解析字符串
@Test
public void jsoupString() throws Exception {
/*FileUtils:是commons-io.jar包中的一个操作文件的工具类*/
//将html文件读入内存并转换为字符串
String file = FileUtils.readFileToString(new File("D:/大创文件/task2/case1.html"), "utf8");
//解析html文件
Document document = Jsoup.parse(file);
System.out.println(document.getElementsByTag("div").text());
}
jsoup解析文件
@Test
public void jsoupFile() throws Exception{
/* 参数1:文件;参数2:编码*/
Document doc = Jsoup.parse(new File("D:/大创文件/task2/case1.html"), "utf8");
System.out.println(doc.getElementsByTag("link"));
}
4.2 获取元素
使用Document遍历文档
获取元素
- 根据id查询元素getElementById
- 根据标签获取元素getElementsByTag
- 根据class获取元素getElementsByClass
- 根据属性获取元素getElementsByAttribute
使用选择器Selector获取元素
document.select(“标签名”); //通过标签查找
- tagname:通过标签查找元素,比如:span
- #id:通过id查找元素
- .class:通过class名称查找元素
- [attribute]:利用属性查找元素
- [attr=value]:利用属性值来查找元素
public void getElementBySeletor() throws Exception {
Document doc = Jsoup.parse(new File("src/file/test.html"), "utf8");
//通过id查找元素
Elements el = doc.select("#d1");
System.out.println(el.text());
}
使用组合选择器获取元素
- el#id:元素+id
- el.class:元素+class
- el[attr]:元素+属性名
- 任意组合:span[abc].s_name
- ancestor child:查找某个元素下子元素
- parent > child:查找某个父元素下的直接子元素
- parent > *:查找某个父元素下的所有子元素
5、爬虫框架WebMagic
5.1 WebMagic简介
1、目标:尽量模块化,并体现爬虫的功能特点
2、组成:
WebMagic项目代码分为核心和扩展两部分:
- 核心部分 webmagic-core:是一个精简的、模块化的爬虫实现
- 扩展部分 webmagic-extension :包括一些便利的、实用性的功能,例如注解模式编写爬虫等。同时内置了一些常用的组件,便于爬虫开发。
5.1.1 特征
- 微内核与高可用性:WebMagic由四个组件(Downloader、PageProcessor、Scheduler、Pipeline)构成,核心代码非常简单,主要是将这些组件结合并完成多线程的任务。所以你基本上可以对爬虫的功能做任何定制。
- 注重实用性:让使用者开发爬虫尽可能的简单,尽可能的易维护。
5.1.2 总体框架
webmagic的四大组件对应的爬虫生命周期的四个功能
- Downloader:下载
- 负责从互联网上下载页面,以便后续处理
- PageProcessor:处理
- 负责解析页面,抽取有用信息,以及**发现新的链接 **
- 对于每个站点,每个页面都不一样,需要使用者自己设计
- Scheduler:管理(需要下载的URL地址;任务)
- **负责管理待抓取的URL,以及一些去重的工作 **
- 默认提供JDK的内存管理队列来管理URL,并用集合进行去重
- 支持Redis进行分布式管理
- Pipeline:输出、持久化等功能
- 负责抽取结果的处理,包括计算、持久化到文件、数据库等
- 默认提供两种结果处理方案
- 输出到控制台
- 保存到文件
- 只定义了结果保存的方式,如果需要保存到指定数据库,则应编写对应的Pipeline。对于一类需求一般只需编写一个Pipeline。
Spider介绍:
- 是一个容器,负责将这几个组件组织起来,让它们可以互相交互,流程化的执行。
- 是WebMagic逻辑的核心
- Downloader、PageProcessor、Scheduler、Pipeline四个组件都是Pipeline的一个属性
- 是WebMagic操作的入口,它封装了爬虫的创建、启动、停止、多线程等功能
public class Spider implements Runnable, Task {
protected Downloader downloader; //下载
protected List<Pipeline> pipelines = new ArrayList<Pipeline>(); //处理最终结果
protected PageProcessor pageProcessor; //处理页面
protected List<Request> startRequests; //封装URL
protected Site site; //站点
protected String uuid;
protected Scheduler scheduler = new QueueScheduler(); //管理URL
protected Logger logger = LoggerFactory.getLogger(getClass()); //日志
protected CountableThreadPool threadPool; //线程池
protected ExecutorService executorService; //执行服务
@FunctionalInterface
public interface Runnable { //创建线程的结构
public abstract void run(); //执行线程
}
public interface Task {
public String getUUID(); //获取任务的id
public Site getSite(); //获取任务的状态
}
5.1.3 数据流转对象
图1中的数据流转对象的介绍
-
Request:是对URL地址的一层封装,一个Request对应一个URL地址 。请求的地址
-
它是PageProcessor(页面解析)与Downloader(下载)交互的载体,也是PageProcessor控制Downloader唯一方式。
-
还包含一个key-value类型的字段,可用于保存特殊的属性
-
-
Page:表示从Downloader下载到的一个页面,可能是HTML、JSON或者其他文本格式的内容。请求地址之后返回的数据。
- Page是WebMagic抽取过程的核心对象,它提供一些方法可供抽取、结果保存等
-
ResultItems:相当于一个Map集合,用于保存PageProcessor处理的结果,供Pipeline使用
- 注:包含一个skip字段,若设置为true,表示不需要由Pipeline处理
5.2 第一个爬虫项目
5.2.1 使用maven导入依赖
导入webmagic的核心包core与扩展包extension。同时使用slf4j作为日志打印工具
<dependency>
<!-- 核心包 -->
<groupId>us.codecraft</groupId>
<artifactId>webmagic-core</artifactId>
<version>0.7.3</version>
</dependency>
<dependency>
<!-- 扩展包 -->
<groupId>us.codecraft</groupId>
<artifactId>webmagic-extension</artifactId>
<version>0.7.3</version>
</dependency>
【注】
-
0.7.3版本对SSL的并不安全,如果事直接从Maven中央仓库下载依赖,在爬取只支持SSLv1.2的网站会有SSL的异常抛出。
-
出现的异常:
-
作者解释:WebMagic默认的HttpClient只会使用TSLv1去请求,对于某些只支持TSL1.2的站点,就会报错。
-
异常:javax.net.ssl.SSLException: Received fatal alert: protocol_version
-
解决方案:临时适配方式,修改HttpClientGenertor中的buildSSLConnectionSocketFactory方法
return new SSLConnectionSocketFactory(createIgnoreVerifySSL(),new String[]{"SSL3","TSLv1","TLSv1.1","TSLV1.2"}, null, new DefaultHostnameVerifier());//优先绕过安全协议 //从GitHub上下载WebMagic源码,webmagic-core中的HttpClientGenertor中的方法,也能解决上述问题。然后install点击跳过测设安装到本地仓库 SSLContext sslContext = createIgnoreVerifySSL(); String[] supportedProtocols; if (SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_11)) { supportedProtocols = new String[] { "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" }; } else { supportedProtocols = new String[] { "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" }; } logger.debug("supportedProtocols: {}", String.join(", ", supportedProtocols)); return new SSLConnectionSocketFactory(sslContext, supportedProtocols, null, new DefaultHostnameVerifier()); // 优先绕过安全证书
-
-
WebMagic使用slf4j-log4j12作为slf4j的实现.如果你自己定制了slf4j的实现,需要项目中去掉此依赖。如下配置所示,exclusions标签表示不包括某个jar包。
<dependency>
<groupId>us.codecraft</groupId>
<artifactId>webmagic-extension</artifactId>
<version>0.7.3</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
SSL(Secure Sockets Layer 安全套接字协议),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性数据完整性的一种安全协议。TLS与SSL在传输层与应用层之间对网络连接进行密。
5.2.2 简单实例
import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Spider;
import us.codecraft.webmagic.processor.PageProcessor;
public class JobProcessor implements PageProcessor {
//解析页面
public void process(Page page) {
page.putField("div",page.getHtml().css("div#content_views h1").all());
}
private Site site = Site.me();
public Site getSite() {
return site;
}
//主函数,启动爬虫
public static void main(String[] args) {
Spider.create(new JobProcessor())
//addUrl("url"):添加需要爬取的url
.addUrl("https://blog.csdn.net/weixin_43889825/article/details/111743857")
.run(); //启动爬虫
}
}
/*结果:
div: [<h1><a id="1MVC_0"></a>1、回顾MVC</h1>, <h1><a id="2Spring_MVC_72"></a>2、Spring MVC</h1>, <h1><a id="3MVC_155"></a>3、第一个MVC程序</h1>, <h1><a id="4RestFul_428"></a>4、RestFul和控制器</h1>, <h1><a id="5SpringMVC__621"></a>5、数据处理及跳转(SpringMVC参数接收处理和结果跳转 )</h1>, <h1><a id="6JSON_823"></a>6、JSON数据交换</h1>]
div为key,[]内的数据为value
*/
5.3 实现PageProcessor
5.3.1 抽取元素Selectable
webmagic里主要使用了三种抽取技术:XPath、正则表达式和CSS选择器。另外,对于JSON的格式,可以使用JsonPath进行解析。
Xpath
获取属性class=mt的div标签,并获取里面h1标签的内容。更多详细资料可查看W3School官网查看XPath语法。
page.getHtml.xpath("//div[@class=mt]/h1/text()");
使用CSS选择器
page.putField("div",page.getHtml().css("div#content_views h1").all());
抽取元素的API
方法 | 说明 | 示例 |
---|---|---|
xpath(String xpath) | 使用XPath选择 | html.xpath("//div[@class=‘title’]") |
$(String selector) | 使用Css选择器选择 | html.$(“div.title”) |
$(String selector,String attr) | 使用Css选择器选择 | html.$(“div.title”,“text”) |
css(String selector) | 功能同$(),使用Css选择器选择 | html.css(“div.title”) |
links() | 选择所有链接 | html.links() |
regex(String regex) | 使用正则表达式抽取 | html.regex("(.*?)") |
regex(String regex,int group) | 使用正则表达式抽取,并指定捕获组 | html.regex("(.*?)",1) |
replace(String regex, String replacement) | 替换内容 | html.replace("","") |
获取结果部分的API
方法 | 说明 | 示例 |
---|---|---|
get() | 返回一条String类型的结果 | String link= html.links().get() |
toString() | 功能同get(),返回一条String类型的结果 | String link= html.links().toString() |
all() | 返回所有抽取结果 | List links= html.links().all() |
match() | 是否有匹配结果 | if (html.links().match()){ xxx; } |
获取链接
爬取网页时,在爬取的页面存在需要处理的新链接。
//获取链接。利用css选择器选择需要获取的链接的范围,然后使用正则表达式确定链接需要包括的什么
page.addTargetRequests(page.getHtml().css("").links().regex("").all());
5.3.2 使用Pipeline保存结果
WebMagic用于保存结果的组件叫做Pipeline。通过“控制台输出结果”这个事件也是通过内置的Pipeline来完成的,该功能为ConsolePipelinr。如果需要将结果保存在文件中,只需要将Pipeline的实现换成“FilePipeline”即可。
//将结果保存在文件中,添加addPipeline(new FilePipeline("path"))即可
public static void main(String[] args) {
Spider.create(new JobProcessor())
.addUrl("https://blog.csdn.net/weixin_43889825/article/details/111743857")
.addPipeline(new FilePipeline("src/file/"))
.thread(3) //爬虫使用了几个线程
.run(); //启动爬虫
}
获取的结果如下:
url: https://blog.csdn.net/weixin_43889825/article/details/111743857
div:
<h1><a id="1MVC_0"></a>1、回顾MVC</h1>
<h1><a id="2Spring_MVC_72"></a>2、Spring MVC</h1>
<h1><a id="3MVC_155"></a>3、第一个MVC程序</h1>
<h1><a id="4RestFul_428"></a>4、RestFul和控制器</h1>
<h1><a id="5SpringMVC__621"></a>5、数据处理及跳转(SpringMVC参数接收处理和结果跳转 )</h1>
<h1><a id="6JSON_823"></a>6、JSON数据交换</h1>
5.4 爬虫的配置,启动、终止
5.4.1 Spider容器
Spider.create(new WebSpiderTest())
.addUrl("https://github.com/code4craft").thread(5).run();
1、Sprider是爬虫启动的入口,在启动爬虫之前,需要使用PageProcessor创建一个Spider对象,然后调用run()方法启动爬虫。
方法:
//1、创建Spider
public static Spider create(PageProcessor pageProcessor) {
return new Spider(pageProcessor);
}
//2、添加初始的URL
public Spider addUrl(String... urls) {
for (String url : urls) {
addRequest(new Request(url));
}
signalNewUrl();
return this;
}
//3、添加初始的Request
private void addRequest(Request request) {
if (site.getDomain() == null && request != null && request.getUrl() != null) {
site.setDomain(UrlUtils.getDomain(request.getUrl()));
}
scheduler.push(request, this);
}
//4、开启多个线程
public Spider thread(int threadNum) {
checkIfRunning();
this.threadNum = threadNum;
if (threadNum <= 0) {
throw new IllegalArgumentException("threadNum should be more than one!");
}
return this;
}
//5、启动,会阻塞当前线程(main线程)执行
spider.run()
//6、异步启动,当前线程继续执行
start()/runAsync()
public void start() {
runAsync();
}
//7、停止爬虫
public void stop() {
if (stat.compareAndSet(STAT_RUNNING, STAT_STOPPED)) {
logger.info("Spider " + getUUID() + " stop success!");
} else {
logger.info("Spider " + getUUID() + " stop fail!");
}
}
//8、抓取一个页面进行测试
public void test(String... urls) {
initComponent();
if (urls.length > 0) {
for (String url : urls) {
processRequest(new Request(url));
}
}
}
spider .test("http://webmagic.io/docs/"); //例如
addPipeline(Pipeline) | 添加一个Pipeline,一个Spider可以有多个Pipeline | spider .addPipeline(new ConsolePipeline()) |
---|---|---|
setScheduler(Scheduler) | 设置Scheduler,一个Spider只能有个一个Scheduler | spider.setScheduler(new RedisScheduler()) |
setDownloader(Downloader) | 设置Downloader,一个Spider只能有个一个Downloader | spider .setDownloader(new SeleniumDownloader()) |
get(String) | 同步调用,并直接取得结果 | ResultItems result = spider .get(“http://webmagic.io/docs/”) |
getAll(String…) | 同步调用,并直接取得一堆结果 | List results = spider .getAll(“http://webmagic.io/docs/”, “http://webmagic.io/xxx”) |
5.4.2 爬虫配置Site
对站点本身的一些配置信息,例如编码、HTTP头、超时时间、重试策略等、代理等,都可以通过设置Site
对象来进行配置。
方法 | 说明 | 示例 |
---|---|---|
setCharset(String) | 设置编码 | site.setCharset(“utf-8”) |
setUserAgent(String) | 设置UserAgent | site.setUserAgent(“Spider”) |
setTimeOut(int) | 设置超时时间,单位是毫秒 | site.setTimeOut(3000) |
setRetryTimes(int) | 设置重试次数 | site.setRetryTimes(3) |
setCycleRetryTimes(int) | 设置循环重试次数 | site.setCycleRetryTimes(3) |
addCookie(String,String) | 添加一条cookie | site.addCookie(“dotcomt_user”,“code4craft”) |
setDomain(String) | 设置域名,需设置域名后,addCookie才可生效 | site.setDomain(“github.com”) |
addHeader(String,String) | 添加一条addHeader | site.addHeader(“Referer”,“https://github.com”) |
setHttpProxy(HttpHost) | 设置Http代理 | site.setHttpProxy(new HttpHost(“127.0.0.1”,8080)) |
private Site site = Site.me()
.setCharset("utf8")
.setTimeOut(10*1000)//毫秒
.setRetrySleepTime(2000) //设置重试事件
.setRetryTimes(2); //设置重试次数
public Site getSite() {
return site;
}
6、爬虫分类
网络爬虫按照系统结构和实现技术,大致可以分为以下几种类型:
- 通用网络爬虫(General Purpose Web Crawler)
- 聚焦网络爬虫(Focused Web Crawler)
- 增量式网络爬虫(Incremental Web Crawler)
- 深层网络爬虫(Deep Web Crawler)
实际的网络爬虫系统通常是几种爬虫技术相结合实现的。
6.1 通用网络爬虫
适用:门户站点搜索引擎、大型Web服务提供商采集数据 。
又称全网爬虫,爬取的对象是从一些种子URL扩充到整个Web,主要为门户站点搜索引起和大型Web服务提供商采集数据。这类网络爬虫的爬取范围和数量巨大,对于爬取速度和空间存储要求较高,对于爬取页面的顺序要求相对较低,同时由于待刷新的页面太多,通常采用并行工作方式,但需要较长时间才能刷新一次。
6.2 聚焦网络爬虫
只爬行特定的某种数据。
又称主题网络爬虫,指的是选择性爬取那些与预先定义好的主题相关页面的网络爬虫。和通用网络爬虫相比,聚焦网络爬虫只需爬取与主题相关的页面,极大地节省了硬件和网络资源,保存的页面也由于数量少而更快,还可以满足一些特定人群对特定领域信息的需求。
6.3 增量式网络爬虫
门户站点搜索引擎、大型Web服务提供商采集数据
指的是:对已下载网页采取增量式更新和只爬取新产生的或者已经发生改变的页面,它能够在一定程度上保证所爬取的页面是尽可能新的页面。和周期性爬取和刷新页面的网络爬虫相比,增量式的网络爬虫只会在需要的时候爬取新产生或发生更新的页面,并不重新下载没有发生变化的页面,可有效地减少数据下载量,即时更新已爬取的网页,减少时间和空间上的浪费,但是增加了爬虫算法的复杂度和实现难度。
6.4 Deep Web网络爬虫
Web网页按存在方式分为表层网页(Surface Web)和深层网页(Deep Web,也称Invisible Web Pages)。表层网页时指传统搜索引擎可以索引的页面,以超链接可以到达的静态网页为主构成的Web网页。Deep Web是那些大部分内容不能通过静态链接获取的、隐藏在搜索表单后,只有用户提交一些关键词才能获得的Web页面。