這篇博客延遲很久了,因爲前段時間有很多雜事瑣事處理。這個週末,暫時沒什麼事情。把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