Jsoup在Android的使用(二)爬取簡書的文章

        這篇博客延遲很久了,因爲前段時間有很多雜事瑣事處理。這個週末,暫時沒什麼事情。把Jsoup在Android的使用總結一下。自己平時喜歡寫點小的應用,最近寫的小應用也使用到了Jsoup。上篇文章我們簡單介紹了一下Jsoup,並且知道了如何使用Jsoup與想要爬取的鏈接建立連接。今天,我將以一個實際的小項目總結一下如何使用Jsoup爬取簡書主頁的文章。學會了這個小項目後,你基本可以使用Jsoup爬取你想要的數據。

        我喜歡Android開發,因此,我會把一些PC端的技術嘗試在Android上實現。最初接觸爬蟲,是六月份從網上找到一段python爬蟲程序,簡單修改了一下,可以爬取百度貼吧的照片。後來,我想,在Android上有沒有類似的技術呢。於是,我找到了Java爬蟲庫Jsoup,並且在Android上爬取了一些數據,覺得還不錯。因此,纔想着把Jsoup在Android的使用分享一下,這樣,我們再也不用擔心我們的app沒有數據了。

一.爬取前的準備

1.明確需求

        既然我們是以一個小項目來講解和總結,那麼,我們首先得明確我們的需求。我們的小項目要實現什麼需求,最終的一個展示效果是怎樣的,我們需要先明確。在這裏,我們先看一下簡書的主頁。那麼,我們的需求就是爬取簡書主頁的文章,並且實現類似下面的一個列表:

2.如何實現需求

        首先,我們需要新建個項目,例如我的項目名稱是JsoupBlog。我們需要在Manifest文件中配置網絡訪問權限,因爲我們的應用是聯網去爬取數據的。做完上面的工作後,我們需要引入幾個關鍵的庫。

(1)Jsoup

        毫無疑問,我們今天的主角是Jsoup,因此,我們導入Jsoup:

implementation 'org.jsoup:jsoup:1.12.1'

(2)Rxjava+RxAndroid

        做android開發的同學都知道,我們不能在UI線程中做網絡請求,應該把網絡請求等耗時操作放在非UI線程。jsoup和網址建立連接的過程,實際上就是一次網絡請求,因此,我們需要放在子線程。自從使用了RxJava,我變懶了,不喜歡去新開子線程或者使用handler或者asynctask了。在這裏,我們導入RxJava和RxAndroid,輕鬆實現線程切換。

implementation "io.reactivex.rxjava2:rxjava:2.2.8"
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'

(3)Glide

        我們看文章列表,每一篇文章是有圖片的,因此,我們需要一個圖片加載庫,在這裏,我們導入Glide:

implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'

(4)RecyclerView

        要實現列表,我們當然需要一個列表控件,在這裏,我使用RecyclerView,RecyclerView是material design庫裏的控件,所以,我們需要導入design庫(注意:版本與你build.gradle自動生成的appcompat-v7版本一致即可)

implementation 'com.android.support:design:28.0.0'

二.使用Jsoup爬取

