帶你入門Java網絡爬蟲

爬蟲初識

記得,在本科時,因爲畢業論文需要不少網絡上用戶的問答數據。最開始,我並沒有搞過網絡爬蟲,只能利用關鍵詞搜索的方式,找到相關的數據,然後一條一條複製。你可能覺得這樣很傻,但不得不承認這確實我最初的操作方式,很艱難,累的手疼。
在這裏插入圖片描述

後來,讀研究生時,既要開展實際的項目,同時也要做科研。項目和科研,都需要採集大量的網絡數據。領頭做項目的師兄,指定了一系列國內外網站,並把採集任務分配給我。額,當時,對於啥都不咋會的我,只能說“這該咋弄啊?這咋弄啊?。。”。沒辦法,硬着頭皮也要上。
在這裏插入圖片描述
後來,經師兄指點,讓我去學習網路爬蟲,說網路爬蟲可以搞定“我想要的數據”。聽師兄的,不會錯,那就學吧。
在這裏插入圖片描述

選Java還是選Python

決定要用網絡爬蟲去採集數據了,但面臨一個選擇就是:是用Java還是Python寫網絡爬蟲呢?對於一個新手,我看了一些網上的各種對比帖子,各有各的觀點,其中不少說Python上手容易,寫起來方便。但最終我還是選擇了Java,有以下幾點原因:
(1) Java火了很多年,而且依舊很火,其生態也比較完善。目前,很多大公司的系統皆採用Java設計,足以說明其強大之處。把Java學好了,足夠讓我找一份不錯的工作。
(2) Java是複雜一些難一些,但嚴謹規範,對於大型工程,對於大型程序,如果不規範不嚴謹維護豈不容易出問題。
(3) 對網絡爬蟲而言,JAVA中也有很多簡單易用的類庫(如Jsoup、Httpclient等),同時還存在不少易於二次開發的網絡爬蟲框架(Crawler4J、WebMagic等)。
(4) 曾在一個帖子中看到,“世界上99%的人都會選擇一條容易走的大路,因爲人都喜歡安逸。這也是人的大腦的思維方式決定的,因爲大腦的使命是爲了讓你生存,而不是求知。但成功是總是屬於那1%的人,這類人是堅持讓大腦做不願意做的事的人——求知”。哎,這在我看來,還真有一定的道理。如果勵志想成爲一名真正的程序員,建議先學習Java。在此基礎上,如果你對Python感興趣,也是可以快速上手的。
在這裏插入圖片描述

Java網絡爬蟲來了

網絡爬蟲流程

學習網絡爬蟲之前,先看了普通網絡爬蟲大致流程,如下圖所示:
在這裏插入圖片描述
主要包括5個步驟:

  1. 選取部分種子URL(或初始URL),將其放入待採集的隊列中。如在Java中,可以放入List、LinkedList以及Queue中。
  2. 判斷URL隊列是否爲空,如果爲空則結束程序的執行,否則執行步驟3。
  3. 從待採集的URL隊列中取出一個URL,獲取URL對應的網頁內容。在此步驟需要使用HTTP響應狀態碼(如200和403等)判斷是否成功獲取到了數據,如響應成功則執行解析操作;如響應不成功,則將其重新放入待採集URL隊列(注意這裏需要過濾掉無效URL)。
  4. 針對響應成功後獲取到的數據,執行頁面解析操作。此步驟根據用戶需求獲取網頁內容中的部分字段,如汽車論壇帖子的id、標題和發表時間等。
  5. 針對步驟4解析的數據,執行數據存儲操作。

需要掌握的Java基礎知識

