前言

我又回来了!经过了一长串的比赛和项目以后,成功的锻炼出了Vue和uni-app,感觉自己走上了全栈=全干的不归路,从现在开始我就要做后端啦,所以以后的文章更偏后端和安全一些。

爬虫

这次主要是刚回来,所以很多知识点都有点忘了,加上不少做java的同学说不太会爬虫,所以这里做一个demo,讲一下基本原理。

什么是爬虫呢? 其实就是一种可以自动化爬取网络资源的脚本。用python写是最方便的,java近年来也开源了一些框架,总的还说还行,不算特别麻烦。

爬虫原理

爬虫的原理主要是以下步骤:获取html页面数据,解析标签,拿到标签中的数据。
是的,就是这么简单.

准备

需要以下的包(我是通过maven管理的) 直接写入数据库,如果想要写入文件,还需要commons-io和commons-lang3

  1. httpclient
  2. slf4j
  3. jsoup
  4. mysql-connector-java

分析

先来看看大佬的博客(鬼鬼 或许这就是大佬吧!) 可以发现所有文章的链接都是在/page/页码数 所以这就是我们要爬取的链接 通过循环爬取每一页的数据

cra
cra

cra

但是…..问题在于首页是一个列表项,只包含了一部分的文章数据,我们要做的是拿到所有的title和具体的文章内容,所以只能先把标题和详情链接存起来,然后再爬取详情数据.

HttpClient

1
apache开源的一个http请求库,可以进行各种http请求,以及设置参数,这个主要用来发起请求,另外有个需要注意的点是,我是直接看的下面一共有6页,但是在URLBuilder中是带有异常的,当页码出错(也就是页面不存在的时候,会反馈异常)后可以捕获异常来处理结束位置。

Jsoup

一个适配java的html文档解析工具,可以把html转换成element类,同时提供了获取元素属性和属性值,元素子节点,元素值等功能。

开始动手

首先是拉起http的get请求,由于本身不需要参数,就不带参数了,如果需要参数的爬取,可以用url.setParameter(key,val)方法。多个参数之间无需带& 直接多调用几次方法就行

然后直接开始爬取,我用List把数据装起来,因为时间比较少我就直接分成标题,链接,详情三个部分了,下面的话主要是写的不太好,应该用递归去完成爬取数据的过程,另外没做多线程,有兴趣的可以自己参考一下httpclient的连接池和多线程的应用。

最后是把数据存数据库里,刚回归java很多知识点都有点记不起来了,mybatis的语法基本忘得差不多了,所以还是用JDBC顶一下吧。特别提醒的是mysql5.8是大改版本,驱动名称已经变了(我的就是5.8) 同时时区也要设置成UTC 还有要阻止SSL访问

然后就是写sql语句的部分,这个我相信应该都能看得懂了。

结果

爬到的数据都存数据库里啦 看看大佬的博客就是不一样!大佬牛逼(破音)

好了,莫了,所有的代码都在这里了:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package com.sammie.top.test;

import org.apache.commons.io.FileUtils;
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;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class CrawlerTest1 {
public static void main(String[] args) throws Exception {
String baseUrl = "http://zgao.top/page/";

for (int i = 1; i < 7; i++) {
doCrawler(baseUrl+i);
}
}

public static void doCrawler(String CrawlerUrl) throws URISyntaxException {
//获取httpClient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//设置访问地址

URIBuilder url = new URIBuilder(CrawlerUrl);
//如需添加参数 可以使用url.setParameter(key,val)方法
HttpGet httpGet = new HttpGet(url.build());
//发起请求
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpGet);
// 解析
if (response.getStatusLine().getStatusCode()==200){
List <String> title = new ArrayList<String>();
List <String> link = new ArrayList<String>();
List <String> moreContent = new ArrayList<String>();
String content = EntityUtils.toString(response.getEntity(), "utf8");
Document doc = Jsoup.parse(content);
Elements titleElements = doc.select(".entry-title");
Elements moreElements = doc.select(".more-link");
for (Element element : titleElements) {
title.add(element.text());
}
// for (String s : title) {
// System.out.println(s);
// }

for (Element moreElement : moreElements) {
link.add(moreElement.attr("href"));
}
for (String s : link) {
//利用url再爬取详情内容,这里我写次了,最好用递归来做.另外可以直接用jsoup的方法 但是最好还是用httpclient
//因为一般我们可以多线程或者要均衡来爬
CloseableHttpClient client = HttpClients.createDefault();
HttpGet httpGet1 = new HttpGet(s);
CloseableHttpResponse res = null;
res = client.execute(httpGet1);
if (res.getStatusLine().getStatusCode()==200){
String result = EntityUtils.toString(res.getEntity(), "utf8");
Document parse = Jsoup.parse(result);
//获取详情数据
String text = parse.select(".entry-content").first().text();
moreContent.add(text);
client.close();
res.close();
}else{
System.out.println("出问题了,应该是服务器问题");
}
}
for (String s : moreContent) {
insert(s);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭资源
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

private static Connection getConn() {
String driver = "com.mysql.cj.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC";
String username = "root";
String password = "";
Connection conn = null;
try {
Class.forName(driver); //classLoader,加载对应驱动
try {
conn = (Connection) DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return conn;
}

private static int insert(String content) {
Connection conn = getConn();
int i = 0;
String sql = "insert into contentList (content) values(?)";
PreparedStatement pstmt;
try {
pstmt = (PreparedStatement) conn.prepareStatement(sql);
pstmt.setString(1, content);
i = pstmt.executeUpdate();
pstmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
return i;
}
}

另外如果想要去下载某个资源 这里我提供一种写法:

1
2
3
4
5
6
7
8
9
10
11
12
public static void downloadHttpUrl(String url, String dir, String fileName) {
try {
URL httpurl = new URL(url);
File dirfile = new File(dir);
if (!dirfile.exists()) {
dirfile.mkdirs();
}
FileUtils.copyURLToFile(httpurl, new File(dir+fileName));
} catch (Exception e) {
e.printStackTrace();
}
}

url是网络地址,dir是要下载到的文件夹,fileName是自定义文件名