爬蟲怎麼實現

言歸正傳,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 
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章