青春都一饷。忍把浮名,换了浅斟低唱!
——柳永《鹤冲天》
HttpClient和Jsoup详解
1. 网络爬虫简介
网络爬虫(web crawler)
,是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。
1.1 爬虫入门程序
JDK1.8
IntelliJ IDEA
DEA自带的Maven
环境准备
创建Maven工程itbuild-crawler-first
并往pom.xml
文件中添加依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<groupId>cn.itbuild</groupId> <artifactId>itbuild-crawler-first</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> </dependency> </dependencies> </project>
|
添加log4j.properties
1 2 3 4 5 6 7
| log4j.rootLogger=DEBUG,A1 log4j.logger.cn.itbuild=INFO
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??ss,SSS} [%t] [%c]-[%p] %m%n
|
编写代码
CrawlerFirst.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package cn.itbuild.crawler.test;
import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils;
import java.io.IOException;
public class CrawlerFirst { public static void main(String[] args) throws IOException {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://www.itcast.cn/");
CloseableHttpResponse response = httpClient.execute(httpGet);
if (response.getStatusLine().getStatusCode() == 200) { HttpEntity entity = response.getEntity(); String content = EntityUtils.toString(entity, "utf-8"); System.out.println(content); } }
}
|
1.2 网络爬虫介绍
在大数据时代,信息的采集
是一项重要的工作,而互联网中的数据是海量的,如果单纯靠人力进行信息采集,不仅低效繁琐,搜集的成木也会提高。如何自动高效地获取互联网中我们感兴趣的信息并为我们所用是一个重要的问题,而爬虫技术就是为了解决这些问题而生的。
网络爬虫(Web crawler)也叫做网络机器人,可以代替人们自动地在互联网中进行数据信息的采集与整理。它是一种按照一定的规则,自动地抓取万维网信息的程序或者脚木,可以自动采集所有其能够访问到的页面内容,以获取相关数据。
从功能上来讲,爬虫一般分为数据采集,处理,储存三个部分。
爬虫从一个或若干初始网页的URL开始,获得初始网页上的URL,在抓取网页的过程中,不断从当前页面上抽取新的URL放入队列,直到满足系统的一定停止条件。
1.3 为什么要学习网络爬虫
我们初步认识了网络爬虫,但是为什么要学习网络爬虫呢?只有清晰地知道我们的学习目的,才能够更好地学习这一项知识。在此,总结了4种常见的学习爬虫的原因:
可以实现搜索引擎
我们学会了爬虫编写之后,就可以利用爬虫自动地采集互联网中的信息,采集回来后进行相应的存储或处理,在需要检索某些信息的时候,只需在采集回来的信息中进行检索,即实现了私人的搜索引擎。
大数据时代,可以让我们获取更多的数据源
在进行大数据分析或者进行数据挖掘的时候,需要有数据源进行分析。我们可以从某些提供数据统计的网站获得,也可以从某些文献或内部资料中获得,但是这些获得数据的方式,有时很难满足我们对数据的需求,而手动从互联网中去寻找这些数据,则耗费的精力过大。此时就可以利用爬虫技术,自动地从互联网中获取我们感兴趣的数据内容,并将这些数据内容爬取回来,作为我们的数据源,再进行更深层次的数据分析,并获得更多有价值的信息。
可以更好地进行搜索引擎优化 (SEO)
对于很多SEO从业者来说,为了更好的完成工作,那么就必须要对搜索引擎的工作原理非常清楚,同时也需要掌握搜索引擎爬虫的工作原理。
而学习爬虫,可以更深层次地理解搜索引擎爬虫的工作原理,这样在进行搜索引擎优化时,才能知己知彼,百战不殆。
有利于就业, 爬虫工程师需求量大, 发展空间广
从就业来说,爬虫工程师方向是不错的遥择之一,因为目前爬虫工程师的需求越来越大,而能够胜任这方面岗位的人员较少,所以属于一个比较紧缺的职业方向,并且随着大数据时代和人工智能的来临,爬虫技术的应用将越来越广泛,在未来会拥有很好的发展空间。
2. HttpClient
网络爬虫就是用程序帮助我们访问网络上的资源,我们一直以来都是使用HTTP协议访问互联网的网页,网络爬虫需要编写程序,在这里使用同样的HTTP协议访问网页。
这里我们使用Java 的 HTTP协议客户端HttpClient这个技术,来实现抓取网页数据。
2.1 Get请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package cn.itbuild.crawler.test;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils;
import java.io.IOException;
public class HttpGetTest { public static void main(String[] args) { CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://www.itcast.cn");
CloseableHttpResponse response = null; try { response = httpClient.execute(httpGet); if (response.getStatusLine().getStatusCode() == 200){ String content = EntityUtils.toString(response.getEntity(), "utf-8"); System.out.println(content.length()); System.out.println(content); } } catch (IOException e) { e.printStackTrace(); }finally { if (response != null) { try { response.close(); } catch (IOException e) { e.printStackTrace(); } } if (httpClient != null) { try { httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
|
2.2 带参数的Get请求
在传智中搜索学习视频,地址为http://yun.itheima.com/search?keys=Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package cn.itbuild.crawler.test;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.utils.URIBuilder; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils;
public class HttpGetParamTest { public static void main(String[] args) throws Exception { CloseableHttpClient httpClient = HttpClients.createDefault();
URIBuilder uriBuilder = new URIBuilder("http://yun.itheima.com/search"); uriBuilder.setParameter("keys","Java");
HttpGet httpGet = new HttpGet(uriBuilder.build());
System.out.println("发送请求的信息:");
CloseableHttpResponse response = httpClient.execute(httpGet); if (response.getStatusLine().getStatusCode() == 200){ String content = EntityUtils.toString(response.getEntity(), "utf-8"); System.out.println(content.length()); System.out.println(content); }
} }
|
2.3 Post请求
使用POST 请求访问传智官网,请求url地址:http://www.itcast.cn/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package cn.itbuild.crawler.test;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils;
import java.io.IOException;
public class HttpPostTest { public static void main(String[] args) { CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost("http://www.itcast.cn");
CloseableHttpResponse response = null; try { response = httpClient.execute(httpPost); if (response.getStatusLine().getStatusCode() == 200){ String content = EntityUtils.toString(response.getEntity(), "utf-8"); System.out.println(content.length()); System.out.println(content); } } catch (IOException e) { e.printStackTrace(); }finally { if (response != null) { try { response.close(); } catch (IOException e) { e.printStackTrace(); } } if (httpClient != null) { try { httpClient.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
|
2.4 带参数的Post请求
在传智中搜索学习视频,使用POST请求,url地址为:http://yun.itheima.com/search
url地址没有参数,参数 keys=java放到表单中进行提交。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| package cn.itbuild.crawler.test;
import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils;
import java.util.ArrayList; import java.util.List;
public class HttpPostParamTest { public static void main(String[] args) throws Exception { CloseableHttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost("http://yun.itheima.com/search");
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("keys","Java"));
UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(params,"utf-8");
httpPost.setEntity(formEntity);
CloseableHttpResponse response = httpClient.execute(httpPost);
if (response.getStatusLine().getStatusCode() == 200){ String content = EntityUtils.toString(response.getEntity(), "utf-8"); System.out.println(content.length()); } } }
|
2.5 连接池
如果每次请求都要创建HttpClient,会有频繁创建和销毁的问题,可以使用连接池来解决这个问题。
测试以下代码,并断点查看每次获取的HttpClient都是不一样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| package cn.itbuild.crawler.test;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.util.EntityUtils;
import java.io.IOException;
public class HttpClientPoolTest { public static void main(String[] args) { PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); cm.setMaxTotal(100); cm.setDefaultMaxPerRoute(10);
doGet(cm); doGet(cm); }
private static void doGet(PoolingHttpClientConnectionManager cm) { CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
HttpGet httpGet = new HttpGet("http://www.itcast.cn");
CloseableHttpResponse response = null; try { response = httpClient.execute(httpGet); if (response.getStatusLine().getStatusCode() == 200) { String content = EntityUtils.toString(response.getEntity(), "utf8"); System.out.println(content.length()); } } catch(IOException e) { e.printStackTrace(); } finally { if (response != null) { try { response.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
|
2.6 请求参数
有时候因为网络,或者目标服务器的原因,请求需要更长的时间才能完成,我们需要自定义相关时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| package cn.itbuild.crawler.test;
import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils;
public class HttpConfigTest { public static void main(String[] args) throws Exception{ CloseableHttpClient httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://yun.itheima.com/search?keys=Java");
RequestConfig config = RequestConfig.custom().setConnectTimeout(1000) .setConnectionRequestTimeout(500) .setSocketTimeout(10 * 1000) .build();
httpGet.setConfig(config);
CloseableHttpResponse response = httpClient.execute(httpGet); if (response.getStatusLine().getStatusCode() == 200) { String content = EntityUtils.toString(response.getEntity(), "utf8"); System.out.println(content.length()); } } }
|
3. Jsoup
我们抓取到页面之后,还需要对页面进行解析。可以使用字符串处理工具解析页面,也可以使用正则表达式,但是这些方法都会带来很大的开发成本,所以我们需要使用一款专门解析html页面的技术—Jsoup。
3.1 Jsoup介绍
jsoup是一款Java 的HTML解析器,可直接解析某个URL地址、HTML文木内容。它提供了一套非常省力的API,可通过DOM,CSS以及类似于jQuery的操作方法来取出和操作数据。
jsoup的主要功能如下:
- 从一个URL,文件或字符串中解析HTML;
- 使用DOM或CSS选择器来查找、取出数据;
- 可操作HTML元素、属性、文本。
添加jsoup相关依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>
<groupId>cn.itbuild</groupId> <artifactId>itbuild-crawler-first</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.11.3</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.4</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.4</version> </dependency> </dependencies> </project>
|
3.2 解析Url
虽然使用Jsoup可以替代HttpClient直接发起请求解析数据,但是往往不会这样用,因为实际的开发过程中,需要使用到多线程,连接池,代理等等方式,而 jsoup 对这些的支持并不是很好,所以我们一般把jsoup 仅仅作为 Html解析工具使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package cn.itbuild.test;
import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.junit.Test;
import java.net.URL;
public class JsoupTest { @Test public void testUrl() throws Exception { Document doc = Jsoup.parse(new URL("http://www.itcast.cn"), 1000);
String title = doc.getElementsByTag("title").first().text();
System.out.println(title); } }
|
将test.html的路径在JsoupTest中先定义好
:
1
| private static final String TESTHTML = "E:\\file\\gitee\\itbuild-crawler-first\\src\\main\\java\\cn\\itbuild\\crawler\\test\\test.html";
|
test.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>传智教育官网-好口碑IT培训机构,一样的教育,不一样的品质</title> <style> .class_a{
} .class_b{
} .a_name{
} </style> </head> <body> <div class="city"> <h3 id="city_bj">北京中心</h3>
<div class="city_in"> <div class="city_con" style="display:none;"> <ul> <li id="test" class="class_a class_b"> <a href="http://www.itcast.cn" target="_blank" > <span class="a_name">北京</span> </a> </li> <li> <a href="http://sh.itcast.cn" target="_blank"> <span class="a_name">上海</span> </a> </li> <li> <a href="http://gz.itcast.cn" target="_blank"> <span abc="123" class="a_name">广东</span> </a> </li> </ul> <ul> <li>天津</li> </ul> </div> </div> </div> </body> </html>
|
3.3 解析字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Test public void testString() throws Exception { String content = FileUtils.readFileToString(new File(TESTHTML), "utf8");
Document doc = Jsoup.parse(content);
String title = doc.getElementsByTag("title").first().text();
System.out.println(title);
}
|
3.4 解析文件
1 2 3 4 5 6 7 8 9
| @Test public void testFile() throws Exception { Document doc = Jsoup.parse(new File(TESTHTML), "utf8");
String title = doc.getElementsByTag("title").first().text();
System.out.println(title); }
|
3.5 使用DOM方式遍历文档
元素获取:
- 根据id查询元素 getElementByld
- 根据标签获取元素getElementsByTag
- 根据class获取元素getElementsByClass
- 根据属性获取元素getElementsByAttribute
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| @Test public void testDom() throws Exception { Document doc = Jsoup.parse(new File(TESTHTML), "utf8");
Element element = doc.getElementById("city_bj");
Elements spans = doc.getElementsByTag("span");
Element clazz = doc.getElementsByClass("class_a class_b").first();
Element abc = doc.getElementsByAttribute("abc").first();
Element href = doc.getElementsByAttributeValue("href", "http://sh.itcast.cn").first();
System.out.println("获取到的元素内容是: " + element.text()); for (Element span : spans) { System.out.println(span.text()); }
System.out.println(clazz.text()); System.out.println("abc.text() = " + abc.text()); System.out.println("href.text() = " + href.text()); }
|
元素中获取数据:
- 从元素中获取id
- 从元素中获取className
- 从元素中获取属性的值 attr
- 从元素中获取所有属性attributes
- 从元紊中获取文本内容 text
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @Test public void testDate() throws IOException { Document doc = Jsoup.parse(new File(TESTHTML), "utf8");
Element element = doc.getElementById("test");
String str = "";
str = element.id(); str = element.className(); Set<String> sets = element.classNames(); for (String set : sets) { System.out.println("set = " + set); }
str = element.attr("id"); str = element.attr("class");
Attributes attributes = element.attributes(); System.out.println("attributes = " + attributes.toString());
str = element.text();
System.out.println("获取到的数据:" + str); }
|
3.6 Selector选择器概述
tagname
:通过标签查找元素,比如: span
#id
:通过ID查找元素,比如:#city_bj
.class
:通过 class名称查找元素,比如:.class_a
[attribute]
:利用属性查找元素,比如:[abc]
[attr=value]
:利用属性值来查找元素,比如:[class=s_name]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @Test public void testSelector() throws Exception{ Document doc = Jsoup.parse(new File(TESTHTML), "utf8");
Elements spans = doc.select("span"); for (Element span : spans) { System.out.println(span.text()); }
Element element = doc.select("#city_bj").first();
element = doc.select(".class_a").first();
element = doc.select("[abc]").first();
element = doc.select("[class=a_name]").first();
System.out.println("获取到的结果为:" + element.text()); }
|
3.7 Selector选择器组合使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Test public void testSelector2() throws Exception { Document doc = Jsoup.parse(new File(TESTHTML), "utf8");
Element element = doc.select("span[abc].a_name").first();
Elements elements = doc.select(".city_con > ul > *");
System.out.println("获取到的内容是: " + element.text());
for (Element ele : elements) { System.out.println("遍历的结果: " + ele.text()); } }
|
☆