1.建立連接

        我們今天要爬的內容是簡書的文章,因此,我們先打開簡書主頁(https://www.jianshu.com)。接下來,我們按照上篇文章說過的方法,使用Jsoup與簡書建立連接:

Document document = null;
String url = "https://www.jianshu.com";
document = Jsoup.connect(url).get();

2.分析html

        通過上面的步驟,我們已經拿到了簡書首頁的html代碼。爲了更方便的查看,我們可以用chrome等可以查看源碼的瀏覽器查看源碼。我們大體瀏覽一下源碼,對比着網站的內容,找到我們要爬取的部分。看下面的兩段源碼,我們忽略置頂的官方文章,只去看用戶的博客,來,看一下源碼:

<li id="note-19243324" data-note-id="19243324" class="have-img">
    <a class="wrap-img" href="/p/7031752823e7" target="_blank">
      <img data-echo="//upload-images.jianshu.io/upload_images/3301720-db890fabf626e0ac.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/360/h/240" class="img-blur" src="//upload-images.jianshu.io/upload_images/3301720-db890fabf626e0ac.jpg?imageMogr2/auto-orient/strip|imageView2/1/w/180/h/120" alt="120" />
    </a>
  <div class="content">
    <a class="title" target="_blank" href="/p/7031752823e7">簡書一年,我找到了一份好工作</a>
    <p class="abstract">
      這是我的親身經歷,沒有虛構,雖然它聽起來不像真的。 2017年11月7日 星期二 晴 說起來很慚愧,在簡書寫了一年的文,至今,我還是原來的我,既...
    </p>
    <div class="meta">
        <span class="jsd-meta">
          <i class="iconfont ic-paid1"></i> 2139.3
        </span>
      <a class="nickname" target="_blank" href="/u/0aba71a90d02">陌上紅裙</a>
      <span><i class="iconfont ic-list-like"></i> 11163</span>
        <span><i class="iconfont ic-list-money"></i> 36</span>
    </div>
  </div>
</li>

        有html語言基礎的,很容易就能看懂。html是使用標籤一層層嵌套的,可以聯想一下Android的Manifest.xml文件,也是一層層標籤嵌套。因此,即使沒有html基礎,對照着網頁渲染後的內容,我們也大體可以解析出如下內容:

(1)最外層有個叫li的標籤,它有個id屬性,id的內容是note-53366789,我們猜測這是這篇文章的id。

(2)li裏面是一個a標籤,a標籤裏面是img的標籤,src是圖片的地址。當然,這個地址是不完整的,需要我們拼接一下。

(3)往裏一層是一個div標籤,它有個class屬性,class的名字是content,毫無疑問,它指的是博客的內容。

(4)再往裏有個a標籤,有個名字爲title的class。它還包含着一個href屬性,內容是/p/7031752823e7。看到href,我們應該很容易考慮到它是一個地址,也就是這篇博客的地址,當然,他也是不完整的,需要我們去拼接。然後,a標籤的內容是”簡書一年,我找到了一份好工作“。

(5)div還有個p標籤,是名爲abstract的class,abstract顧名思義,是摘要的意思。它的內容是“這是我的親身經歷,xxx”。

(6)再往下是另一個div標籤,它的名字是meta,裏面有兩個span標籤,其中一個的內容是2139.3,它代表的是這篇文章的鑽石數量。另一個span標籤內容是11163,代表的是這篇文章被喜歡的數量。還有a標籤,一個名字爲nickname的class,我們很容易聯想到,這是博客作者的暱稱。

3.建立Model類

        經過上面我們自己的人工初步解析,我們確認,我們可以拿到如下的有用的內容:

(1)博客的標號id

(2)博客的圖片地址img

(3)博客的標題title

(4)博客的鏈接href

(5)博客的摘要abstract

(6)博客的價值paid

(7)博客的作者nickname

(8)博客的喜歡數量like

        既然我們上面已經確認了我們可以獲取的數據,那麼,接下來,我們可以創建一個model類,我們取名爲JianShuBlog,代碼如下(由於abstract是java的關鍵字,所以我們就使用abs吧):

package com.example.tuduzhao.jsoupblog;

/**
 * Created by Tudu.Zhao
 * on 2019/9/6
 **/
public class JianShuBlog {

    private String id;
    private String title;
    private String href;
    private String abs;
    private String paid;
    private String name;
    private String like;
    private String img;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

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

    public String getHref() {
        return href;
    }

    public void setHref(String href) {
        this.href = href;
    }

    public String getAbs() {
        return abs;
    }

    public void setAbs(String abs) {
        this.abs = abs;
    }

    public String getPaid() {
        return paid;
    }

    public void setPaid(String paid) {
        this.paid = paid;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLike() {
        return like;
    }

    public void setLike(String like) {
        this.like = like;
    }

    public String getImg() {
        return img;
    }

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

4.解析html

        建立好model類後,接下來我們可以解析html了,解析出來的結果我們存到JianShuBlog,供我們接下來使用。解析的方法說難有點難,說簡單也簡單,難是因爲很多同學沒用過,不知道如何下手,大家可以去百度或者谷歌一下Jsoup的常用方法。後面我也會大體整理一下常用的一些方法。接下來,按照上面的model,看一下我們的解析過程。

        在解析之前,稍微說一下html元素的概念,html的每一個元素(elements),就是一對標籤之間的內容。html的標籤都是成對出現的,有開始有結尾,就好比我們Android的xml佈局文件,一對標籤中的內容就是一個控件。好,接下來,我們正式開始解析工作。

        通過觀察上面的博客源碼,我們可以看到,我們所需要的博客的內容,都包裹在一個li標籤中。可以看到,我們的li標籤中,有一個名爲have-img的class。接下來,我們通過如下的方法獲取所有的博客元素:

Elements input = document.getElementsByClass("have-img");

        這段代碼理解起來不難,就是獲取所有class爲“have-img”的元素。Elements繼承自ArrayList,那麼,ArrayList的常用的方法,Elements也都具有。我們可以打印log,打印出我們獲取到的blog的數量。可以看到,數量是40:

        接下來,我們遍歷所有的博客元素,依次獲取博客的標題、鏈接、摘要、價值、暱稱、喜歡數量以及圖片地址。不熟悉的同學可以跟着我後面的完整代碼和註釋來看。

(1)獲取每篇博客的id

        獲取博客的id,直接遍歷元素列表,獲取屬性名爲“data-note-id”的內容,代碼如下:

String id = input.get(i).attr("data-note-id");

(2)獲取博客的圖片

        獲取博客圖片稍微麻煩一點點。因爲,很明顯,src的內容不是標準的圖片地址,需要我們簡單處理一下。這是src的內容://upload-images.jianshu.io/upload_images/5303650-16bd31b30bff0a71.png?imageMogr2/auto-orient/strip|imageView2/1/w/180/h/120

        我們在源碼中點擊這個鏈接過去,查看完整的url如下:https://upload-images.jianshu.io/upload_images/5303650-16bd31b30bff0a71.png?imageMogr2/auto-orient/strip|imageView2/1/w/180/h/120

        對比一下,完整的url需要在前面加上https:,但是,這仍然不是一個標準的圖片鏈接地址,標準的圖片地址是以.png或者.jpg等結尾的。我們發現,鏈接中確實有.png,我們把後面的內容去掉,如下:https://upload-images.jianshu.io/upload_images/5303650-16bd31b30bff0a71.png

        好了,我們的圖片地址終於像樣了,訪問一下,可以看到,這個地址是原始圖片,這也是我們需要的內容。實現起來不難,三步走。第一步,獲取到img標籤的src屬性的內容;第二步,截掉?以後的字符串;第三步,字符串拼接,具體代碼如下:

String src = input.get(i).select("a").select("img").attr("src");
String s = src.substring(0, src.lastIndexOf("?"));
String img = "https:" + s;

(3)獲取博客的標題

        獲取每篇博客的標題,這裏分爲兩個步驟。首先遍歷元素列表(input),獲取class名爲“content”的元素。然後獲取名字爲a的標籤的內容,由於content的div下有兩個a標籤,一個是標題,一個是暱稱。因此獲取標題的代碼如下:

Elements content = input.get(i).getElementsByClass("content");
String title = content.select("a").get(0).text();

(4)獲取博客的暱稱

        接着上面獲取博客的標題,第二個a標籤的內容就是暱稱:

String name = content.select("a").get(1).text();

(5) 獲取博客的鏈接

        上面我們提起過,博客的鏈接不完整,我們需要拼接一下。以第一篇博客的href爲例,它是”/p/7031752823e7”,從源碼中跳轉到這個地址,我們查看一下瀏覽器的地址欄,發現它的完整地址是:https://www.jianshu.com/p/7031752823e7

        我們先獲取到href屬性的內容,獲取後,我們在前面拼接上“https://www.jianshu.com/”,代碼如下:

String href = "https://www.jianshu.com/" + content.select("a").get(0).attr("href");

(6)獲取博客的摘要 

        使用的方法和獲取標題的一樣,只不過獲取摘要的標籤是p,p標籤只有一個,代碼如下:

String abs = content.select("p").text();

 (7)獲取博客的價值

          博客價值在div標籤裏的第一個span標籤,因此,獲取博客價值的方法如下:

String paid = content.select("div").select("span").get(0).text();

(8)獲取博客的喜歡數量

        博客喜歡數量在div標籤裏的第二個span標籤,因此,獲取博客喜歡數量的代碼如下:

String like = content.select("div").select("span").get(1).text();

        通過上面的代碼,我們獲取到了首頁所有博客的所有的信息,最終,我們得到了一個博客的List。完整的代碼如下: 

private void connectJianShu() {
        blogList = new ArrayList<>();

        Observable.create(new ObservableOnSubscribe<List<JianShuBlog>>() {

            @Override
            public void subscribe(ObservableEmitter<List<JianShuBlog>> emitter) throws Exception {

                Document document = null;
                String url = "https://www.jianshu.com";
                document = Jsoup.connect(url).get();

                Elements input = document.getElementsByClass("have-img");
                Log.d("TTTT", "size:" + input.size());

                for (int i = 0; i < input.size(); i++) {

                    JianShuBlog blog = new JianShuBlog();

                    //獲取id:.attr()方法通過key獲取內容
                    String id = input.get(i).attr("data-note-id");

                    //獲取圖片鏈接:獲取src屬性的內容,只保留.png之前的內容,最前面拼接"https:"
                    String src = input.get(i).select("a").select("img").attr("src");
                    String s = src.substring(0, src.lastIndexOf("?"));
                    String img = "https:" + s;

                    //獲取content元素
                    Elements content = input.get(i).getElementsByClass("content");

                    //獲取標題和暱稱:.text()直接獲取標籤的內容,由於div下有兩個a標籤,第一個是title,第二個是暱稱
                    String title = content.select("a").get(0).text();
                    String name = content.select("a").get(1).text();

                    //獲取鏈接:.attr()通過key獲取內容
                    String href = "https://www.jianshu.com/" + content.select("a").get(0).attr("href");
                    //獲取摘要:與獲取標題一樣的方法
                    String abs = content.select("p").text();
                    //獲取價值和喜歡:價值和喜歡數量在div標籤的兩個span標籤,因此,第0個是價值,第1個是喜歡數量
                    String paid = content.select("div").select("span").get(0).text();
                    String like = content.select("div").select("span").get(1).text();

                    blog.setId(id);
                    blog.setHref(href);
                    blog.setImg(img);
                    blog.setAbs(abs);
                    blog.setLike(like);
                    blog.setName(name);
                    blog.setPaid(paid);
                    blog.setTitle(title);

                    blogList.add(blog);
                    emitter.onNext(blogList);
                }
            }
        }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<List<JianShuBlog>>() {

                    @Override
                    public void onSubscribe(Disposable d) {

                    }

                    @Override
                    public void onNext(List<JianShuBlog> blogs) {
                        showBlog(blogs);
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

 三.展示爬取的內容

        我們使用RecyclerView來展示解析到的博客列表,RecyclerView的使用在此不詳細展開。展示效果如下圖:

        最後,總結一下。本篇文章,以Android使用Jsoup爬取簡書主頁博客爲例,總結了Jsoup的一些常用方法,通過這個例子,如果有html的基礎,可以輕鬆爬取想爬取的數據。即使沒有html基礎,也可以根據示例代碼和註釋爬取自己想要的數據。Jsoup的功能還是比較強大的,這是源碼地址,需要的可以參考:https://github.com/hearttudu/JsoupBlog

 

 

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