在使用Java構建網絡爬蟲時,需要掌握很多Java方面的基礎知識。例如,Java中基本的數據類型、Java中的數組操作、判斷語句的使用、集合操作、對象和類的使用、String類的使用、日期和時間的處理、正則表達式的使用、Maven工程的創建、多線程操作、日誌的使用等。
在這裏插入圖片描述
看着知識點很多,但如果將其放入到具體的網絡爬蟲實戰項目中去學習,會發現很簡單。下面,我舉兩個例子。
在網絡爬蟲中,我們經常需要將待採集的URL放到集合中,然後循環遍歷集合中的每個URL去採集數據。比如,我們使用Queue集合操作:

		Queue<String> urlQueue = new LinkedList<String>();
		//添加要採集的URL
		urlQueue.offer("https://ccm.net/download/?page=1");
		urlQueue.offer("https://ccm.net/download/?page=2");
		urlQueue.offer("https://ccm.net/download/?page=3");
		boolean t = true;
		while (t) {
			//如果隊列爲空,循環結束
			if( urlQueue.isEmpty() ){
				t = false;
			}else {
				//取出每個URL
				String url = urlQueue.poll();
				//獲取HTML
				String getHtml = ...;
				//判斷是否成功請求到HTML
				if (成功請求到HTML) {
					//解析數據
					...;
				}else {    //如果網頁存在但沒有請求到數據,重新添加到隊列中
					urlQueue.offer(url);  
				}
			}
		}

