爬蟲專案

貓小爪喵發表於2019-06-07

一、目的

       雖然說python很好寫爬蟲,並且Java也有很多爬蟲框架,比如,crawler4j,WebMagic,WebCollector,我寫的這個爬蟲框架呢,只能解決特定的小問題,還沒辦法達到很好的通用性,但是通過這個專案,我們可以瞭解熟悉一下爬蟲的整體思路,以後用第三方爬蟲框架的時候也就很好上手了。

二、分析以及實現

      我們這裡以古詩文網作為資料來源,進行爬蟲;

      大致框架如下:

 

採集:

      我們的目標是,拿下來古詩文網中的古詩資料(標題,朝代,作者,正文),然後將資料放入資料庫中,或許可以直接copy頁面內容,再分析儲存,但是這個工作量簡直是太大了,不太現實,畢竟頁面中有很多我們並不需要的資料。

       那麼我們就需要採用第三方工具htmlunit去進行網頁資訊抓取,這個過程我們稱之為採集;htmlunit 是一款開源的java 頁面分析工具,讀取頁面後,可以有效的使用htmlunit分析頁面上的內容;瀏覽官網,我們可以看到htmlunit是這樣採集網頁資訊的:

try (WebClient webClient=new WebClient(BrowserVersion.CHROME);){
            HtmlPage htmlPage=webClient.getPage("https://so.gushiwen.org/gushi/tangshi.aspx");
            String text=htmlPage.asText();
            System.out.println(text);
        } catch (IOException e) {
            e.printStackTrace();
        }

        我們知道瀏覽器既可以傳送資料,也可以接收資料,htmlunit中正好就是模擬了一個瀏覽器來發請求接收資料;執行程式碼我們可以得到該網頁的全部資料。

         但是我們發現會有很多警告,主要原因是:(1)HtmlUnit對JavaScript支援不是很好

                                                                             (2)HtmlUnit對CSS支援不是很好

        我們的這個頁面中就有很多的js檔案,所以我們需要手動禁用一下,當然,爬不同的網頁,情況是不同的,可以按情況選擇是否禁用。

 webClient.getOptions().setJavaScriptEnabled(false);
webClient.getOptions().setCssEnabled(false);

===============================================================================================

我們先進行一次簡單的爬蟲,來理解一下采集,解析,清洗:

 try (WebClient webClient=new WebClient(BrowserVersion.CHROME);){
            webClient.getOptions().setJavaScriptEnabled(false);
            webClient.getOptions().setCssEnabled(false);
            HtmlPage htmlPage=webClient.getPage("https://so.gushiwen.org/shiwenv_45c396367f59.aspx");
            HtmlDivision division=(HtmlDivision) htmlPage.getElementById("contson45c396367f59");
            String text=division.asText();
            System.out.println(text);
        } catch (IOException e) {
            e.printStackTrace();
        }

         如上,我們採集了一個網頁,用getElementById()得到了網頁的一個片段,用asTest()取得了它的文字,這個過程可以看做是解析,我們進行了簡單的控制檯輸出,可以看做是清洗。那我們就明白了,解析就是得到我們想要的資料,清洗就是決定資料的去向。

===============================================================================================

解析:     

        我們分析,對於古詩文網這個網站,首先進去的時候是一個包含有很多超連結的頁面,我們稱呼為文件頁面,點進去超連結之後才是我們要找的古詩,這個頁面我們成為詳情頁,也就是剛才文件頁面的子頁面,那麼就是說,對於抓取下來的不同的頁面,我們要進行不同的解析。

       再分析,我們抓取下來的頁面,實際上只有htmlpage這個資訊,htmlpage有很多是我們需要的,但是我們想要頁面可以告訴我們,它是不是詳情頁,這個頁面資訊htmlpage,它的子頁面內容又是些什麼呢,頁面資料,頁面的url;

      所以,我們就需要包裝一個Page類,包含url(根地址+具體路徑),detail,htmlpage,subpage(子頁面集合),dataSet(資料集合,HashMap實現,key-value的形式存入,全程用HashMap的話,非常不清楚,我們就把它包裝成資料物件,提供get,put方法,用於放資料和取資料);

      這樣,我們就對我們包裝過的Page進行解析:

(1)詳情頁解析:

          前提,非詳情頁則返回;

         我們要得到古詩的標題,朝代,作者,正文;

