總結一下五種實現網路爬蟲的方法(一,基於socket通訊編寫爬蟲)
最近呢,由於實習需要呢,複習一遍爬蟲,前斷時間閉關刷題去了,也會把刷題心得總結成部落格分享給大家,比如java集合類特性及原始碼解析,作業系統資料結構的一些演算法,設計模式等,放心,肯定不會鴿的,雖然可能會晚一點寫。
言歸正傳,java實現網路爬蟲一般有五種方法(據我所知,要是有其他方法的同學歡迎分享)
1.基於socket通訊編寫爬蟲:最底層的方式,同時也是執行最高效的,不過開發效率最低。
2.基於HttpURLConnection類編寫爬蟲:java se的net包的核心類,主要用於http的相關操作。
3.基於apache的HttpClient包編寫爬蟲:由net包拓展而來,專為java網路通訊程式設計而服務。
4.基於phantomjs之類的無頭(無介面)瀏覽器:
(1)它是瀏覽器的核心,並非瀏覽器。換言之,它是沒有UI的瀏覽器。
(2)它提供的js api,故它可以方便直接的被各種程式語言呼叫。換言之,似乎是js寫的。
5.基於Selenium或者是WebDriver之類的有頭(有介面)瀏覽器
(1)它是瀏覽器核心,並非瀏覽器。換言之,它是沒有介面UI的瀏覽器。無頭,即無介面。
(2)它提供的js api,故它可以方便直接的被各種程式語言呼叫。
其實第五個呢,我也不是很熟悉,到時候寫第五篇的時候呢,會一邊學一邊寫,可能會有比較幼稚的錯誤,歡迎大家指點哈。
這部分呢。借鑑了周光亮老師的視訊上的部分講解。
首先,這篇介紹的是socket程式設計編寫爬蟲,當然,一般在程式開發的時候我們一般不會用這種方式,畢竟httpclient幾行程式碼的事情,但是基於這種方式使其他的方式更易於理解,瞭解一下還是比較必要的。
socket並不是什麼通訊協義,只是為了方便tcp/ip層的上層訪問tcp/ip層而做一層封裝。即應用層以下負責ip地址間的傳輸,而應用層應用socket實現端對端的傳輸,即精確到 IP:埠。
首先呢,因為之後要寫出很多的類,所以提前規劃一下包結構是比較好的,如下:
com.lzx.simple.control這個包是用來給使用者控制行為的包
com.lzx.simple.enumeration 這個包集合了一些列舉型別,如TaskLevel用來控制優先順序。
package com.lzx.simple.enumeration;
/**
* 抓取任務的優先順序等級
* @author Administrator
*
*/
public enum TaskLevel {
HIGH,MIDDLES,LOW
}
com.lzx.simple.iface.crawl這個包集合了一些介面,畢竟我們需要面向介面來程式設計,什麼是面向介面程式設計?去拿本設計模式的書出去罰站
package com.lzx.simple.iface.crawl;
import com.lzx.simple.pojos.CrawlResultPojo;
import com.lzx.simple.pojos.UrlPojo;
public interface ICrawler {
public CrawlResultPojo crawl(UrlPojo urlPojo);
}
com.lzx.simple.imple.crawl這個包集合了一些介面的實現類,比如這次的SocktCrawlerImpl類實現了ICrawler介面,這個類的程式碼不放了,待會兒單獨解釋。
com.lzx.simple.manager這個包集合了一些管理方法
com.lzx.simple.pojos這個包集合了一些簡單物件,例如UrlPojo類
package com.lzx.simple.pojos;
import java.net.MalformedURLException;
import java.net.URL;
import com.lzx.simple.enumeration.TaskLevel;
/**
* 簡單的Java物件(Plain Ordinary Java Objects)
* @author Administrator
*
*/
public class UrlPojo {
private String url;
private TaskLevel taskLevel;
public String getHost(){
try {
URL url = new URL(this.url);
return url.getHost();
} catch (MalformedURLException e) {
// TODO 自動生成的 catch 塊
e.printStackTrace();
}
return null;
}
public UrlPojo(String url) {
this.url = url;
}
public UrlPojo(String url, TaskLevel taskLevel) {
this.url = url;
this.taskLevel = taskLevel;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public TaskLevel getTaskLevel() {
return taskLevel;
}
public void setTaskLevel(TaskLevel taskLevel) {
this.taskLevel = taskLevel;
}
}
我們可以看到UrlPojo中有url屬性(網址)和taskLevel(優先順序),除了getset還有gethost函式,這個類封裝 了一個需抓取的網頁,還有另一個類如下:
package com.lzx.simple.pojos;
public class CrawlResultPojo {
private boolean isSuccess;
private String pageContent;
private int httpStatuCode;
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean isSuccess) {
this.isSuccess = isSuccess;
}
public String getPageContent() {
return pageContent;
}
public void setPageContent(String pageContent) {
this.pageContent = pageContent;
}
public int getHttpStatuCode() {
return httpStatuCode;
}
public void setHttpStatuCode(int httpStatuCode) {
this.httpStatuCode = httpStatuCode;
}
}
我們用CrawlResultPojo這個類來表示抓取結果,三個屬性都很顯而易見,抓取是否成功,網頁內容,狀態碼。還有相應getset。有些package還沒有檔案,之後用到會新增並說明。
然後開始我們的socket通訊的類:
package com.lzx.simple.imple.crawl;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.swing.text.AbstractDocument.BranchElement;
import com.lzx.simple.iface.crawl.ICrawler;
import com.lzx.simple.pojos.CrawlResultPojo;
import com.lzx.simple.pojos.UrlPojo;
public class SocketCrawlerImpl implements ICrawler{
public CrawlResultPojo crawl(UrlPojo urlPojo) {
CrawlResultPojo crawlResultPojo=new CrawlResultPojo();
if (urlPojo==null||urlPojo.getUrl()==null ){
crawlResultPojo.setSuccess(false);
crawlResultPojo.setPageContent(null);
return crawlResultPojo;
}
String host=urlPojo.getHost();
if (host==null) {
crawlResultPojo.setSuccess(false);
crawlResultPojo.setPageContent(null);
return crawlResultPojo;
}
BufferedWriter bufferedWriter=null;
BufferedReader bufferedReader=null;
try {
Socket socket=new Socket(host, 80);
//socket.setKeepAlive(false);
bufferedWriter=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write("GET "+urlPojo.getUrl()+" HTTP/1.1\r\n");
bufferedWriter.write("HOST:"+host+"\r\n");
bufferedWriter.write("Connection:close"+"\r\n");
bufferedWriter.write("\r\n");//提示http header結束
bufferedWriter.flush();//flush()表示強制將緩衝區中的資料傳送出去,不必等到緩衝區滿.
bufferedReader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
StringBuilder stringBuilder=new StringBuilder();
while((line=bufferedReader.readLine())!=null){
stringBuilder.append(line+"\n");
}
crawlResultPojo.setSuccess(true);
crawlResultPojo.setPageContent(stringBuilder.toString());
return crawlResultPojo;
} catch (UnknownHostException e) {
// TODO 自動生成的 catch 塊
e.printStackTrace();
} catch (IOException e) {
// TODO 自動生成的 catch 塊
e.printStackTrace();
}finally {
try {
if (bufferedReader!=null) {
bufferedReader.close();
}
if (bufferedWriter!=null) {
bufferedWriter.close();
}
} catch (Exception e2) {
// TODO: handle exception
e2.printStackTrace();
}
}
return null;
}
public static void main(String[] args) {
SocketCrawlerImpl socketCrawlerImpl=new SocketCrawlerImpl();
UrlPojo urlPojo=new UrlPojo("http://www.baidu.com");
CrawlResultPojo crawlResultPojo=socketCrawlerImpl.crawl(urlPojo);
System.out.println(crawlResultPojo.getPageContent());
}
}
這個是整個類的原始碼
主要的抓取工作就在crawl這個函式中,因為我們要返回CrawlResultPojo物件,所以一開始new了一個CrawlResultPojo;
然後判斷待抓取的網頁物件urlPojo是不是空,以及它的屬性url是不是空。如果是就構造個返回失敗的CrawlResultPojo返回回去。
然後gethost獲得待抓取物件的主機名,用於待會兒構造http header。當然也要判斷一下首部是否為空了。
然後構造緩衝輸入流和輸出流bufferedWriter和bufferedReader;
然後構造Socket,建構函式的引數這裡是主機名+埠,埠預設是80,如果有特殊需要再改動;當然建構函式的引數也可以是IP地址+埠,端對端通訊的本質。
然後在bufferedWriter構造http首部以便傳送。
這裡就有一個比較有趣事情了,周光亮老師在視訊的講解中發現了一個bug,他發現使用http/1.1的協議會導致抓取有一段真空期,即程式輸出完這個頁面的字串後,會停頓一段時間然後再結束程式,而改用http/1.0的協議就不會,抓取完直接結束程式,周老師當時除錯了很久都沒有解決。
其實主要原因呢,是他在構造http首部的時候沒有加
bufferedWriter.write("Connection:close"+"\r\n");
這段程式碼,為什麼加了這段程式碼後就可以去除真空期呢?
因為,
http 1.0中預設是關閉的,需要在http頭加入"Connection: Keep-Alive",才能啟用Keep-Alive;
http 1.1中預設啟用Keep-Alive,如果加入"Connection: close ",才關閉。
嗯,1.1版本更持久一點- -,
構造完就呼叫flush扔出去了
然後輸入流就不多說了,基本套路。
這樣就實現了socket抓取網頁= =。