另外,在採集數據時,不同網站的時間使用格式可能不同。而不同的時間格式,會爲數據存儲以及數據處理帶來一定的困難。例如,下圖爲某汽車論壇中時間使用的格式,即“yyyy-MM-dd”和“yyyy-MM-dd HH:mm”兩種類型。
在這裏插入圖片描述
下圖爲某新聞網站中的時間使用格式“yyyy-MM-dd HH:mm:ss”。
在這裏插入圖片描述
再如,藝術品網站deviantart(https://www.deviantart.com/enterthespectrum/modals/memberlist/)的時間使用的是UNIX時間戳的形式。
在這裏插入圖片描述
針對汽車論壇中的“yyyy-MM-dd”和“yyyy-MM-dd HH:mm”格式,可以統一轉化成“yyyy-MM-dd HH:mm:ss”格式,以方便數據存儲以及後期數據處理。此時,可以寫個方法將將字符串類型的時間標準化成指定格式的時間。如下程序:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TimeTest {
	public static void main(String[] args) {
		System.out.println(parseStringTime("2016-05-19 19:17",
				"yyyy-MM-dd HH:mm","yyyy-MM-dd HH:mm:ss"));
		System.out.println(parseStringTime("2018-06-19",
				"yyyy-MM-dd","yyyy-MM-dd HH:mm:ss"));
	}
	/**
	 * 字符型時間格式標準化方法
	 * @param inputTime(輸入的字符串時間),inputTimeFormat(輸入的格式),outTimeFormat(輸出的格式).
	 * @return 轉化後的時間(字符串)
	 */
	public static String parseStringTime(String inputTime,String inputTimeFormat,
			String outTimeFormat){
		String outputDate = null;
		try {
			//日期格式化及解析時間
			Date inputDate = new SimpleDateFormat(inputTimeFormat).parse(inputTime); 
			//轉化成新的形式的字符串
			outputDate = new SimpleDateFormat(outTimeFormat).format(inputDate); 
		} catch (ParseException e) {
			e.printStackTrace();
		}
		return outputDate;
	}
}

針對UNIX時間戳,可以通過如下方法處理:

	//將unix時間戳轉化成指定形式的時間
	public static String TimeStampToDate(String timestampString, String formats) {
		Long timestamp = Long.parseLong(timestampString) * 1000;
		String date = new SimpleDateFormat(formats, 
				Locale.CHINA).format(new Date(timestamp));
		return date;
	}

HTTP協議基礎與網絡抓包

做網絡爬蟲,還需要了解HTTP協議相關的內容,即要清楚數據是怎麼在服務器和客戶端傳輸的。
在這裏插入圖片描述
具體需要了解的內容包括:

  1. URL的組成:如協議、域名、端口、路徑、參數等。
  2. 報文:分爲請求報文和響應報文。其中,請求報文包括請求方法、請求的URL、版本協議以及請求頭信息。響應報文包括請求協議、響應狀態碼、響應頭信息和響應內容。響應報文包括請求協議、響應狀態碼、響應頭信息和響應內容。
  3. HTTP請求方法:在客戶端向服務器發送請求時,需要確定使用的請求方法(也稱爲動作)。請求方法表明了對URL指定資源的操作方式,服務器會根據不同的請求方法做不同的響應。網絡爬蟲中常用的兩種請求方法爲GET和POST。
  4. HTTP狀態碼:HTTP狀態碼由3位數字組成,描述了客戶端向服務器請求過程中發生的狀況。常使用200判斷網絡是否請求成功。
  5. HTTP信息頭:HTTP信息頭,也稱頭字段或首部,是構成HTTP報文的要素之一,起到傳遞額外重要信息的作用。在網絡爬蟲中,我們常使用多個User-Agent和多個referer等請求頭來模擬人的行爲,進而繞過一些網站的防爬措施。
  6. HTTP響應正文:HTTP響應正文(或HTTP響應實體主體),指服務器返回的一定格式的數據。網絡爬蟲中常遇到需要解析的幾種數據包括:HTML/XML/JSON。

在這裏插入圖片描述
在這裏插入圖片描述
在開發網絡爬蟲時,給定 URL,開發者必須清楚客戶端是怎麼向服務器發送請求的,以及客戶端請求後服務器返回的數據是什麼。只有瞭解這些內容,開發者才能在程序中拼接URL,針對服務返回的數據類型設計具體的解析策略。因此,網絡抓包是實現網絡爬蟲必不可少的技能之一,也是網絡爬蟲開發的起點。例如,給定下面的URL:

https://tianchi.aliyun.com/dataset/?spm=5176.12282016.0.0.7bba15a2ZOO4VC

即我想要獲取阿里天池上的數據集介紹的信息:
在這裏插入圖片描述
從上圖中,可以看到共有107頁,點擊第二頁,會發現瀏覽器中的URL變爲發生變化。但通過谷歌瀏覽器抓包的方式,會發現數據真實請求的URL。另外從下圖可以看到請求的方式使用的是POST的形式,並且POST提交的參數是以JSON的形式呈現,同時在請求頭中包含x-csrf-token。

在這裏插入圖片描述
另外,通過Preview可以看到網頁數據返回的形式爲JSON:
在這裏插入圖片描述

小工具實現數據採集

網絡爬蟲主要涉及的是網頁請求,網頁解析和內容存儲。下面,使用一塊簡單易用的Jsoup工具實現幾個案例數據的採集。

Jsoup是一款基於Java語言的開源項目,主要用於請求URL獲取網頁內容、解析HTML和XML文件。使用Jsoup可以非常輕鬆的構建一些輕量級網絡爬蟲。

文本類數據採集

要採集的網站地址爲:https://www.pythonforbeginners.com/。網站內容如下圖所示:
在這裏插入圖片描述
其中,我們要採集的字段包括:帖子的標題和帖子的簡介

我們利用網絡抓包的形式發現該網頁返回的數據爲HTML格式,如下圖所示。從中可以看到,我們想要的數據都在標籤“li class=‘hentry’”中。
在這裏插入圖片描述

之後,我們需要使用的jar包,即Jsoup。在Eclipse中創建Maven工程,並在工程的pom.xml文件中添加Jsoup對應的dependency:

		<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
		<dependency>
			<groupId>org.jsoup</groupId>
			<artifactId>jsoup</artifactId>
			<version>1.11.3</version>
	       </dependency>

基於此jar包,可以編寫一個請求該網站數據,解析該網站數據的程序,如下:

package com.qian.test;

import java.io.IOException;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class Test {

	public static void main(String[] args) throws IOException {
		//創建連接---注意這裏是HTTPS協議
		Connection connect = Jsoup.connect("https://www.pythonforbeginners.com/").validateTLSCertificates(false);
		//請求網頁
		Document document = connect.get();
		//解析數據--CSS選擇器
		Elements elements = document.select("li[class=hentry]");
		for (Element ele : elements) {
			String title = ele.select("h2").text();
			String url = ele.select("h2>a").attr("href");
			//控制檯輸出的形式---也可以文件流輸出或導入數據庫
			System.out.println(title + "\t" + url); 
		}
	}
}

執行該程序會在控制檯輸出以下內容:
在這裏插入圖片描述
這裏需要注意的是,該網站使用的是HTTPS協議,因此需要調用validateTLSCertificates()方法。否則,如果只使用下面的方式請求頁面,則會報錯。

		//創建連接
		Connection connect = Jsoup.connect("https://www.pythonforbeginners.com/");
		//請求網頁
		Document document = connect.get();

在這裏插入圖片描述

圖片數據的採集

圖片數據是非常常見的一種數據形式。很多做圖片處理的研究者,經常需要採集一些網站上的圖片,下面以Jsoup工具演示如下采集圖片。給定圖片地址:

https://www.makro.co.za/sys-master/images/h98/h64/9152530710558/06cf39e4-7e43-42d4-ab30-72c81ab0e941-qpn13_medium

對應的圖片爲:
在這裏插入圖片描述
如下爲操作程序:

package com.qian.jsoupconnect;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.Connection.Method;
import org.jsoup.Connection.Response;
//實現圖片的下載
public class JsoupConnectInputstream {
	
	public static void main(String[] args) throws IOException {
		String imageUrl = "https://www.makro.co.za/sys-master/images/h98/h64/9152530710558/06cf39e4-7e43-42d4-ab30-72c81ab0e941-qpn13_medium";
		Connection connect = Jsoup.connect(imageUrl);
		Response response = connect.method(Method.GET).ignoreContentType(true).execute();  
		System.out.println("文件類型爲:" + response.contentType());
		//如果響應成功,則執行下面的操作
		if (response.statusCode() ==200) {
			//響應轉化成輸出流
			BufferedInputStream bufferedInputStream = response.bodyStream();
			//保存圖片
			saveImage(bufferedInputStream,"image/1.jpg");
		}
	}
	
	/**
     * 保存圖片操作
     * @param  輸入流
     * @param  保存的文件目錄
	 * @throws IOException
     */
	static void saveImage(BufferedInputStream inputStream, String savePath) throws IOException  {
		
		byte[] buffer = new byte[1024];
		int len = 0;
		//創建緩衝流
		FileOutputStream fileOutStream = new FileOutputStream(new File(savePath));
		BufferedOutputStream bufferedOut = new BufferedOutputStream(fileOutStream);
		//圖片寫入
		while ((len = inputStream.read(buffer, 0, 1024)) != -1) {
			bufferedOut.write(buffer, 0, len);
		}
		//緩衝流釋放與關閉
		bufferedOut.flush();
		bufferedOut.close();
	}
}

在上述程序中,使用了ignoreContentType()方法,即忽略請求的數據類型。如果狀態碼爲200,即響應成功,接着對數據流進行操作,將圖片下載到指定目錄下。執行該程序,可以發現工程的“image/”目錄下成功多了,一張圖片。
在這裏插入圖片描述

大文件內容獲取問題

在採集數據時,經常遇到一些較大的文件,如包含大量文本信息的HTML文件、超過10M的圖片、PDF和ZIP等文件。Jsoup默認情況下最大隻能獲取1M的文件。因此,直接使用Jsoup請求包含大量文本信息的HTML文件,將導致獲取的內容不全;請求下載超過1M的圖片和ZIP等文件,將導致文件無法查看或解壓。但在Jsoup中,可以使用maxBodySize(int bytes)設置請求文件大小限制,來避免這種問題的出現。如下網站:

http://poi.mapbar.com/shanghai/F10

在這裏插入圖片描述
該網站中按照A-B-C-D…一直排下去,導致HTML過大。因此需要使用maxBodySize()方法。程序如下所示:

package com.qian.jsoupconnect;

import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.Connection.Method;
import org.jsoup.Connection.Response;

public class JsoupConnectBodySize2 {

	public static void main(String[] args) throws IOException {
		//如果不設置maxBodySize,會導致網頁不全
		String url = "http://poi.mapbar.com/shanghai/F10";
		Response response = Jsoup.connect(url).timeout(10*10*1000).maxBodySize(Integer.MAX_VALUE)
				.method(Method.GET).ignoreContentType(true).execute();
		System.out.println(response.parse());
	}
}

再如,我要請求一個壓縮文件,URL地址爲:

https://www-us.apache.org/dist//httpd/httpd-2.4.37.tar.gz

Jsoup下載httpd-2.4.37.tar.gz文件(8.75M)時,也需要使用maxBodySize()方法,同時用Integer.MAX_VALUE設置的請求文件大小。另外,在請求大文件時,超時時間也需設置的儘量長些。程序如下:

package com.qian.jsoupconnect;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.Connection.Method;
import org.jsoup.Connection.Response;
public class JsoupConnectBodySize1 {
	public static void main(String[] args) throws IOException {
		String url = "https://www-us.apache.org/dist//httpd/httpd-2.4.37.tar.gz";
		//超時時間設置長一些,下載大文件
		Response response = Jsoup.connect(url).timeout(10*60*1000)
				.maxBodySize(Integer.MAX_VALUE)
				.method(Method.GET).ignoreContentType(true).execute();
		//如果響應成功,則執行下面的操作
		if (response.statusCode() ==200) {
			//響應轉化成輸出流
			BufferedInputStream bufferedInputStream = response.bodyStream();
			//保存圖片
			saveFile(bufferedInputStream,"image/httpd-2.4.37.tar.gz");
		}
	}
	/**
     * 保存文件
     * @param  輸入流
     * @param  保存的文件目錄
	 * @throws IOException
     */
	static void saveFile(BufferedInputStream inputStream, String savePath) throws IOException  {
		
		byte[] buffer = new byte[1024];
		int len = 0;
		//創建緩衝流
		FileOutputStream fileOutStream = new FileOutputStream(new File(savePath));
		BufferedOutputStream bufferedOut = new BufferedOutputStream(fileOutStream);
		//圖片寫入
		while ((len = inputStream.read(buffer, 0, 1024)) != -1) {
			bufferedOut.write(buffer, 0, len);
		}
		//緩衝流釋放與關閉
		bufferedOut.flush();
		bufferedOut.close();
	}
}

執行該程序,可以發現“image/”目錄下成功下了httpd-2.4.37.tar.gz。
在這裏插入圖片描述

其他知識點

上面,只介紹了Jsoup的一些簡單用法。更多的知識點將涉及到連接超時問題、請求頭添加問題、POST請求問題、代理問題、詳細的HTML/XML數據解析問題。

另外,在Java網絡爬蟲中,還涉及其他網頁請求工具,例如:HttpClient/URLConnection/OkHttp等。

在數據解析方面,還涉及CSS選擇器的使用、Xpath語法的使用、HtmlCleaner和Htmlparser如何解析HTML;XML數據如何解析;JSON校正、GSON和Fastjson的使用等。

在數據存儲方面,還涉及輸入流和輸出流的操作、EXCEL的操作、數據庫的操作等。

另外,在Java中還存在很多優秀的開源的網絡爬蟲框架,如Crawler4J、Webcollector、WebMagic等。

在這裏插入圖片描述

爲幫助想要入門Java網絡爬蟲的讀者,這裏特意推薦一本書《網絡數據採集技術 Java網絡爬蟲實戰》。
在這裏插入圖片描述
該書的特色包括:

  1. 注重基礎:即梳理了Java網絡爬蟲涉及的Java基礎知識,如Maven的操作、Java基於語法、字符串操作、集合操作、日期格式化、正則表達式、輸入輸出流操作、數據庫操作等一系列知識點;
  2. **注重系統性:**即系統梳理網絡爬蟲開發的邏輯、HTTP協議的內容、網頁數據請求、網頁數據解析、網絡數據存儲等;
  3. **詳細的案例講解:**爲便於讀者實際操作,本書以典型網站爲例,講解Java網絡爬蟲涉及知識點,如HTTPS 請求認證問題、文本/圖片/PDF/壓縮包下載存儲問題、大文件內容獲取不全問題、模擬登陸問題、Javascript動態加載問題、定時數據採集問題等;
  4. **開源框架的使用:**介紹了三種簡單易用的Java網絡爬蟲框架,即Crawler4j、WebCollector和WebMagic,通過這三種框架的學習,讀者可以輕輕鬆鬆開發符合自身需求的網絡爬蟲項目;
  5. 爲便於讀者學習,每個數據採集項目,都提供了完整的代碼(github可下載),並且代碼給出了清晰的註釋

另外,本書適合的讀者包括:

  1. 想要以項目的方式學習Java的人員;
  2. Java網絡爬蟲開發的初學者和進階者;
  3. 科研人員,尤其是從事網絡大數據驅動的碩士生和博士生;
  4. 開設相關課程的高等院校師生。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章