Java實現網絡爬蟲-Java入門|Java基礎課程

目標

  1. 網絡爬蟲的是作什麼的?
  2. 手動寫一個簡單的網絡爬蟲;

1. 網絡爬蟲

1.1. 名稱

  • 網絡爬蟲(又被稱爲網頁蜘蛛,網絡機器人),是一種按照必定的規則,自動地抓取萬維網信息的程序或者腳css

    本。html

  • 另一些不常使用的名字還有螞蟻、自動索引、模擬程序或者蠕蟲

1.2. 簡述

  • 網絡爬蟲是經過網頁的連接地址來尋找網頁,從網站某一個頁面(一般是首頁)開始,讀取網頁的內容,找到java

    在網頁中的其它連接地址,而後經過這些連接地址尋找下一個網頁,這樣一直循環下去,直到把這個網站全部node

    的網頁都抓取完爲止。web

  • 若是把整個互聯網當成一個網站,那麼網絡蜘蛛就能夠用這個原理把互聯網上全部的網頁都抓取下來。ajax

  • 因此要想抓取網絡上的數據,不只須要爬蟲程序還須要一個能夠接受」爬蟲「發回的數據並進行處理過濾的服務正則表達式

    器,爬蟲抓取的數據量越大,對服務器的性能要求則越高。數據庫

2. 流程

  • 網絡爬蟲是作什麼的? 他的主要工做就是 跟據指定的url地址 去發送請求,得到響應, 而後解析響應 , 一方面從編程

    響應中查找出想要查找的數據,另外一方面從響應中解析出新的URL路徑,而後繼續訪問,繼續解析;繼續查找須要的json

    數據和繼續解析出新的URL路徑 .

  • 這就是網絡爬蟲主要乾的工做. 下面是流程圖:

Java實現網絡爬蟲-Java入門|Java基礎課程

經過上面的流程圖 能大概瞭解到 網絡爬蟲 幹了哪些活 ,根據這些 也就能設計出一個簡單的網絡爬蟲出來.

一個簡單的爬蟲 必需的功能:

  1. 發送請求和獲取響應的功能 ;
  2. 解析響應的功能 ;
  3. 對 過濾出的數據 進行存儲 的功能 ;
  4. 對解析出來的URL路徑 處理的功能 ;

2.1. 關注點

  • 爬蟲須要關注的三個點:
    • 對抓取目標的描述或定義;
    • 對網頁或數據的分析與過濾;
    • 對URL的搜索策略

3. 分類

  • 網絡爬蟲按照系統結構和實現技術,大體能夠分爲如下幾種類型:
    • 通用網絡爬蟲(General Purpose Web Crawler)
    • 聚焦網絡爬蟲(Focused Web Crawler)
    • 增量式網絡爬蟲(Incremental Web Crawler)
    • 深層網絡爬蟲(Deep Web Crawler)。
    • 實際的網絡爬蟲系統一般是幾種爬蟲技術相結合實現的。

4. 思路分析

好了,咱們上面已經將在代碼中須要獲取的關鍵信息的XPath表達式都找到了,接下來就能夠正式寫代碼來實現了

5. 代碼實現

代碼實現部分採用webmagic框架,由於這樣比使用基本的的Java網絡編程要簡單得多
注:關於webmagic框架能夠看一下面講義

5.1. 代碼結構

Java實現網絡爬蟲-Java入門|Java基礎課程

5.2. 程序入口

Demo.java

/**
 * 程序入口
 */
public class Demo {

    public static void main(String[] args) {
        // 爬取開始
        Spider
        // 爬取過程
        .create(new WanhoPageProcessor())
        // 爬取結果保存
        .addPipeline(new WanhoPipeline())
        // 爬取的第一個頁面
        .addUrl("http://www.wanho.net/a/jyxb/")
        // 啓用的線程數
        .thread(5).run();
    }

}

5.3. 爬取過程

  • WanhoPageProcessor.java
