Java爬蟲系列三:使用Jsoup解析HTML

JAVA開發老菜鳥發表於2019-05-25

在上一篇隨筆《Java爬蟲系列二:使用HttpClient抓取頁面HTML》中介紹了怎麼使用HttpClient進行爬蟲的第一步--抓取頁面html,今天接著來看下爬蟲的第二步--解析抓取到的html。

有請第二步的主角:Jsoup粉墨登場。下面我們把舞臺交給Jsoup,讓他完成本文剩下的內容。

============華麗的分割線=============

一、Jsoup自我介紹

大家好,我是Jsoup。

我是一款Java 的HTML解析器,可直接解析某個URL地址、HTML文字內容。它提供了一套非常省力的API,可通過DOM,CSS以及類似於jQuery的操作方法來取出和運算元據,用Java寫爬蟲的同行們十之八九用過我。為什麼呢?因為我在這個方面功能強大、使用方便。不信的話,可以繼續往下看,程式碼是不會騙人的。

二、Jsoup解析html

上一篇中,HttpClient大哥已經抓取到了部落格園首頁的html,但是一堆的程式碼,不是程式設計師的人們怎麼能看懂呢?這個就需要我這個html解析專家出場了。

下面通過案例展示如何使用Jsoup進行解析,案例中將獲取部落格園首頁的標題和第一頁的部落格文章列表

 

請看程式碼(在上一篇程式碼的基礎上進行操作,如果還不知道如何使用httpclient的朋友請跳轉頁面進行閱讀):

  1. 引入依賴
    <dependency>
        <groupId>org.jsoup</groupId>
        <artifactId>jsoup</artifactId>
        <version>1.12.1</version>
    </dependency>

     

  2. 實現程式碼。實現程式碼之前首先要分析下html結構。標題是<title>不用說了,那文章列表呢?按下瀏覽器的F12,檢視頁面元素原始碼,你會發現列表是一個大的div,id="post_list",每篇文章是小的div,class="post_item"

    接下來就可以開始程式碼了,Jsoup核心程式碼如下(整體原始碼會在文章末尾給出):

    /**
                     * 下面是Jsoup展現自我的平臺
                     */
                    //6.Jsoup解析html
                    Document document = Jsoup.parse(html);
                    //像js一樣,通過標籤獲取title
                    System.out.println(document.getElementsByTag("title").first());
                    //像js一樣,通過id 獲取文章列表元素物件
                    Element postList = document.getElementById("post_list");
                    //像js一樣,通過class 獲取列表下的所有部落格
                    Elements postItems = postList.getElementsByClass("post_item");
                    //迴圈處理每篇部落格
                    for (Element postItem : postItems) {
                        //像jquery選擇器一樣,獲取文章標題元素
                        Elements titleEle = postItem.select(".post_item_body a[class='titlelnk']");
                        System.out.println("文章標題:" + titleEle.text());;
                        System.out.println("文章地址:" + titleEle.attr("href"));
                        //像jquery選擇器一樣,獲取文章作者元素
                        Elements footEle = postItem.select(".post_item_foot a[class='lightblue']");
                        System.out.println("文章作者:" + footEle.text());;
                        System.out.println("作者主頁:" + footEle.attr("href"));
                        System.out.println("*********************************");
                    }

    根據以上程式碼你會發現,我通過Jsoup.parse(String html)方法對httpclient獲取到的html內容進行解析獲取到Document,然後document可以有兩種方式獲取其子元素:像js一樣 可以通過getElementXXXX的方式 和 像jquery 選擇器一樣通過select()方法。 無論哪種方法都可以,我個人推薦用select方法處理。對於元素中的屬性,比如超連結地址,可以使用element.attr(String)方法獲取, 對於元素的文字內容通過element.text()方法獲取。

  3. 執行程式碼,檢視結果(不得不感慨部落格園的園友們真是太厲害了,從上面分析首頁html結構到Jsoup分析的程式碼執行完,這段時間首頁多了那麼多文章)

    由於新文章釋出的太快了,導致上面的截圖和這裡的輸出有些不一樣。

 

三、Jsoup的其他用法

我,Jsoup,除了可以在httpclient大哥的工作成果上發揮作用,我還能自己獨立幹活,自己抓取頁面,然後自己分析。分析的本領已經在上面展示過了,下面來展示自己抓取頁面,其實很簡單,所不同的是我直接獲取到的是document,不用再通過Jsoup.parse()方法進行解析了。

