一、网络爬虫概述
网络爬虫:是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。
应用场景:
1、实现搜索引擎
当我们经常查询某类数据,可能会在几个或多个不同的渠道来回检索,此时我们便可以通过抓取定向数据进行处理,存储到个人存储库中,比如用到ES,再从ES中进行全文检索就更方便地获取精准数据了。
2、数据分析
当我们需要在某一方面做决策时,可能会需要到大数据做支撑,毕竟在互联网时代,以技术为基础,以数据为驱动,比如当下火爆的自媒体运营,需要分析文章的数据,自己的文章数据分析可以看到,但我还想看竞争对手的或者整个行业的综合数据怎么办?如果手工一遍篇统计相加时非常慢的,此时便可以通过爬虫爬取指定的内容数据,再进行加工处理就可以了。
3、内容生产
当一个新网站刚刚建立,没有内容怎么办,便可以通过爬虫,进行抓取网络资源进行数据采集、加工处理、存储。
二、httpclient
1、无参get请求
通过爬虫获取百度首页
引入依赖包:
<dependencies>
<!--HTTP通信库,类似浏览器使用-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
日志配置:
log4j.rootLogger=DEBUG,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
实现代码:
public class Crawlerdemo1 {
public static void main(String[] args) {
//创建一个httpClient对象(打开浏览器)
CloseableHttpClient httpClient = HttpClients.createDefault();
//发起get请求,创建HttpGet对象(输入网址)
HttpGet httpGet = new HttpGet("http://www.baidu.com");
CloseableHttpResponse response=null;
try {
//使用httpclient发起请求,返回响应(打开网址)
response = httpClient.execute(httpGet);
//解析响应获取数据
//获取响应里的状态行里的状态码,请求成功就获取响应体
if(response.getStatusLine().getStatusCode()==200){
//获取响应体对象
HttpEntity httpEntity = response.getEntity();
//解析响应体,获取静态数据
String content = EntityUtils.toString(httpEntity,"utf8");
System.out.println(content.length());
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(response!=null){
response.close();
}
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
结果:
2287
2、带参get请求
通过爬虫获取百度关键词搜索页
public class Crawlerdemo2Parm {
public static void main(String[] args) {
//创建一个httpClient对象(打开浏览器)
CloseableHttpClient httpClient = HttpClients.createDefault();
//请求地址:http://www.baidu.com/s?wd=seo
HttpGet httpGet = null;
try {
//创建URIBuilder
URIBuilder uriBuilder = new URIBuilder("http://www.baidu.com/s");
//设置参数
uriBuilder.setParameter("wd","seo");
//发起get请求,创建HttpGet对象(输入网址)
httpGet = new HttpGet(uriBuilder.build());
} catch (URISyntaxException e) {
e.printStackTrace();
}
CloseableHttpResponse response=null;
try {
//使用httpclient发起请求,返回响应(打开网址)
response = httpClient.execute(httpGet);
//解析响应获取数据
//获取响应里的状态行里的状态码,请求成功就获取响应体
if(response.getStatusLine().getStatusCode()==200){
//获取响应体对象
HttpEntity httpEntity = response.getEntity();
//解析响应体,获取静态数据
String content = EntityUtils.toString(httpEntity,"utf8");
System.out.println(content.length());
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(response!=null){
response.close();
}
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
结果:
325779
3、无参post请求
post请求只需将不带参数的get修改为post请求方式即可
HttpPost httpPost = new HttpPost("http://www.baidu.com");
结果:
2287
4、带参post请求
没找post提交后返回页面的网页
public class CrawlerPostParmdemo2 {
public static void main(String[] args) {
//创建一个httpClient对象(打开浏览器)
CloseableHttpClient httpClient = HttpClients.createDefault();
//请求地址:https://www.baidu.com/s?wd=seo
//创建HttpPost对象,设置uri访问地址
HttpPost httpPost = new HttpPost("https://www.baidu.com/s");
//通过集合封装参数
List<NameValuePair> parms = new ArrayList<NameValuePair>();
parms.add(new BasicNameValuePair("wd","seo"));
//创建表单Entity对象
UrlEncodedFormEntity formEntity = null;
try {
formEntity = new UrlEncodedFormEntity(parms, "utf8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
//将表单Entity对象设置进post请求中
httpPost.setEntity(formEntity);
CloseableHttpResponse response=null;
try {
//使用httpclient发起请求,返回响应(打开网址)
response = httpClient.execute(httpPost);
//解析响应获取数据
//获取响应里的状态行里的状态码,请求成功就获取响应体
if(response.getStatusLine().getStatusCode()==200){
//获取响应体对象
HttpEntity httpEntity = response.getEntity();
//解析响应体,获取静态数据
String content = EntityUtils.toString(httpEntity,"utf8");
System.out.println(content.length());
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(response!=null){
response.close();
}
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
5、连接池
每次请求都要创建HttpClient,为了避免频繁的创建连接和关闭连接,可以使用连接池。
public class HttpClientPool {
public static void main(String[] args) {
//创建连接池管理器
PoolingHttpClientConnectionManager poolCM = new PoolingHttpClientConnectionManager();
//设置最大连接数
poolCM.setMaxTotal(100);
//设置一个主机下最大连接数
poolCM.setDefaultMaxPerRoute(10);
//使用连接池创建连接
doGet(poolCM);
doGet(poolCM);
}
private static void doGet(PoolingHttpClientConnectionManager poolCM) {
//创建httpClient连接对象
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(poolCM).build();
//创建get请求
HttpGet httpGet = new HttpGet("http://www.baidu.com");
CloseableHttpResponse response=null;
try {
response = httpClient.execute(httpGet);
if(response.getStatusLine().getStatusCode()==200){
HttpEntity httpEntity = response.getEntity();
String content = EntityUtils.toString(httpEntity, "utf8");
System.out.println(content.length());
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(response!=null){
response.close();
}
//此处不用关闭httpClient,由连接池管理
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
6、请求参数
在进行请求时,可能会因为网络等原因无法连接,此时需要设置相关的请求最大时间参数。
public class CrawlerConfigdemo {
public static void main(String[] args) {
//创建一个httpClient对象(打开浏览器)
CloseableHttpClient httpClient = HttpClients.createDefault();
//发起get请求,创建HttpGet对象(输入网址)
HttpGet httpGet = new HttpGet("http://www.baidu.com");
//设置请求参数
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(1000) //设置创建连接的最长时间,单位是毫秒
.setConnectionRequestTimeout(500) //设置连接请求的最长时间
.setSocketTimeout(10 * 1000) //设置数据传输的最长时间
.build();
//给请求设置请求参数
httpGet.setConfig(requestConfig);
CloseableHttpResponse response=null;
try {
//使用httpclient发起请求,返回响应(打开网址)
response = httpClient.execute(httpGet);
//解析响应获取数据
//获取响应里的状态行里的状态码,请求成功就获取响应体
if(response.getStatusLine().getStatusCode()==200){
//获取响应体对象
HttpEntity httpEntity = response.getEntity();
//解析响应体,获取静态数据
String content = EntityUtils.toString(httpEntity,"utf8");
System.out.println(content.length());
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(response!=null){
response.close();
}
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
三、Jsoup
1、jsoup介绍
当我们通过HttpClient抓取到数据后,需要对数据进行处理,可以使用字符串处理工具解析页面,也可以使用正则表达式解析页面,但这些方法会带来很大的开发成本,可以使用专门解析html的技术Jsoup解析。
Jsoup介绍
jsoup是一款Java的HTML解析器,可直接解析某个URL地址、HTML文本内容。它提供了一套非常省力的API, 可通过DOM, CSS以及类似于jQuery的操作方法来取出和操作数据。。
Jsoup参考文档:https://www.open-open.com/jsoup/
jsoup的主要功能如下:。
1.从一个URL,文件或字符串中解析HTML;。
2.使用DOM或CSS选择器来查找、取出数据;
3.可操作HTML元素、属性、文本;。
引入依赖
<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.7</version>
</dependency>
2、jsoup3种解析方式
1)、url解析
@Test
public void testUrl() throws Exception {
//解析url地址,第一个参数是url,第二个参数是超时时间
Document doc = Jsoup.parse(new URL("http://www.baidu.com"), 10000);
//使用标签选择器选择title标签中的内容
String title = doc.getElementsByTag("title").first().text();
System.out.println(title);
}
运行结果:
百度一下,你就知道
PS:虽然使用Jsoup可以替代HttpClient直接发起请求解析数据,但是往往不会这样用,因为实际的开发过程中,需要使用到多线程,连接池,代理等等方式,而jsoup对这些的支持并不是很好,所以我们一-般把jsoup仅仅作为Html解析工具使用。
2)、字符串解析
@Test
public void testString() throws IOException {
//使用工具类获取文件字符串
String string = FileUtils.readFileToString(new File("C:\\Users\\ztnetbook\\Desktop\\file.html"), "utf8");
//解析字符串
Document doc = Jsoup.parse(string);
//使用标签选择器选择title标签中的内容
String title = doc.getElementsByTag("title").first().text();
System.out.println(title);
}
运行结果:
百度一下,你就知道
3)、文件解析
@Test
public void testFile() throws IOException {
//解析文件
Document doc = Jsoup.parse(new File("C:\\Users\\ztnetbook\\Desktop\\file.html"), "utf8");
//使用标签选择器选择title标签中的内容
String title = doc.getElementsByTag("title").first().text();
System.out.println(title);
}
运行结果:
百度一下,你就知道
3、使用dom方式遍历文档
元素获取。
1)、根据id查询元素getElementById
//标签是唯一的
Element id = doc.getElementById("Id");
2)、根据标签获取元素getElementsByTag
Element span = doc.getElementsByTag("span").first();
3)、根据class获取元素getElementsByClass
//class可以全部获取,也可以单独获取一个
Element byClass = doc.getElementsByClass("class_a class_b").first();
4)、根据属性获取元素getElementsByAttribute
Element abc = doc.getElementsByAttribute("abc").first();
Elements abc = doc.getElementsByAttributeValue("abc", "123");
元素中获取数据。
Element element = doc.getElementById("Id");
1).从元素中获取 id
String id = element.id();
2).从元素中获取classNames
String className = element.className();
Set<String> set = element.classNames();
3).从元素中获取属性的值attr
String attr = element.attr("abc");
4).从元素中获取所有属性attributes
Attributes attributes = element.attributes();
5).从元素中获取文本内容text
String text = element.text();
4、Selector选择器
1)、tagname:通过标签查找元素,比如: span
Elements elements = doc.select("span");
2)、#id:通过ID查找元素,比如: #id
Element element = doc.select("#id").first();
3)、.class:通过class名称查找元素,比如: .class_ ao
Elements elements = doc.select(".class_a");
4)、[attribute]:利用属性查找元素,比如: [abc]。
Element element = doc.select("[abc]").first();
5)、[attr=value]:利用属性值来查找元素,比如: [class=s_ name]
Elements elements = doc.select("[class=s_ name]");
5、Selector选择器组合
el#id: 元素+ID,比如:h3#id
el.class: 元素+class,比如:li.class_a
el[attr]: 元素+属性名,比如:span[abc]
任意组合: 比如:span[abc].s_ name
ancestor child: 查找某个元素下子元素,比如: .city_ con li查找"city_ con"下的所有li
parent > child: 查找某个父元素下的直接子元素,比如:.abc>ul>li 查找abc第一级(直接子元素)的ul, 再找所有ul下的第一级li。
parent> *: 查找某个父元素下所有直接子元素。