1. 爬虫的基本概念
1.1 什么是爬虫:
网络爬虫是一个程序, 采用一种特定的解析结构来获取互联网中数据的, 爬虫一般分为三大模块: 获取数据, 解析数据, 保存数据
1.2 爬虫的价值:
网络爬虫的价值其实就是数据的价值, 一切皆为数据, 例如: 用户的信息,分析用户的维度, 商品的信息,竞价的网站
1.3 爬虫的分类:
1.3.1 通用的爬虫:
指的就是爬虫互联网中所有的信息, 例如: 百度 谷歌
1.3.2 垂直爬虫:
指的爬取某个行业或者某个网站或者某个分类下的信息,这样的爬虫程序, 垂直爬虫 例如: 慢慢网, 笔趣阁
在开发过程中, 大部分开发的都是垂直爬虫,
1.4 爬虫的基本原理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s2DZvhH4-1605084008952)(…\图片\01-爬虫的基本原理.png)]
爬虫基本原理:
1. 确定爬虫的url
2. 发起http请求, 获取数据
1. 原始的jdk的方式: get post
2. httpClient get post
3. 解析获取到数据
1. jsoup
4. 保存数据
1. JDBC
2. DButils
3. JDBCTemplate(Spring)
4. mybatis
2. 爬虫的三大模块:
2.1第一大模块: 获取数据
获取数据的过程, 其实就是发送一个http请求, 获取其响应的内容
2.1.1回顾: http
- get请求和post的请求的区别:
-
- 请求方式不同
-
- get没有请求体, post有请求体
-
- get请求数据拼接在url后面 ?username=zs&password=123, post将请求参数放置在请求体中
-
- 请求头:
- user-agent: 指定当前使用的浏览器的版本
- cookie: 携带当前网站的cookie信息
- 响应头:
- Location: 一般和302结合使用, 进行重定向
- set-cookie: 服务器向浏览器写入cookie的信息
- 常见的状态码:
- 200: 请求成功
- 302: 重定向
- 304: 缓存浏览器的内容
- 404: 资源不存在
- 500: 服务端错误
2.1.2使用jdk的方式发起http请求:
- 发送get请求:
//演示 jdk 的get请求方式
public class JDKget {
public static void main(String[] args) throws Exception {
//1. 创建URL对象
URL url = new URL("http://www.itcast.cn");
//2. 打开一个连接
HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection();
//3. 设置请求方式
urlConnection.setRequestMethod("GET");//此处必须使用大写, 默认是get请求
//4. 获取输入流
InputStream in = urlConnection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
//5. 获取数据
String len = null;
while((len = bufferedReader.readLine())!=null){
System.out.println(len);
}
//6. 关流
bufferedReader.close();
in.close();
}
}
- 发生post请求
//演示jdk发送post请求
public class JDKpost {
public static void main(String[] args) throws Exception {
//1. 创建url对象
URL url = new URL("http://www.itcast.cn");
//2. 建立连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//3. 设置请求方式
connection.setRequestMethod("POST");
//如果要有使用jdk的方式发送post请求, 需要设置doOutput为true
connection.setDoOutput(true);
//4. 设置参数
OutputStream out = connection.getOutputStream();
out.write("username=zs&password=123".getBytes());
//5. 获取响应体, 获取输入流
InputStream in = connection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
String len = null;
while((len = bufferedReader.readLine())!=null){
System.out.println(len);
}
//6. 关流
bufferedReader.close();
in.close();
}
}
总结:
实现的步骤:
-
创建url对象, 指定url路径
-
打开一个连接, 获取连接对象(HttpURLConnection)
-
设置请求方式
-
如果是post, 需要设置两个参数:
设置输出数据, 和 doOutPut设置为true
-
获取输入流(获取响应体)
-
读取输入流中的数据
-
关流
-
2.1.3 使用 httpClient完成http请求
httpclient是一个专为用来做http请求的工具, 是Apache开发
使用步骤:
第一步: 导包
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.4</version>
</dependency>
- get请求
//演示 httpclient的get请求
public class HTTPClientGet {
public static void main(String[] args) throws IOException {
//1. 创建httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//2. 创建get请求对象
HttpGet httpGet = new HttpGet("http://www.itcast.cn");
//3. 发送一个请求
CloseableHttpResponse response = httpClient.execute(httpGet);
//4. 获取状态码
int statusCode = response.getStatusLine().getStatusCode();
System.out.println(statusCode);
if(statusCode==200){
//获取响应体(数据)
String html = EntityUtils.toString(response.getEntity(), Charset.forName("utf-8"));
System.out.println(html);
}
}
}
- post请求
//演示 httpclient 发送post请求
public class HTTPClientPost {
public static void main(String[] args) throws Exception {
//1. 创建 httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//2. 创建post请求对象
HttpPost httpPost = new HttpPost("http://www.itcast.cn");
//3. 设置请求参数
List<BasicNameValuePair> list = new ArrayList<BasicNameValuePair>();
list.add(new BasicNameValuePair("usernam","zs"));
list.add(new BasicNameValuePair("password","123"));
HttpEntity entity = new UrlEncodedFormEntity(list);
httpPost.setEntity(entity);
//4. 发送数据
CloseableHttpResponse response = httpClient.execute(httpPost);
//5. 获取数据
Header[] headers = response.getHeaders("Date");
System.out.println(headers[0].getValue());
System.out.println(EntityUtils.toString(response.getEntity(),"utf-8"));
}
}
总结:
get请求:
-
创建httpclient对象:HttpClients.createDefault();
-
创建请求对象(httpget)
-
发送请求: httpclient.execute(httpGet);
-
获取数据:
- 1 获取状态码
1.2 获取响应头
1.3 获取响应体
-
post请求:
1.创建httpclient对象: HttpClients.createDefault();
2.创建请求对象(httpPost)
3.设置参数: httpPost.setEntity(entity)
List<BasicNameValuePair> list = new ArrayList<BasicNameValuePair>();
list.add(new BasicNameValuePair("usernam","zs"));
list.add(new BasicNameValuePair("password","123"));
HttpEntity entity = new UrlEncodedFormEntity(list);
4.发送请求:httpclient.execute(httpPost);
5.获取数据:
1.1获取状态码
1.2获取响应头
1.3 获取响应体
2.2 第二大模块: 解析数据
解析数据, 其实就是解析HTML文档,js中dom操作就是在解析HTML文档
2.2.1 Document 对象集合
集合 | 描述 |
---|---|
[all] | 提供对文档中所有 HTML 元素的访问。 |
[anchors] | 返回对文档中所有 Anchor 对象的引用。 |
applets | 返回对文档中所有 Applet 对象的引用。 |
[forms] | 返回对文档中所有 Form 对象引用。 |
[images] | 返回对文档中所有 Image 对象引用。 |
[links] | 返回对文档中所有 Area 和 Link 对象引用。 |
2.2.2 Document 对象属性
属性 | 描述 |
---|---|
body | 提供对 元素的直接访问。 对于定义了框架集的文档,该属性引用最外层的 。 |
cookie | 设置或返回与当前文档有关的所有 cookie。 |
domain | 返回当前文档的域名。 |
lastModified | 返回文档被最后修改的日期和时间。 |
referrer | 返回载入当前文档的文档的 URL。 |
title | 返回当前文档的标题。 |
URL | 返回当前文档的 URL。 |
2.2.3 Document 对象方法
方法 | 描述 |
---|---|
close() | 关闭用 document.open() 方法打开的输出流,并显示选定的数据。 |
getElementById() | 返回对拥有指定 id 的第一个对象的引用。 |
getElementsByName() | 返回带有指定名称的对象集合。 |
getElementsByTagName() | 返回带有指定标签名的对象集合。 |
open() | 打开一个流,以收集来自任何 document.write() 或 document.writeln() 方法的输出。 |
write() | 向文档写 HTML 表达式 或 JavaScript 代码。 |
2.2.4 jsoup概念:
jsoup是一个专门为HTML解析而生的工具,提供了丰富解析方案, 一种使用类似于js中原生dom操作的方案 , 一种是类似于jQuery(css选择器)中选择器的方案
2.2.5 jsoup的入门:
- 第一步: 导包
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.3</version>
</dependency>
- 第二步: 获取document对象
public class JsoupToDocument {
public static void main(String[] args) throws IOException {
//1. 获取document对象:通过url获取document
//Document document = Jsoup.connect("http://www.itcast.cn").get();
//1.1. 获取网页的标题
//String title = document.title();
//System.out.println(title);
//2. 获取document: 通过HTML文档获取
// Document document = Jsoup.parse("<!DOCTYPE html>\n" +
"<html lang=\"en\">\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">\n" +
" <title>黑马程序员</title>\n" +
"</head>\n" +
"<body>\n" +
"\n" +
"</body>\n" +
"</html>");
// String title = document.title();
//System.out.println(title);
//3. 通过本地html文件获取document对象
// Document document = Jsoup.parse(new File(""), "utf-8");
//4. 通过html片段获取
// Document document = Jsoup.parseBodyFragment("<a href='http://www.itcast.cn'>传智博客</a>");
}
}
总结: 四种
1.通过url路径的方式获取
2.通过HTML文档的形式获取(重点)
3.通过本地HTML文件的形式获取
4.通过HTML片段形式获取
-
第三步: 解析数据(如何解析document)
选择器 例子 例子描述 CSS .class .intro 选择 class=“intro” 的所有元素。 1 #id #firstname 选择 id=“firstname” 的所有元素。 1 * * 选择所有元素。 2 element p 选择所有 元素。
1 element,element div,p 选择所有 元素和所有元素。
1 element element div p 选择 元素内部的所有元素。
1 element>element div>p 选择父元素为 元素的所有元素。
2 element+element div+p 选择紧接在 元素之后的所有元素。
2 [attribute] [target] 选择带有 target 属性所有元素。 2 [attribute=value] [target=_blank] 选择 target="_blank" 的所有元素。 2 [attribute~=value] [title~=flower] 选择 title 属性包含单词 “flower” 的所有元素。 2 [attribute|=value] [lang|=en] 选择 lang 属性值以 “en” 开头的所有元素。 2 :link a:link 选择所有未被访问的链接。 1 :visited a:visited 选择所有已被访问的链接。 1 :active a:active 选择活动链接。 1 :hover a:hover 选择鼠标指针位于其上的链接。 1 :focus input:focus 选择获得焦点的 input 元素。 2 :first-letter p:first-letter 选择每个 元素的首字母。
1 :first-line p:first-line 选择每个 元素的首行。
1 :first-child p:first-child 选择属于父元素的第一个子元素的每个 元素。
2 :before p:before 在每个 元素的内容之前插入内容。
2 :after p:after 在每个 元素的内容之后插入内容。
2 :lang(language) p:lang(it) 选择带有以 “it” 开头的 lang 属性值的每个 元素。
2 element1~element2 p~ul 选择前面有 元素的每个
- 元素。
3 [attribute^=value] a[src^=“https”] 选择其 src 属性值以 “https” 开头的每个 元素。 3 [attribute$=value] a[src$=".pdf"] 选择其 src 属性以 “.pdf” 结尾的所有 元素。 3 [attribute*=value] a[src*=“abc”] 选择其 src 属性中包含 “abc” 子串的每个 元素。 3 :first-of-type p:first-of-type 选择属于其父元素的首个 元素的每个
元素。
3 :last-of-type p:last-of-type 选择属于其父元素的最后 元素的每个
元素。
3 :only-of-type p:only-of-type 选择属于其父元素唯一的 元素的每个
元素。
3 :only-child p:only-child 选择属于其父元素的唯一子元素的每个 元素。
3 :nth-child(n) p:nth-child(2) 选择属于其父元素的第二个子元素的每个 元素。
3 :nth-last-child(n) p:nth-last-child(2) 同上,从最后一个子元素开始计数。 3 :nth-of-type(n) p:nth-of-type(2) 选择属于其父元素第二个 元素的每个
元素。
3 :nth-last-of-type(n) p:nth-last-of-type(2) 同上,但是从最后一个子元素开始计数。 3 :last-child p:last-child 选择属于其父元素最后一个子元素每个 元素。
3 :root :root 选择文档的根元素。 3 :empty p:empty 选择没有子元素的每个 元素(包括文本节点)。
3 :target #news:target 选择当前活动的 #news 元素。 3 :enabled input:enabled 选择每个启用的 元素。 3 :disabled input:disabled 选择每个禁用的 元素 3 :checked input:checked 选择每个被选中的 元素。 3 :not(selector) :not§ 选择非 元素的每个元素。
3 ::selection ::selection 选择被用户选取的元素部分。 3 - 原生的dom解析(了解)
public class JsoupToParse { @Test public void jsoupToDomParse() throws IOException { //1. 获取document Document document = Jsoup.connect("http://www.itcast.cn/subject/cloudzly/index.shtml").get(); //1.1 获取文档的标题 String title = document.title(); System.out.println(title); //1.2 Elements elements = document.getElementsByClass("head"); Element element = elements.get(0); elements = element.getElementsByClass("inner"); //System.out.println(elements.size()); element = elements.get(0); Elements lis = element.getElementsByTag("li"); /*for (Element li : lis) { System.out.println(li.); }*/ Elements a = lis.get(0).getElementsByTag("a"); String text = a.text(); System.out.println(text); } }
- 选择器的方案
//使用jsoup的选择器来解析网页的数据 @Test public void jsoupToSelectParse() throws IOException { //1.获取document对象 Document document = Jsoup.connect("http://www.itcast.cn/subject/cloudzly/index.shtml").get(); //2. 获取标题 Elements title = document.select("title"); System.out.println(title.text()); //3. 获取 云计算大数据培训 内容 //Elements elements = document.select(".head .inner li"); Elements elements = document.select("body > div.wrap > div.head > div > ul > li:nth-child(1) > a"); /*Element element = elements.get(0); Elements a = element.select("a");*/ System.out.println(elements.text()); } }
//获取网易新闻的内容 @Test public void jsoupTo163Parse() throws IOException { Document document = Jsoup.connect("http://news.163.com/18/0727/08/DNN5HCQU0001875N.html").get(); //1. 解析新闻的标题 Elements elements = document.select("#epContentLeft"); Elements h1 = elements.select("h1"); System.out.println(h1.text()); //2. 获取新闻的来源 Elements laiyuan = document.select("#ne_article_source"); System.out.println(laiyuan.text()); //3. 获取新闻的正文 Elements ps = document.select("#endText p"); for (Element p : ps) { System.out.println(p.text()); } } }
2.3 第三大模块: 保存数据
目前采用的保存到mysql数据库中, 以后hadoop, hbase
四种方案:
jdbc:
dbutils:
jdbcTemplate
mybatis
3. 案例一:爬起起点中文网的小说
//需求: 爬取起点中文网中任意一个榜单的小说
public class QiDianSprider {
public static void main(String[] args) throws IOException {
//1. 确定爬取的url
String url = "https://www.qidian.com/";
//2. 发起请求获取数据: httpClient
//2.1 创建httpclient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
//2.2 创建请求方式: get
HttpGet httpGet = new HttpGet(url);
//2.3 发起请求获得响应
CloseableHttpResponse response = httpClient.execute(httpGet);
String html = EntityUtils.toString(response.getEntity(), "utf-8");
//3.解析数据: jsoup
Document document = Jsoup.parse(html);
Elements elements = document.select("div[class=rank-list sort-list]");
Elements lis = elements.select(".book-list ul li");
//System.out.println(lis.size());
Elements as = lis.select("a[href^=//book.qidian.com]:not([class=link])");
for (Element a : as) {
String href = a.attr("href");
// http://book.qidian.com/info/1012284323
//System.out.println(href);
//拼接url
href = "https:" + href;
//System.out.println(href);
//重新发起请求, 获取每一个小说页面
httpClient = HttpClients.createDefault();
httpGet = new HttpGet(href);
response = httpClient.execute(httpGet);
html = EntityUtils.toString(response.getEntity(), "utf-8");
document = Jsoup.parse(html);
//解析小说详情页
elements = document.select("#readBtn");
href = elements.attr("href");
//拼接小说内容的url
href = "https:" + href;
System.out.println(href);
while (true) {
//重新发起请求, 获取每一个小说页面
httpClient = HttpClients.createDefault();
httpGet = new HttpGet(href);
response = httpClient.execute(httpGet);
html = EntityUtils.toString(response.getEntity(), "utf-8");
//获取到小说内容页数据
document = Jsoup.parse(html);
//获取小说的名称
elements = document.select(".book-cover-wrap h1");
System.out.println("小说名称:" + elements.text());
//获取章节名称
elements = document.select(".j_chapterName");
System.out.println("章节名称" + elements.text());
//获取小说的内容
elements = document.select("div[class=read-content j_readContent] p");
for (Element element : elements) {
System.out.println(element.text());
}
//获取下一章节的url
elements = document.select("#j_chapterNext");
href = elements.attr("href");
if(href==null||href==""||href==" "){
System.out.println("跳出本小说内容");
break;
}
href = "https:" + href;
}
}
}
}
4. 案例二: 模拟登陆
//需求: 模拟登陆, 将登陆后的用户的数据获取到
public class LoginSpider {
public static void main(String[] args) throws Exception {
//1.确定url
String url = "http://www.svn.club/user/login";
//2. 发起请求, 获取数据
CloseableHttpClient httpClient = HttpClients.createDefault();
//3. 创建请求方式: post
HttpPost httpPost =new HttpPost(url);
//4. 设置参数
List<BasicNameValuePair> list = new ArrayList<BasicNameValuePair>();
list.add(new BasicNameValuePair("uid","itcast"));
list.add(new BasicNameValuePair("pwd","www.itcast.cn"));
list.add(new BasicNameValuePair("x","97"));
list.add(new BasicNameValuePair("y","29"));
HttpEntity entity = new UrlEncodedFormEntity(list);
httpPost.setEntity(entity);
//5. 设置浏览器的类型: 模拟浏览器的
httpPost.setHeader("User-Agent","Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36");
//6.发起请求
CloseableHttpResponse response = httpClient.execute(httpPost);
//7. 获取响应的内容
// String html = EntityUtils.toString(response.getEntity(), "utf-8");
//7. 获取状态码
int statusCode = response.getStatusLine().getStatusCode();
if(statusCode==302){
Header[] locations = response.getHeaders("Location");
Header[] cookies = response.getHeaders("Set-Cookie");
String reURL = locations[0].getValue();
String cookie = cookies[0].getValue();
// System.out.println(reURL);
//拼接url
reURL = "http://www.svn.club"+reURL;
//重新发送请求, 获取登陆后的数据
httpClient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(reURL);
httpGet.setHeader("Cookie",cookie);
response = httpClient.execute(httpGet);
String html = EntityUtils.toString(response.getEntity(), "utf-8");
Document document = Jsoup.parse(html);
//System.out.println(document);
Elements elements = document.select(".tb");
Element element = elements.get(0);
Elements trs = element.select("tr");
Element element1 = trs.get(1);
String aText = element1.select("td").get(0).select("a").text();
System.out.println(aText);
}
}
}
作业:
1. 演示 jdk发送get和post请求(www.itcast.cn)
2. 演示httpClient发送post请求
3. 寻找一个网页, 进行解析(解析传智博客的科目列表)
4. 将案例一书写(200%)
5. 将案例二写一遍