言歸正傳,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抓取網頁= =。
---------------------
作者:AaronLin_
來源:CSDN
原文:https://blog.csdn.net/qq_35159818/article/details/80753701
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!