如圖,分析頁面原始碼,我們看到標題是h1標籤裡的文字內容,我們可以右擊複製xPath,也可以自己寫,如下

String titlePath = "//div[@class='cont']/h1/text()";
DomText titleDom = (DomText) body.getByXPath(titlePath).get(0);
String title = titleDom.asText();

類似分析,我們也可以得到朝代,作者,正文資訊啦,就可以把他們存到我們包裝的Page裡面的DataSet裡

(2)文件頁解析

前提:是詳情頁則返回

    這就是我們的文件頁面,這裡顯然是拿不到我們想要的全部資訊的,可以看到,每個古詩對應一個url的具體路徑,我們只有把url的具體路徑拿下來,加上根地址組成url,再進入這個url頁面,進行詳情頁解析,就可以拿到我們想要的資料了;

    所以看頁面原始碼,可以看到url的具體路徑是在div下,屬性為class ,值為typecont的下的a標籤下,屬性為href的值,那麼遍歷儲存,我們就可以得到文件頁面所有的具體路徑;得到之後,new 一個subpage物件,根地址base不變,具體路徑path就是剛才得到的,此時detail設定為true,然後再把這個頁面加入子頁面集合中。

清洗:

(1)儲存到資料庫

解析完資料,就已經把資料放到DataSet裡面了,我們只需要從裡面getdata(),然後提供一個資料來源dataSource,sql語句,建立連線,然後preparedStatement,執行更新,放入資料就好了。

(2)列印到控制檯

直接從DataSet裡面獲取資料,然後輸出。

================================================================================================ 

爬蟲排程器:

        由上面可以看出,解析器和清洗器都有多個,那麼我們就建立一個解析器集合和清洗器集合

        要實現爬蟲排程器,主要要做到以下幾步:啟動(採集,解析,清洗),關閉,多執行緒執行

        我們建立一個文件頁佇列和詳情頁佇列來放文件頁和詳情頁。

      (1)  從文件頁佇列中拿Page,迴圈拿,直到page為空,然後對page進行解析遍歷,完成之後,如果是文件頁,就會得到subpage,這時,subpage只有url和detail資訊,所以遍歷subpage集合,把子頁面放回到文件佇列中去,第二次的時候,就會得到htmlpage資訊,遍歷解析器,進行詳情頁解析器解析,得到dataset,放到詳情頁佇列中去;這整個過程就是parse()方法要實現的。

     (2)清洗器同樣也是,pipeline()方法內實現,從詳情頁佇列中拿Page,遍歷清洗器集合。

     (3)給parse,pipeline加上執行緒執行器executorService,由單執行緒變成多執行緒;

     (4)向解析器集合中加解析器,實現addParse();向清洗器集合中加清洗器,實現addPipeline();向文件佇列中加Page,實現addPage();

     (5)關閉排程器,如果排程器不為空,並且沒有關閉,我們就shutdown();

然後在主方法中執行:

public static void main(String[] args) {
        final Page page=new Page("https://so.gushiwen.org","/gushi/tangshi.aspx",false);

        Crawler crawler=new Crawler();

        crawler.addParse(new DocumentParse());
        crawler.addParse(new DataPageParse());

        DruidDataSource dataSource=new DruidDataSource();
        dataSource.setUsername("***");//加入你的資料庫的使用者名稱
        dataSource.setPassword("***");//加入你資料庫的密碼
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/tangshi");//按這個格式,加入你的資料庫表
        crawler.addPipeline(new DatabasePipeline(dataSource));
//       crawler.addPipeline(new ConsolePipeline());
        crawler.addPage(page);
        crawler.start();

    }

這裡使用Druid連結池,減少連線建立的次數和時間,控制資源的使用。(我覺得是可以類比晾衣服,你不可能晾一個衣服就取去衣櫃取一次衣架,最好的肯定是把衣架多拿幾個,不夠用了再拿,如果衣服幹了,就把衣架拿下來重複使用);   

================================================================================================

總結:

     爬蟲的整個過程其實就是,爬下來頁面資訊放到相應的佇列中去,然後給解析解析器,進行不同的解析,如果是文件頁,解析完之後再放到文件頁佇列中,接著採集,如果是詳情頁,就給清洗,讓它存到資料庫中。

     如果要爬其他頁面的話,把解析器,清洗器修改一下就好了。

專案原始碼:https://github.com/wawSophie/Crawler

 

        

相關文章