除了能直接訪問網上的資源,我還能解析本地資源:

程式碼:

public static void main(String[] args) {
        try {
            Document document = Jsoup.parse(new File("d://1.html"), "utf-8");
            System.out.println(document);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

 

四、Jsoup另一個值得一提的功能

 你肯定有過這種經歷,在你的頁面文字框中,如果輸入html元素的話,儲存後再檢視很大概率會導致頁面排版亂七八糟,如果能對這些內容進行過濾的話,就完美了。

剛好我Jsoup就能做到。

public static void main(String[] args) {
        String unsafe = "<p><a href='網址' onclick='stealCookies()'>部落格園</a></p>";
        System.out.println("unsafe: " + unsafe);
        String safe = Jsoup.clean(unsafe, Whitelist.basic());
        System.out.println("safe: " + safe);
    }

通過Jsoup.clean方法,用一個白名單進行過濾。執行結果:

unsafe: <p><a href='網址' onclick='stealCookies()'>部落格園</a></p>
safe: <p><a rel="nofollow">部落格園</a></p>

 

五、結束語

 通過以上大家相信我很強大了吧,不僅可以解析HttpClient抓取到的html元素,我自己也能抓取頁面dom,我還能load並解析本地儲存的html檔案。

此外,我還能通過一個白名單對字串進行過濾,篩掉一些不安全的字元。

最最重要的,上面所有功能的API的呼叫都比較簡單。

 

============華麗的分割線=============

碼字不易,點個贊再走唄~~

最後,附上案例中 解析部落格園首頁文章列表的完整原始碼:

package httpclient_learn;

import java.io.IOException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.HttpClientUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class HttpClientTest {
    
    public static void main(String[] args) {
        //1.生成httpclient,相當於該開啟一個瀏覽器
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = null;
        //2.建立get請求,相當於在瀏覽器位址列輸入 網址
        HttpGet request = new HttpGet("https://www.cnblogs.com/");
        //設定請求頭,將爬蟲偽裝成瀏覽器
        request.setHeader("User-Agent","Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36");
//        HttpHost proxy = new HttpHost("60.13.42.232", 9999);
//        RequestConfig config = RequestConfig.custom().setProxy(proxy).build();
//        request.setConfig(config);
        try {
            //3.執行get請求,相當於在輸入位址列後敲Enter鍵
            response = httpClient.execute(request);
            
            //4.判斷響應狀態為200,進行處理
            if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                //5.獲取響應內容
                HttpEntity httpEntity = response.getEntity();
                String html = EntityUtils.toString(httpEntity, "utf-8");
                System.out.println(html);
                
                /**
                 * 下面是Jsoup展現自我的平臺
                 */
                //6.Jsoup解析html
                Document document = Jsoup.parse(html);
                //像js一樣,通過標籤獲取title
                System.out.println(document.getElementsByTag("title").first());
                //像js一樣,通過id 獲取文章列表元素物件
                Element postList = document.getElementById("post_list");
                //像js一樣,通過class 獲取列表下的所有部落格
                Elements postItems = postList.getElementsByClass("post_item");
                //迴圈處理每篇部落格
                for (Element postItem : postItems) {
                    //像jquery選擇器一樣,獲取文章標題元素
                    Elements titleEle = postItem.select(".post_item_body a[class='titlelnk']");
                    System.out.println("文章標題:" + titleEle.text());;
                    System.out.println("文章地址:" + titleEle.attr("href"));
                    //像jquery選擇器一樣,獲取文章作者元素
                    Elements footEle = postItem.select(".post_item_foot a[class='lightblue']");
                    System.out.println("文章作者:" + footEle.text());;
                    System.out.println("作者主頁:" + footEle.attr("href"));
                    System.out.println("*********************************");
                }
                
                
            } else {
                //如果返回狀態不是200,比如404(頁面不存在)等,根據情況做處理,這裡略
                System.out.println("返回狀態不是200");
                System.out.println(EntityUtils.toString(response.getEntity(), "utf-8"));
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //6.關閉
            HttpClientUtils.closeQuietly(response);
            HttpClientUtils.closeQuietly(httpClient);
        }
    }
}
View Code

 

相關文章