Java爬蟲-01快速入門
HttpClient+JSoup詳解 (附各種Demo)
寫在前面:記錄了學習數據挖掘以來的學習歷程,先上之前的一些總結,隨着學習的加深會慢慢更新。
(2)使用dom方法(遍歷一個Document對象)來查找元素 -不推薦
(1)GetResult類,用HttpClient來抓取頁面
1.所需環境
我使用的環境是IDEA+Maven創建的maven project。其他方法亦可。
關於IDEA的下載安裝及使用,可以參考這裏:https://www.cnblogs.com/demingblog/p/5817125.html
關於Maven的下載安裝,官網下載地址:http://maven.apache.org/download.cgi
關於Maven的相關配置請參考:https://blog.csdn.net/cs4380/article/details/79158268
2.HttpClient與Jsoup簡介
1.什麼是HttpClient?
HttpClient 是Apache Jakarta Common 下的子項目,可以用來提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,並且它支持 HTTP 協議最新的版本和建議。
2.爲什麼使用HttpClient?
HTTP 協議可能是現在 Internet 上使用得最多、最重要的協議了,越來越多的 Java 應用程序需要直接通過 HTTP 協議來訪問網絡資源。雖然在 JDK 的 java net包中已經提供了訪問 HTTP 協議的基本功能,但是對於大部分應用程序來說,JDK 庫本身提供的功能還不夠豐富和靈活。
它的主要功能有:
(1) 實現了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)
(2) 支持自動轉向
(3) 支持 HTTPS 協議
(4) 支持代理服務器等
3.什麼是JSoup?
jsoup是一款Java的HTML解析器,可直接解析某個URL地址、HTML文本內容。它提供了一套非常省力的API,可通過DOM,CSS以及類似於jQuery的操作方法來取出和操作數據。
它的主要功能有:
(1) 從一個URL,文件或字符串中解析HTML;
(2) 使用DOM或CSS選擇器來查找、取出數據;
(3) 可操作HTML元素、屬性、文本;
3.爲什麼要和JSoup共同使用?
httpClient 屬於專業的抓取網頁的庫,可以設置代理,抓取失敗可以重試抓取
在我的實際使用中,單獨用jsoup也可以直接抓取網頁,但是在抓取上,jsoup比較弱,API簡單,功能也簡單,主要是擴展htmlparser的功能吧,解析html。測試過程中jsoup抓取頁面經常報錯(time out等等)。
因此,我們可以用httpclient抓取網頁,再用Jsoup.parse解析頁面。
4.項目maven依賴
HttpClient 4.5.6:
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
JSoup 1.8.3
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.8.3</version>
</dependency>
5.HttpClient的入門使用
(1)簡介
使用HttpClient發送請求、接收響應很簡單,一般需要如下幾步即可。
1. 創建HttpClient對象。
2. 創建請求方法的實例,並指定請求URL。如果需要發送GET請求,創建HttpGet對象;如果需要發送POST請求,創建HttpPost對象。
3. 如果需要發送請求參數,可調用HttpGet、HttpPost共同的setParams(HetpParams params)方法來添加請求參數;對於HttpPost對象而言,也可調用setEntity(HttpEntity entity)方法來設置請求參數。
4. 調用HttpClient對象的execute(HttpUriRequest request)發送請求,該方法返回一個HttpResponse。
5. 調用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可獲取服務器的響應頭;調用HttpResponse的getEntity()方法可獲取HttpEntity對象,該對象包裝了服務器的響應內容。程序可通過該對象獲取服務器的響應內容。
6. 釋放連接。無論執行方法是否成功,都必須釋放連接
(2)上一個簡單的示例Demo:
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 test {
//聲明需要爬取的網址
static String URL="http://www.baidu.com";
//主函數入口
public static void main(String args[]){
//建立一個新的請求客戶端
CloseableHttpClient httpClient=HttpClients.createDefault();
//使用HttpGet的方式請求網址
HttpGet httpGet = new HttpGet(URL);
//獲取網址的返回結果
CloseableHttpResponse response=null;
try {
response=httpClient.execute(httpGet);
} catch (IOException e) {
e.printStackTrace();
}
//獲取返回結果中的實體
HttpEntity entity = response.getEntity();
//將返回的實體輸出
try {
System.out.println(EntityUtils.toString(entity));
} catch (IOException e) {
e.printStackTrace();
}
}
}
6.JSoup的入門使用
我學習JSoup的過程中,這個作者的博客專欄提供了很大的幫助:專欄:使用JSOUP實現網絡爬蟲
同時,可以參考jsoup Cookbook(中文版),也非常的棒。
這裏就根據CookBook來對jsoup的使用進行歸納
(1)主要類的簡介
JSoup API中有6
個包提供用於開發jsoup應用程序的類和接口。包中有很多類。
我們常用到的主要類:Jsoup、Document、Element/Elements
1.Document類:
是Jsoup的HTML文檔對象模型,它由很多節點組成。
Document將html文檔(String類型)解析爲很多的Element和TextNode對象,其中TextNode繼承自Node對象
繼承鏈:
Document extends Element extends Node
TextNode extends Node
2.Element/Elements類:
Element類是Node的直接子類,它表示由一個標籤名,多個屬性和子節點組成的html元素。從這個元素中,你可以提取數據,可以遍歷節點樹,可以操縱html。
注:Node是節點的抽象模型。Elements, Documents, Comments等都是節點的實例。
Elements對象類似一個由多個Element對象組成的集合,有一接口爲List<Element>,可以使用Element.select()方法去得到Elements 對象。
注:判斷Elements對象是否爲空需要.isEmpty()方法,而Element可以用==null
注:Elements.select()不能得到Element對象
3.Jsoup類:
通常用來建立連接,獲取響應或發送響應
一個簡單示例:
Document doc = Jsoup.connect(url).timeout(2000).get();
(2)使用dom方法(遍歷一個Document對象)來查找元素 -不推薦
Elements這個對象提供了一系列類似於DOM的方法來查找元素,抽取並處理其中的數據。
1.查找元素
getElementById(String id)
getElementsByTag(String tag)
getElementsByClass(String className)
getElementsByAttribute(String key) (and related methods)
Element siblings: siblingElements(), firstElementSibling(), lastElementSibling(); nextElementSibling(), previousElementSibling()
Graph: parent(), children(), child(int index)
2.元素數據
attr(String key)獲取屬性attr(String key, String value)設置屬性
attributes()獲取所有屬性
id(), className() and classNames()
text()獲取文本內容text(String value) 設置文本內容
html()獲取元素內HTMLhtml(String value)設置元素內的HTML內容
outerHtml()獲取元素外HTML內容
data()獲取數據內容(例如:script和style標籤)
tag() and tagName()
3.操作HTML和文本
append(String html), prepend(String html)
appendText(String text), prependText(String text)
appendElement(String tagName), prependElement(String tagName)
html(String value)引用自CookBook-dom
(3)使用選擇器語法來查找元素
jsoup與其他解析器的區別就是可以使用類似jquery的選擇器語法來搜索及過濾出所需的元素
這裏我們還要介紹最重要的選擇器語法
jsoup elements對象支持類似於CSS (或jquery)的選擇器語法,來實現非常強大和靈活的查找功能。.
這個select 方法在Document, Element,或Elements對象中都可以使用。且是上下文相關的,因此可實現指定元素的過濾,或者鏈式選擇訪問。
Select方法將返回一個Elements集合,並提供一組方法來抽取和處理結果。
1.Selector選擇器概述
tagname: 通過標籤查找元素,比如:a
ns|tag: 通過標籤在命名空間查找元素,比如:可以用 fb|name 語法來查找 <fb:name> 元素
#id: 通過ID查找元素,比如:#logo
.class: 通過class名稱查找元素,比如:.masthead
[attribute]: 利用屬性查找元素,比如:[href]
[^attr]: 利用屬性名前綴來查找元素,比如:可以用[^data-] 來查找帶有HTML5 Dataset屬性的元素
[attr=value]: 利用屬性值來查找元素,比如:[width=500]
[attr^=value], [attr$=value], [attr*=value]: 利用匹配屬性值開頭、結尾或包含屬性值來查找元素,比如:[href*=/path/]
[attr~=regex]: 利用屬性值匹配正則表達式來查找元素,比如: img[src~=(?i)\.(png|jpe?g)]
*: 這個符號將匹配所有元素
Selector選擇器組合使用
el#id: 元素+ID,比如: div#logo
el.class: 元素+class,比如: div.masthead
el[attr]: 元素+class,比如: a[href]
任意組合,比如:a[href].highlight
ancestor child: 查找某個元素下子元素,比如:可以用.body p 查找在"body"元素下的所有 p元素
parent > child: 查找某個父元素下的直接子元素,比如:可以用div.content > p 查找 p 元素,也可以用body > * 查找body標籤下所有直接子元素
siblingA + siblingB: 查找在A元素之前第一個同級元素B,比如:div.head + div
siblingA ~ siblingX: 查找A元素之前的同級X元素,比如:h1 ~ p
el, el, el:多個選擇器組合,查找匹配任一選擇器的唯一元素,例如:div.masthead, div.logo
2.僞選擇器selectors
:lt(n): 查找哪些元素的同級索引值(它的位置在DOM樹中是相對於它的父節點)小於n,比如:td:lt(3) 表示小於三列的元素
:gt(n):查找哪些元素的同級索引值大於n,比如: div p:gt(2)表示哪些div中有包含2個以上的p元素
:eq(n): 查找哪些元素的同級索引值與n相等,比如:form input:eq(1)表示包含一個input標籤的Form元素
:has(seletor): 查找匹配選擇器包含元素的元素,比如:div:has(p)表示哪些div包含了p元素
:not(selector): 查找與選擇器不匹配的元素,比如: div:not(.logo) 表示不包含 class=logo 元素的所有 div 列表
:contains(text): 查找包含給定文本的元素,搜索不區分大不寫,比如: p:contains(jsoup)
:containsOwn(text): 查找直接包含給定文本的元素
:matches(regex): 查找哪些元素的文本匹配指定的正則表達式,比如:div:matches((?i)login)
:matchesOwn(regex): 查找自身包含文本匹配指定正則表達式的元素
注意:上述僞選擇器索引是從0開始的,也就是說第一個元素索引值爲0,第二個元素index爲1等*/引用自CookBook-選擇器
select()的使用還可以參考這一篇博文,寫的也很好。
(4)從元素抽取屬性,文本和HTML
在解析獲得一個Document實例對象,並查找到一些元素之後,如何取得在這些元素中的數據呢?
方法
要取得一個屬性的值,可以使用Node.attr(String key) 方法
對於一個元素中的文本,可以使用Element.text()方法
對於要取得元素或屬性中的HTML內容,可以使用Element.html(), 或 Node.outerHtml()方法
示例:
String html = "<p>An <a href='http://example.com/'><b>example</b></a> link.</p>";
Document doc = Jsoup.parse(html);//解析HTML字符串返回一個Document實現
Element link = doc.select("a").first();//查找第一個a元素String text = doc.body().text(); // "An example link"//取得字符串中的文本
String linkHref = link.attr("href"); // "http://example.com/"//取得鏈接地址
String linkText = link.text(); // "example""//取得鏈接地址中的文本String linkOuterH = link.outerHtml();
// "<a href="http://example.com"><b>example</b></a>"
String linkInnerH = link.html(); // "<b>example</b>"//取得鏈接內的html內容
tips:
上述方法是元素數據訪問的核心辦法。此外還其它一些方法可以使用:Element.id()
Element.tagName()
Element.className() and Element.hasClass(String className)
這些訪問器方法都有相應的setter方法來更改數據.
(5)這個示例是當時看完某教程後的練手Demo:
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
class test2{
public static void main(String args[]){
//設置需要爬取的網頁,這裏爲方便起見就直接用Jsoup自帶的api來爬取網頁了
//這個網頁是桂林電子科技大的信息科技學院的學院新聞版塊頁面
String url = "http://www.guit.edu.cn/xwzx/mtxk.htm";
//聲明Document類,來存儲爬取到的html文檔
Document doc = null;
try {
doc = Jsoup.connect(url).timeout(2000).get();
//調用Jsoup類中的connect()方法,url爲需要爬取的頁面
//timeout()來設置超時時間,get()方法來獲取響應頁面
} catch (IOException e) {
e.printStackTrace();
}
//System.out.println(doc);//測試用
//使用select選擇器
Elements elements = doc.select(".box-list").select(".oh").select("a");
//System.out.println(elements);//測試用
for(Element e:elements){
if(e.text().length()>8){
//逐條輸出新聞信息
System.out.println(e.text());
}
}
}
}
7.一個完整的Demo
當時寫這個Demo時,這篇博文給了我很大幫助。我對其中的代碼加以完善和詳細註釋,附在下面。
(1)GetResult類,用HttpClient來抓取頁面
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.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
public class GetResult {
public static String getResult(String url) throws Exception {
//這裏用了try-with-resource語法,在try()括號中的資源會在try語句塊執行完之後自動釋放
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build();
CloseableHttpResponse response = httpClient.execute(new HttpGetConfig(url)))
{
String result = EntityUtils.toString(response.getEntity());
return result;
} catch (Exception e) {
System.out.println("獲取失敗");
return "";
}
//所以不需要再finally中釋放資源。
}
}
//內部類,繼承HttpGet,爲了設置請求超時的參數
class HttpGetConfig extends HttpGet {
public HttpGetConfig(String url) {
super(url);
setDefaulConfig();
}
private void setDefaulConfig() {
this.setConfig(RequestConfig.custom()
.setConnectionRequestTimeout(10000)
.setConnectTimeout(10000)
.setSocketTimeout(10000).build());
this.setHeader("User-Agent", "spider");
}
}
(2)GetImg類,用Jsoup解析獲取的頁面
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.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
public class GetImg {
public class GetImg {
public GetImg(String url) throws Exception{
//獲取工具類GetResult返回的html,並用Jsoup解析
String result = GetResult.getResult(url);
Document document = Jsoup.parse(result);
//若HTML文檔包含相對URLs路徑,需要將這些相對路徑轉換成絕對路徑的URLs
document.setBaseUri(url);//指定base URI
//獲取所有的img元素
Elements elements = document.select("img");
int i=1;
for (Element e : elements) {
//獲取每個src的絕對路徑
String src = e.absUrl("src");
URL urlSource = new URL(src);
URLConnection urlConnection = urlSource.openConnection();
//設置圖片名字
String imageName = src.substring(src.lastIndexOf("/") + 1,i++);
//控制檯輸出圖片的src
System.out.println(e.absUrl("src"));
//通過URLConnection得到一個流,將圖片寫到流中,並且新建文件保存
InputStream in = urlConnection.getInputStream();
OutputStream out = new FileOutputStream(new File("E:\\IDEA\\imgs\\", imageName));
byte[] buf = new byte[1024];
int l = 0;
while ((l = in.read(buf)) != -1) {
out.write(buf, 0, l);
}
}
}
}
想要了解更多關於URL的處理,可以查看CookBook-URL
(3)測試單元test1.java
import org.junit.Test;
/**
* 2018-9-8 單元測試
* @author ljx
*/
public class test1 {
@Test
public void testGetResult() throws Exception{
GetImg getImg = new GetImg("https://www.bilibili.com/");
}
}
關於JUnit我這裏就不班門弄斧了,也是正在學習,想要了解的轉這裏。