package net.wanho.wanhosite;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.selector.Html;

public class WanhoPageProcessor implements PageProcessor {

    // 部分一:抓取網站的相關配置,包括編碼、抓取間隔、重試次數等
    private Site site = Site
            .me()
            .setTimeOut(10000)
            .setRetryTimes(3)
            .setSleepTime(1000)
            .setCharset("UTF-8");

    // 獲得站點
    @Override
    public Site getSite() {
        return site;
    }

    //爬取過程
    @Override
    public void process(Page page) {
        //獲取當前頁的全部喜報
        List<String> list = page.getHtml().xpath("//div[@class='main_l']/ul/li").all();
        //要保存喜報的集合
        Vector<ArticleVo> voLst = new Vector<>();
        //遍歷喜報
        String title;
        String content;
        String img;
        for (String item : list) {
            Html tmp = Html.create(item);
            //標題
             title = tmp.xpath("//div[@class='content']/h4/a/text()").toString();
            //內容
             content = tmp.xpath("//div[@class='content']/p/text()").toString();
            //圖片路徑
             img = tmp.xpath("//a/img/@src").toString();
            //加入集合
            ArticleVo vo = new ArticleVo(title, content, img);
            voLst.add(vo);
        }
        //保存數據至page中,後續進行持久化
        page.putField("e_list", voLst);

        //加載其它頁
        page.addTargetRequests( getOtherUrls());

    }
    //其它頁
    public List<String> getOtherUrls(){
        List<String> urlLsts = new ArrayList<>();
        for(int i=2;i<5;i++){
            urlLsts.add("http://www.wanho.net/a/jyxb/list_15_"+i+".html");
        }
        return urlLsts;
    } 
}

5.4. 結果保存

  • WanhoPipeline.java
package net.wanho.wanhosite;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Vector;

import us.codecraft.webmagic.ResultItems;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.pipeline.Pipeline;

public class WanhoPipeline implements Pipeline {

