FreeBook 基於 MVP 模式開發的帶快取網路爬蟲,採用最流行框架搭建,乾貨多多
用到的主流框架
-
RxJava+Retrofit2+Okhttp+RxCache 實現 API 資料請求以及快取(快取不區分 GET&POST 快取策略可根據自己要求修改)
-
RxJava+jsoup+RxCache 實現 HTMl 頁面爬蟲資料的請求以及快取 快取實現與 API 一致 不需要另寫邏輯
-
glide 載入圖片
-
LCRapidDevelop 下拉重新整理 狀態頁 RecyclerView 介面卡 RecyclerView 載入動畫 等等感興趣的自行了解 傳送門
-
bga-banner 首頁的 Banner 實現無限迴圈 還不錯 整合簡單
功能點
-
首頁 banner 以及推薦資料 根據後臺介面更新(總要有點自己可控的元素嘛 比如加個廣告什麼的 哈哈 比如說)
-
書庫類別 以及類別的 HTML 地址等資料 通過後臺介面控制 (如果哪天我覺得這個網站的資源不是很豐富 我可以很任性的直接在後臺換一個)
-
資料快取 請求 HTML 網頁再從網頁上抓取想要的資料其實相對 API 來說耗時會比較大 快取就顯得非常重要了
-
檔案下載統一管理 並且呼叫系統支援的程式開啟檔案
首先詳細講解一下 RxJava+Retrofit2+Okhttp+RxCache 的使用 五部曲
第一步:導包
compile 'io.reactivex:rxjava:1.1.8' compile 'io.reactivex:rxandroid:1.2.1' compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4' compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4' compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4' compile 'com.github.VictorAlbertos.RxCache:core:1.4.6'
第二步:新建 API 介面
/** * API 介面 * 因為使用 RxCache 作為快取策略 所以這裡不需要寫快取資訊 */ public interface MovieService { //獲取書庫分類資訊 @GET("freebook/typeconfigs.json") Observable<List<BookTypeDto>> getBookTypes(); //獲得首頁 banner 以及書籍資料 @GET("freebook/home.json") Observable<HomeDto> getHomeInfo(); //獲得搜尋標籤 @GET("freebook/search_lable.json") Observable<List<String>> getSearchLable(); }
第三步:新建快取介面(Html 爬蟲共用)
/**
* 快取 API 介面
* @LifeCache 設定快取過期時間. 如果沒有設定@LifeCache , 資料將被永久快取理除非你使用了 EvictProvider, EvictDynamicKey or EvictDynamicKeyGroup .
* EvictProvider 可以明確地清理清理所有快取資料.
* EvictDynamicKey 可以明確地清理指定的資料 DynamicKey.
* EvictDynamicKeyGroup 允許明確地清理一組特定的資料. DynamicKeyGroup.
* DynamicKey 驅逐與一個特定的鍵使用 EvictDynamicKey 相關的資料。比如分頁,排序或篩選要求
* DynamicKeyGroup。驅逐一組與 key 關聯的資料,使用 EvictDynamicKeyGroup。比如分頁,排序或篩選要求
*/
public interface CacheProviders {
//獲取書庫對應類別列表 快取時間 1 天
@LifeCache(duration = 7, timeUnit = TimeUnit.DAYS)
Observable<Reply<List<BookInfoListDto>>> getStackTypeList(Observable<List<BookInfoListDto>> oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);
//獲取書庫分類資訊快取資料 快取時間 永久
Observable<Reply<List<BookTypeDto>>> getBookTypes(Observable<List<BookTypeDto>> oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);
//獲取首頁配置資料 banner 最熱 最新 快取時間 7 天
@LifeCache(duration = 7, timeUnit = TimeUnit.DAYS)
Observable<Reply<HomeDto>> getHomeInfo(Observable<HomeDto> oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);
//獲取搜尋標籤 快取時間 7 天
@LifeCache(duration = 7, timeUnit = TimeUnit.DAYS)
Observable<Reply<List<String>>> getSearchLable(Observable<List<String>> oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);
//獲取書籍詳情 快取時間 7 天
@LifeCache(duration = 7, timeUnit = TimeUnit.DAYS)
Observable<Reply<BookInfoDto>> getBookInfo(Observable<BookInfoDto> oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);
}
第四步:新建 retrofit 抽象類
/**
*封裝一個 retrofit 整合 0kHttp3 的抽象基類
*/
public abstract class RetrofitUtils {
private static Retrofit mRetrofit;
private static OkHttpClient mOkHttpClient;
/**
* 獲取 Retrofit 物件
*
* @return
*/
protected static Retrofit getRetrofit() {
if (null == mRetrofit) {
if (null == mOkHttpClient) {
mOkHttpClient = new OkHttpClient.Builder().build();
}
//Retrofit2 後使用 build 設計模式
mRetrofit = new Retrofit.Builder()
//設定伺服器路徑
.baseUrl(Constant.API_SERVER + "/")
//新增轉化庫,預設是 Gson
.addConverterFactory(GsonConverterFactory.create())
//添加回調庫,採用 RxJava
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
//設定使用 okhttp 網路請求
.client(mOkHttpClient)
.build();
}
return mRetrofit;
}
}
第五步:新建 HttpData 類 用於統一管理請求
/*
*所有的請求資料的方法集中地
* 根據 MovieService 的定義編寫合適的方法
* 其中 observable 是獲取 API 資料
* observableCahce 獲取快取資料
* new EvictDynamicKey(false) false 使用快取 true 載入資料不使用快取
*/
public class HttpData extends RetrofitUtils {
private static File cacheDirectory = FileUtil.getcacheDirectory();
private static final CacheProviders providers = new RxCache.Builder()
.persistence(cacheDirectory)
.using(CacheProviders.class);
protected static final MovieService service = getRetrofit().create(MovieService.class);
//在訪問 HttpMethods 時建立單例
private static class SingletonHolder {
private static final HttpData INSTANCE = new HttpData();
}
//獲取單例
public static HttpData getInstance() {
return SingletonHolder.INSTANCE;
}
//獲取 app 書本類別
public void getBookTypes(Observer<List<BookTypeDto>> observer){
Observable observable=service.getBookTypes();
Observable observableCahce=providers.getBookTypes(observable,new DynamicKey("書本類別"),new EvictDynamicKey(false)).map(new HttpResultFuncCcche<List<BookTypeDto>>());
setSubscribe(observableCahce,observer);
}
//獲取 app 首頁配置資訊 banner 最新 最熱
public void getHomeInfo(Observer<HomeDto> observer){
Observable observable=service.getHomeInfo();
Observable observableCache=providers.getHomeInfo(observable,new DynamicKey("首頁配置"),new EvictDynamicKey(false)).map(new HttpResultFuncCcche<HomeDto>());
setSubscribe(observableCache,observer);
}
//獲得搜尋熱門標籤
public void getSearchLable(Observer<List<String>> observer){
Observable observable=service.getSearchLable();
Observable observableCache=providers.getSearchLable(observable,new DynamicKey("搜尋熱門標籤"), new EvictDynamicKey(false)).map(new HttpResultFuncCcche<List<String>>());
setSubscribe(observableCache,observer);
}
/**
* 插入觀察者
*
* @param observable
* @param observer
* @param <T>
*/
public static <T> void setSubscribe(Observable<T> observable, Observer<T> observer) {
observable.subscribeOn(Schedulers.io())
.subscribeOn(Schedulers.newThread())//子執行緒訪問網路
.observeOn(AndroidSchedulers.mainThread())//回撥到主執行緒
.subscribe(observer);
}
/**
* 用來統一處理 RxCacha 的結果
*/
private class HttpResultFuncCcche<T> implements Func1<Reply<T>, T> {
@Override
public T call(Reply<T> httpResult) {
return httpResult.getData();
}
}
}
RxJava+Retrofit2+Okhttp+RxCache 的搭建就是這麼簡單的五步就完成了,剩下的就是怎麼去使用了 我來舉個栗子 像這樣請求資料肯定是需要寫到 Model 裡面的
/**
* 獲得類別資料
*/
public class HomeStackFragmentModel {
public void LoadData(final OnLoadDataListListener listener){
HttpData.getInstance().getBookTypes(new Observer<List<BookTypeDto>>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
listener.onFailure(e);
}
@Override
public void onNext(List<BookTypeDto> bookTypeDtos) {
listener.onSuccess(bookTypeDtos);
}
});
}
}
想要的資料已經拿到了,故事到這裡結束了,但是新的故事又開始了,吃瓜群眾們你們準備好瓜子了嗎?
RxJava+jsoup+RxCache 實現 HTMl 頁面爬蟲資料的請求以及快取 四部曲
第一步:導包 還是熟悉的套路
compile 'org.jsoup:jsoup:1.9.2'
第二步:其實就是 RxJava+Retrofit2+Okhttp+RxCache 的第三步 新建快取介面 第三步:新建自定義 OnSubscribe 用於解析 Html 獲得自己資料
/**
* 其實這裡面的玩法還很多
* 這是 jsop 的中文文件 http://www.open-open.com/jsoup/ 再牛逼的資料都能抓取
* 其實 doc.select(".bookcover h1:eq(1)"); ()裡面的資料完全可以通過介面定義 達到完全控制的效果
* 我是懶得寫了 但是這個需求還是提一下 很 nice 的 裝逼必備啊
*/
public class BookInfoHtmlOnSubscribe<T> implements Observable.OnSubscribe<T> {
private String url;
public BookInfoHtmlOnSubscribe(String url) {
//獲取到需要解析 html 地址
this.url = url;
}
@Override
public void call(Subscriber<? super T> subscriber) {
try {
//開始瘋狂的資料抓取啦 這個我就不解釋了 大家去看看文件 http://www.open-open.com/jsoup/
Document doc = Jsoup.connect(url).get();
Elements bookIntroduction = doc.select(".con");
Elements bookname = doc.select(".bookcover h1:eq(1)");
Elements bookImageUrl = doc.select(".bookcover img");
Elements bookAuthor = doc.select(".bookcover p:eq(2)");
Elements bookType = doc.select(".bookcover p:eq(3)");
Elements bookLength = doc.select(".bookcover p:eq(4)");
Elements bookProgress = doc.select(".bookcover p:eq(5)");
Elements bookUpdateTime = doc.select(".bookcover p:eq(6)");
String[] strs=url.split("/");
String bookDownload="http://www.txt99.cc/home/down/txt/id/"+((strs[strs.length-1]));
T bookInfoDto= (T) new BookInfoDto(bookImageUrl.attr("src"),bookname.text(),bookAuthor.text(),bookType.text(),bookLength.text(),bookProgress.text(),bookUpdateTime.text(),bookDownload,bookIntroduction.html());
subscriber.onNext(bookInfoDto);
subscriber.onCompleted();
} catch (IOException e) {
throw new ApiException("ERROR:資料解析錯誤");
}
}
}
第四步:新建 HtmlData 類 和上面的非常相似 哎 就不解釋了 就是這麼 666
/**
* Created by Administrator on 2016/9/14. */
public class HtmlData {
//這裡是設定一個快取地址 如果地址不存在就新建一個
private static File cacheDirectory = FileUtil.getcacheDirectory();
//新增快取提供者
private static final CacheProviders providers = new RxCache.Builder()
.persistence(cacheDirectory)
.using(CacheProviders.class);
//在訪問 HttpMethods 時建立單例
private static class SingletonHolder {
private static final HtmlData INSTANCE = new HtmlData();
}
//獲取單例
public static HtmlData getInstance() {
return SingletonHolder.INSTANCE;
}
//根據型別獲取書籍集合
public void getStackTypeHtml(BookTypeDto bookType, int pageIndex, Observer<List<BookInfoListDto>> observer) {
Observable observable = Observable.create(new StackTypeHtmlOnSubscribe<BookInfoListDto>(bookType.getBookTypeUrl().replace("{Page}",pageIndex+"")));
Observable observableCache=providers.getStackTypeList(observable,new DynamicKey("getStackTypeHtml"+bookType.getBookTypeName()+pageIndex), new EvictDynamicKey(false)).map(new HttpResultFuncCache<List<BookInfoListDto>>());
setSubscribe(observableCache, observer);
}
//根據關鍵字搜尋書籍
public void getSearchList(String key,Observer<List<BookInfoListDto>> observer){
try {
//中文記得轉碼 不然會亂碼 搜尋不出想要的效果
key = URLEncoder.encode(key, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
Observable observable=Observable.create(new StackTypeHtmlOnSubscribe<BookInfoListDto>(Constant.API_SEARCH.replace("{Key}",key)));
Observable observableCache=providers.getStackTypeList(observable,new DynamicKey("getSearchList&"+key), new EvictDynamicKey(false)).map(new HttpResultFuncCache<List<BookInfoListDto>>());
setSubscribe(observableCache, observer);
}
//獲得書籍的詳情
public void getBookInfo(String bookUrl,String bookName, Observer<BookInfoDto> observer){
Observable observable=Observable.create(new BookInfoHtmlOnSubscribe<BookInfoDto>(bookUrl));
Observable observableCache=providers.getBookInfo(observable,new DynamicKey(bookName),new EvictDynamicKey(false)).map(new HttpResultFuncCache<BookInfoDto>());
setSubscribe(observableCache, observer);
}
/**
* 插入觀察者
*
* @param observable
* @param observer
* @param <T>
*/
public static <T> void setSubscribe(Observable<T> observable, Observer<T> observer) {
observable.subscribeOn(Schedulers.io())
.subscribeOn(Schedulers.newThread())//子執行緒訪問網路
.observeOn(AndroidSchedulers.mainThread())//回撥到主執行緒
.subscribe(observer);
}
private class HttpResultFuncCache<T> implements Func1<Reply<T>, T> {
@Override
public T call(Reply<T> httpResult) {
return httpResult.getData();
}
}
}
使用方式和 RxJava+Retrofit2+Okhttp+RxCache 一致 我也舉個栗子好了
/**
* 獲取書籍詳情資料
*/
public class BookInfoModel {
public void loadData(String bookUrl,String bookName, final OnLoadDataListListener listener){
HtmlData.getInstance().getBookInfo(bookUrl,bookName, new Observer<BookInfoDto>() {
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
listener.onFailure(e);
}
@Override
public void onNext(BookInfoDto bookInfoDto) {
listener.onSuccess(bookInfoDto);
}
});
}
}
好了是不是覺得特別簡單 當然我只是帶你們入門 真正想玩轉想拓展 還是要好好的多瞭解瞭解 有吃瓜群眾要問了