    @Override
    public void process(ResultItems resultItems, Task arg1) {
        // 獲取抓取過程當中保存的數據
        Vector<ArticleVo> voLst =resultItems.get("e_list");
        // 持久到文件中
        PrintWriter pw=null;
        try {
            pw = new PrintWriter(new FileWriter("wanho.txt",true));
            for(ArticleVo vo :voLst){
                pw.println(vo);
                pw.flush();

                saveImg(vo.getImg());
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            pw.close();
        }
    }

    private void saveImg(String img) {
        String imgUrl = "http://www.wanho.net"+img;
        InputStream is = null;
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        try {
            URL url = new URL(imgUrl);
            URLConnection uc = url.openConnection();
             is = uc.getInputStream();
             bis = new BufferedInputStream(is);
            File photoFile = new File("photo");
            if(!photoFile.exists()){
                photoFile.mkdirs();
            }
            String imgName = img.substring(img.lastIndexOf("/")+1);
            File saveFile = new File(photoFile,imgName);
             bos = new BufferedOutputStream(new FileOutputStream(saveFile));
            byte[] bs = new byte[1024];
            int len;
            while((len=bis.read(bs))!=-1){
                bos.write(bs, 0, len);
            }

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally{
            try {
                bos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                bis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

5.5. 模型對象

  • ArticleVo.java
package net.wanho.wanhosite;
public class ArticleVo {
    private String title;
    private String content;
    private String img;
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public String getImg() {
        return img;
    }
    public void setImg(String img) {
        this.img = img;
    }
    public ArticleVo(String title, String content, String img) {
        super();
        this.title = title;
        this.content = content;
        this.img = img;
    }
    @Override
    public String toString() {
        return "Article [title=" + title + ", content=" + content + ", img=" + img + "]";
    }

}

6. webmagic框架

在開源社區搜索java爬蟲框架 : 共有83種

Java實現網絡爬蟲-Java入門|Java基礎課程
咱們使用的是
Java實現網絡爬蟲-Java入門|Java基礎課程

6.1. 簡介

  • webmagic的是一個無須配置、便於二次開發的爬蟲框架,它提供簡單靈活的API,只需少許代碼便可實現一

    個爬蟲

  • webmagic採用徹底模塊化的設計,功能覆蓋整個爬蟲的生命週期(連接提取、頁面下載、內容抽取、持久

    化),支持多線程抓取,分佈式抓取,並支持自動重試、自定義UA/cookie等功能

  • webmagic包含強大的頁面抽取功能,開發者能夠便捷的使用css selector、xpath和正則表達式進行連接和內

    容的提取,支持多個選擇器鏈式調用

注:官方中文文檔:<http://webmagic.io/docs/zh/>;

6.2. 實現

6.2.1. jar包下載:

可使用maven構建依賴,如:

<dependency>
    <groupId>us.codecraft</groupId>
    <artifactId>webmagic-core</artifactId>
    <version>0.6.1</version>
</dependency>
<dependency>
    <groupId>us.codecraft</groupId>
    <artifactId>webmagic-extension</artifactId>
    <version>0.6.1</version>
</dependency>

固然,也能夠自行下載jar包,其地址是:<http://webmagic.io/>;

6.2.2. 觀察目標數據所處的HTML頁面位置:

通常來講,若是咱們須要抓取的目標數據不是經過ajax異步加載進來的話,那麼咱們均可以在頁面的HTML源代碼中的某個位置找到咱們所須要的數據

注:若是數據是經過異步加載到頁面中,那麼通常有如下兩種方式來獲取數據:

  1. 觀察頁面加載完成以前請求的全部URL(F12 –> Network選項),而後找到加載數據的那些json請求,最後直接請求那些URL獲取數據便可
  2. 模擬瀏覽器請求,等待必定時間以後從徹底加載完成的頁面中獲取數據便可。這類爬蟲一般須要內嵌瀏覽器內核,如:webmagic、phantom.js、HttpUnit等

下面我用咱們的官網將跟你們一塊兒來分析下如何實現這樣的一個爬蟲:
Java實現網絡爬蟲-Java入門|Java基礎課程
首先觀察咱們爬蟲的起始頁面是:http://www.wanho.net/a/jyxb/

  • 分析頁面
    Java實現網絡爬蟲-Java入門|Java基礎課程
    Java實現網絡爬蟲-Java入門|Java基礎課程

從上圖能夠看出,咱們是能夠直觀地從首頁的HTML源代碼中找到咱們所須要的標題、內容、圖片連接等信息的。那麼接下來咱們能夠經過哪一種方式將這些目標數據提取出來呢?

其實,關於頁面元素的抽取webmagic框架主要支持如下三種方式:

  1. XPath
  2. 正則表達式
  3. CSS選擇器

固然,選擇哪一種方式來抽取數據須要根據具體頁面具體分析。在這個例子中,很顯然使用XPath來抽取數據是最方便的

所以,接下來我就直接給出咱們須要抓取的數據的所在XPath路徑了:

  • 全部的喜報信息的URL用XPath表達式來表示就是://div[@class='main_l']/ul/li
  • 標題:用XPath表達式來表示 //div[@class='content']/h4/a/text()
  • 描述:用XPath表達式來表示//div[@class='content']/p/text()
  • 圖片:用XPath表達式來表示 //a/img/@src

注:「//」表示從相對路徑開始,最前面以「/」開始則表示從頁面的跟路經開始;後面的兩個/之間的內容表示一個元素,中括號裏面的內容則表示該元素的執行屬性,如:h1[@class=’entry-title’] 表示:擁有class屬性爲「entry-title」的h1元素

6.2.3. 頁面數據抽取:

使用webmagic抽取頁面數據時須要自定義一個類實現PageProcessor接口。

這個實現了PageProcessor接口的類主要功能爲如下三步曲:

  1. 爬蟲的配置:抓取頁面的相關配置,包括編碼、抓取間隔、重試次數等
  2. 頁面元素的抽取:使用正則表達式或者XPath等方式來抽取頁面元素
  3. 新連接的發現:從一個頁面發現待爬取的其餘目標頁面的連接
package net.wanho.wanhosite;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import us.codecraft.webmagic.Page;
import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.processor.PageProcessor;
import us.codecraft.webmagic.selector.Html;

public class WanhoPageProcessor implements PageProcessor {

    // 部分一:抓取網站的相關配置,包括編碼、抓取間隔、重試次數等
    private Site site = Site
            .me()
            .setTimeOut(10000)
            .setRetryTimes(3)
            .setSleepTime(1000)
            .setCharset("UTF-8");

    // 獲得站點
    @Override
    public Site getSite() {
        return site;
    }

    //爬取過程
    @Override
    public void process(Page page) {
        //獲取當前頁的全部喜報
        List<String> list = page.getHtml().xpath("//div[@class='main_l']/ul/li").all();
        //要保存喜報的集合
        Vector<ArticleVo> voLst = new Vector<>();
        //遍歷喜報
        String title;
        String content;
        String img;
        for (String item : list) {
            Html tmp = Html.create(item);
            //標題
             title = tmp.xpath("//div[@class='content']/h4/a/text()").toString();
            //內容
             content = tmp.xpath("//div[@class='content']/p/text()").toString();
            //圖片路徑
             img = tmp.xpath("//a/img/@src").toString();
            //加入集合
            ArticleVo vo = new ArticleVo(title, content, img);
            voLst.add(vo);
        }
        //保存數據至page中,後續進行持久化
        page.putField("e_list", voLst);

        //加載其它頁
        page.addTargetRequests( getOtherUrls());

    }
    //其它頁
    public List<String> getOtherUrls(){
        List<String> urlLsts = new ArrayList<>();
        for(int i=2;i<5;i++){
            urlLsts.add("http://www.wanho.net/a/jyxb/list_15_"+i+".html");
        }
        return urlLsts;
    } 

}

6.2.4. 爬蟲的啓動與中止:

Spider是爬蟲啓動的入口。在啓動爬蟲以前,咱們須要使用一個PageProcessor建立一個Spider對象,而後使用run()進行啓動。同時Spider的其餘組件(Downloader、Scheduler、Pipeline)均可以經過set方法來進行設置

注:更多詳細參數介紹能夠參考這裏的官方文檔:<http://webmagic.io/docs/zh/posts/ch4-basic-page-processor/spider-config.html>;

Demo.java

package net.wanho.wanhosite;
import us.codecraft.webmagic.Spider;
public class Demo {
    /**
     * 程序的入口
     */
    public static void main(String[] args) {
        Spider
        // 爬取過程
        .create(new WanhoPageProcessor())
        // 爬取結果保存
        .addPipeline(new WanhoPipeline())
        //第一個頁面
        .addUrl("http://www.wanho.net/a/jyxb/")
        //啓動三個線程
        .thread(3)
        //開始
        .run(); 
    }
}

6.3. 基於註解

對於抽取邏輯比較複雜的爬蟲咱們一般像上面那樣實現PageProcessor接口本身來寫頁面元素的抽取邏輯。可是對於抽取邏輯比較簡單的爬蟲來講,這時咱們能夠選擇在實體類上添加註解的方式來構建輕量型的爬蟲

6.3.1. 實體類的構造:

package net.wanho.wanhosite;
import java.util.List;

import us.codecraft.webmagic.model.annotation.ExtractBy;
import us.codecraft.webmagic.model.annotation.HelpUrl;
import us.codecraft.webmagic.model.annotation.TargetUrl;

@TargetUrl(value="http://www.wanho.net/a/jyxb/\\w+.html")
@HelpUrl(value="http://www.wanho.net/a/jyxb/")
public class ArticleExtra{
    /**
     * 標題
     */
    @ExtractBy(value="//div[@class='content']/h4/a/text()",notNull=true)
    private List<String> title;

    /**
     * 描述
     */
    @ExtractBy(value="//div[@class='content']/p/text()",notNull=true)
    private List<String> content;

    /**
     * 圖片
     */
    @ExtractBy("//a/img/@src")
    private List<String> img;

    @Override
    public String toString() {
        return "ArticleExtra [title=" + title + ", content=" + content + ", img=" + img + "]";
    }

    public List<String> getTitle() {
        return title;
    }

    public void setTitle(List<String> title) {
        this.title = title;
    }

    public List<String> getContent() {
        return content;
    }

    public void setContent(List<String> content) {
        this.content = content;
    }

    public List<String> getImg() {
        return img;
    }

    public void setImg(List<String> img) {
        this.img = img;
    }

}

從上面的代碼能夠看出,這裏除了添加了幾個註解以外,這個實體類就是一個普通的POJO,沒有依賴其餘任何東西。關於上面使用到的幾個註解的大概含義是:

  1. @TargetUrl:咱們須要抽取的數據全部的目標頁面,其值是一個正則表達式
  2. @HelpUrl:爲了獲得目標頁面的連接所須要訪問的頁面
  3. @ExtractBy:一個用於抽取元素的註解,它描述了一種抽取規則。它表示「使用這個抽取規則,將抽取到的結果保存到這個字段中」。可使用XPath、CSS選擇器、正則表達式和JsonPath等方式來抽取元素

6.3.2. 數據的持久化:

雖然在PageProcessor中咱們能夠實現數據的持久化(PS:基於註解的爬蟲能夠實現AfterExtractor 接口達到相似的目的),將爬蟲抓取到的數據保存到文件、數據庫、緩存等地方。可是很顯然PageProcessor或者實體類主要負責的是頁面元素的抽取工做,所以更好的處理方式是在另外一個地方單獨作數據的持久化。這個地方也就是——Pipeline

爲了實現數據的持久化,咱們一般須要實現Pipeline 或者PageModelPipeline接口。普通爬蟲使用前一個接口,基於註解的爬蟲則使用後一個接口

package net.wanho.wanhosite;
import java.util.List;

import us.codecraft.webmagic.Site;
import us.codecraft.webmagic.Task;
import us.codecraft.webmagic.model.OOSpider;
import us.codecraft.webmagic.pipeline.PageModelPipeline;

/**
 * 自定義Pipeline以實現數據的保存
 *
 */
public class WanhoPipeline2 implements PageModelPipeline<ArticleExtra> {

    public void process(ArticleExtra articleExtra, Task task) {
       List<String> titleLst = articleExtra.getTitle();
       List<String> contentLst = articleExtra.getContent();
       for(int i=0;i<titleLst.size();i++){
           System.out.println("title:"+titleLst.get(i));
           System.out.println("content:"+contentLst.get(i));
       }

    }

    public static void main(String[] args) {
        Site site = Site.me().setTimeOut(10000).setRetryTimes(3).setSleepTime(1000).setCharset("UTF-8");
        OOSpider.create(site,new WanhoPipeline2 (), ArticleExtra.class)
        .addUrl("http://www.wanho.net/a/jyxb")
        .thread(5)
        .run();
    }

}

6.3.3. 爬蟲的啓動:

基於註解的爬蟲,其啓動類就不是Spider了,而是OOSpider類了,固然兩者的使用方式相似。示例代碼以下:

public static void main(String[] args) {
    Site site = Site.me().setTimeOut(10000).setRetryTimes(3).setSleepTime(1000).setCharset("UTF-8");
    OOSpider.create(site,new WanhoPipeline2 (), ArticleExtra.class)
        .addUrl("http://www.wanho.net/a/jyxb")
        .thread(5)
        .run();
}

7. Jsoup介紹

jsoup 是一款 Java 的HTML 解析器,可直接解析某個URL地址、HTML文本內容。它提供了一套很是省力的API,可經過DOM,CSS以及相似於JQuery的操做方法來取出和操做數據

Jsoup是Java世界的一款HTML解析工具,它支持用CSS Selector方式選擇DOM元素,也可過濾HTML文本,防止XSS×××。

下載Jsoup<http://jsoup.org/download>;

查看官方提供的手冊:<http://jsoup.org/cookbook/>;

7.1. 概述

Jsoup是Java世界用做html解析和過濾的不二之選。支持將html解析爲DOM樹、支持CSS Selector形式選擇、支持html過濾,自己還附帶了一個Http下載器。

Jsoup的代碼至關簡潔,Jsoup總共53個類,且沒有任何第三方包的依賴,對比最終發行包9.8M的SAXON,實在算得上是短小精悍了。

jsoup
    ├── examples #樣例,包括一個將html轉爲純文本和一個抽取全部連接地址的例子。    
    ├── helper #一些工具類,包括讀取數據、處理鏈接以及字符串轉換的工具
    ├── nodes #DOM節點定義
    ├── parser #解析html並轉換爲DOM樹
    ├── safety #安全相關,包括白名單及html過濾
    └── select #選擇器,支持CSS Selector以及NodeVisitor格式的遍歷

7.2. 功能

  • 從一個URL,文件或字符串中解析HTML;
  • 使用DOM或CSS選擇器來查找、取出數據;
  • 可操做HTML元素、屬性、文本;

7.3. 使用

Jsoup的入口是Jsoup類。examples包裏提供了兩個例子,解析html後,分別用CSS Selector以及NodeVisitor來操做Dom元素。

這裏用ListLinks裏的例子來講明如何調用Jsoup:

package net.wanho.blog;
import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

/**
 * 
 * 使用JSoup 解析網頁,語法使用 JS,css,Jquery 選擇器語法,方便易懂
 * Jsoup教程網:http://www.open-open.com/jsoup/
 * 
 */
public class JsoupDemo {

    public static void main(String[] args) throws IOException {
        String url = "https://www.oschina.net/news";
        Document document = Jsoup.connect(url)
                .userAgent("Mozilla/5.0 (Windows NT 6.1; rv:30.0) Gecko/20100101 Firefox/30.0").get();
        Elements elements = document.select("#all-news div ");

        Elements es = document.select("#all-news div div ");
        for (Element e : es) {
            System.out.println(e.select("a.title>span").text());
            System.out.println(e.select("div").text());
            System.out.println("-----------");
        }
        System.out.println(elements.size());
    }
}

Jsoup使用了本身的一套DOM代碼體系,這裏的Elements、Element等雖然名字和概念都與Java XML

APIorg.w3c.dom相似,但並無代碼層面的關係。就是說你想用XML的一套API來操做Jsoup的結果是辦不到的,

可是正由於如此,才使得Jsoup能夠拋棄xml裏一些繁瑣的API,使得代碼更加簡單。

8. XPath語法介紹

  • 第一種形式
    • /AAA/DDD/BBB:表示一層一層的,AAA下面DDD下面的BBB
  • 第二種形式
    • //BBB:表示和這個名稱相同,表示只要名稱是BBB,都獲得
  • 第三種形式
    • /*:全部元素
  • 第四種形式
    • BBB[1]: 表示第一個BBB元素
    • BBB[last()]:表示最後一個BBB元素
  • 第五種形式
    • //BBB[@id]:表示只要BBB元素上面有id屬性,都獲得
  • 第六種形式
    • //BBB[@id='b1']表示元素名稱是BBB,在BBB上面有id屬性,而且id的屬性值是b1
  • 例如:
    • /students/student[@id='1002']
    • 根students標記下student標記下屬性名爲id且屬性值爲1002的student元素
相關文章
相關標籤/搜索