作者:hwj3747
轉載請註明

簡介

在看動漫追番的時候,發現每一季度的新番都是被優酷,愛奇藝,嗶哩嗶哩,PPTV等各大視頻廠商買了版權,導致我在手機上要裝各種軟件,並且很多番更是直接被廣電禁掉了,很煩。於是乎,我找到了一個山寨的網站:風車動漫,裏面資源倒是挺多,(當然,廣告彈窗什麼的也很多)可惜沒有APP端。剛好最近學習了爬蟲技術,於是我就想,能不能用爬蟲技術幫他搞一個APP端呢?說干就干,剛好好久沒寫代碼了,就當是練練手,於是我製作了一個簡易版的APP,不會設計界面,界面有點丑,並且還是有很多問題沒解決就是了。
目前完成了新番展示頁面,即展示周一到周日每日新番表,番劇詳情頁面,播放頁面效果如下:


ezgif-2-b3fd223a68.gif

基本功能算是實現了,但是還有很多問題,能力有限,還沒解決。

前期準備

  • 技術準備:需要掌握Android開發技術,以及一點點的前端HTML,CSS,JS技術。
  • 基本框架就是我以前寫過的[Android MVP+Retrofit+dagger2+RxAndroid框架整合])(其實這麼小的項目,根本不需要這麼重的框架,但是拿過來練手,熟悉下框架還是不錯的)
  • jsoup:好像Java做爬蟲都是用的這個包。
  • contextmenu:就是右邊周一到周日的選擇菜單,看他動效還不錯就拿過來用了,詳細使用方法點進去GitHub就看得到了。
  • glide :這個沒什麼好說的,就是加載圖片的庫了。
  • 谷歌瀏覽器 :因為要做爬蟲,所以分析前端的HTML代碼的工具也要有,這裏推薦Chrome瀏覽器,好用!
  • 注意,本文主要講述爬蟲方面如何爬數據,至於Android端的實現都只是一些基本的頁面,所以就不一一贅述了。

番劇列表頁面

首先進入風車動漫這個網站,找到這個地方,我們只需要獲取周一到周日的番劇列表就行了。


5.PNG

然後用谷歌瀏覽器F12查看源代碼,得到:


6.PNG

我們發現它的結構是這樣的:最上層一個div標籤 class為tists,包含7個ul標籤,每個u標籤l包含若干個li標籤,這個li標籤裏面就是每個番劇里的信息了,只包含番劇名,番劇鏈接以及當前第幾話的信息。
知道這些后,我們就可以用爬蟲來獲取這些數據了。
首先我們建立一個用來存放番劇簡要信息的實體類:

public class BangumiEntity {
    String title;//番劇標題
    String number;//當前是第幾話
    String url;//番劇的鏈接
}

然後初始化,用Jsoup獲取網站連接:

            String url = "http://www.fengchedm.com/";
            Connection conn = Jsoup.connect(url);
            // 修改http包中的header,偽裝成瀏覽器進行抓取
            conn.header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:32.0) Gecko/    20100101 Firefox/32.0");
            Document doc = null;
            try {
                doc = conn.get();
            } catch (IOException e) {
                e.printStackTrace();
            }

先獲取最上層class為tists的這個div標籤

Elements noteList = doc.select("div.tists");

然後獲取其下的ul標籤列表

 Elements ul = noteList.select("ul");

然後用同樣的方法遍歷ul標籤下的li標籤,獲取其中的數據,因為這裏只有兩個a標籤,第一個是第幾話,第二個是番劇名,所以我就用first和last獲取了,沒在用數組了。.而attr方法可以用來獲取標籤內的某個屬性,比如這裏的a標籤里的href屬性,並且加上abs:可以取得到完整的路徑,因為很多網站寫路徑的時候都是用的相對路徑,最後,用二維數組保存這個信息:

ArrayList<ArrayList<BangumiEntity>> arrayList=new ArrayList<ArrayList<BangumiEntity>>();
for (Element ulElement : ul) {
                Elements li=ulElement.select("li");
                ArrayList<BangumiEntity> bangumiEntities=new ArrayList<BangumiEntity>();
                for (Element liElement : li) {
                    BangumiEntity bangumiEntity=new BangumiEntity();
                    bangumiEntity.setNumber(liElement.select("a").first().text());
                    bangumiEntity.setTitle(liElement.select("a").last().text());
                    bangumiEntity.setUrl(liElement.select("a").last().attr("abs:href"));
                    bangumiEntities.add(bangumiEntity);
                }
                arrayList.add(bangumiEntities);
            }

接下來要做的就是把這個arrayList的數據展示到頁面上了,頁面我是用的RecycleView+CarView實現,具體見源碼。

番劇詳情頁面(1)

在前面的頁面上,我們點擊某一番劇進入頁面,如下:


1.PNG

用F12查看源代碼,得到這一部分的HTML代碼:


2.PNG

接下來分析一下,這段HTML的結構是這樣的:

  • 番劇名:class為spay的div標籤,裏面的a標籤
  • 封面鏈接:class為tpic l的div標籤下的img標籤
  • 作者,狀態等詳細信息: class為alex的div標籤下的span標籤數組
    同樣先新建一個實體對象用來保存這些信息:
    public class BangumiInfoEntity {
      String name;//名字
      String cover;//封面
      String all;//全集
      String autor;//作者
      String type;//類型
      String state;//狀態
      String version;//版本
    }

    用前面得到的URL訪問這個頁面:

    getItemInfo(String url){
    Connection conn = Jsoup.connect(url);
              // 修改http包中的header,偽裝成瀏覽器進行抓取
              conn.header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:32.0) Gecko/    20100101 Firefox/32.0");
              Document doc = null;
              try {
                  doc = conn.get();
              } catch (IOException e) {
                  e.printStackTrace();
              }
    }

    然後就可以用爬蟲來解析的這段代碼了,還是和上面一樣,先是獲取spay div下的a標籤得到番劇名,然後獲取tpic l div(注意:這裏兩個class用.鏈接)下的img標籤用attr方法獲取src屬性得到封面圖片鏈接最後獲取alex div 下的span數組,遍歷得到番劇的詳情信息。

    BangumiInfoEntity bangumiInfoEntity =new BangumiInfoEntity();
    bangumiInfoEntity.setName(doc.select("div.spay").select("a").text());
    bangumiInfoEntity.setCover(doc.select("div.tpic.l").select("img").attr("src"));
    Elements noteList = doc.select("div.alex").select("span");
    bangumiInfoEntity.setAll(noteList.get(0).text());
    bangumiInfoEntity.setState(noteList.get(1).text());
    bangumiInfoEntity.setAutor(noteList.get(2).text());
    bangumiInfoEntity.setVersion(noteList.get(3).text());
    bangumiInfoEntity.setType(noteList.get(4).text());

    番劇詳情頁面(2)

    我們已經獲取到了番劇的詳細信息,接下來,我們要獲取的就是番劇下的資源信息了,頁面如下:


3.PNG

用F12查看源代碼,得到這一部分的HTML代碼:


4.PNG

接下來分析一下,這段HTML的結構是這樣的:所有的資源都是在tabs的div下,在其下面,資源來源在menu0的ul標籤下,資源的集數在main0的div標籤下,一一對應。並且,main0的div下用若干的li標籤,每個li標籤用a標籤包裹每集的集數以及鏈接。
新建一個實體類,用來保存集數以及鏈接的信息:

public class BangumiEpisodeEntity {
    String num;//第幾話
    String url;//地址
}

同樣的,用番劇列表頁面得到的鏈接訪問這個頁面:

getItemList(String url){
Connection conn = Jsoup.connect(url);
          // 修改http包中的header,偽裝成瀏覽器進行抓取
          conn.header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:32.0) Gecko/    20100101 Firefox/32.0");
          Document doc = null;
          try {
              doc = conn.get();
          } catch (IOException e) {
              e.printStackTrace();
          }
}

最後,用一個title的一維數組保存資源來源信息,用一個child的二維數組保存資源每一集的信息。先獲取class為menu0下的ul標籤數組,遍歷將裏面class為on的li標籤存入title數組。然後獲取class為main0下的div數組,再遍歷下面的li標籤,分別獲取li標籤下的a標籤的text以及href屬性,得到劇集名,以及劇集鏈接。

ArrayList<String> title=new ArrayList();
ArrayList<ArrayList<BangumiEpisodeEntity>> child=new ArrayList<ArrayList<BangumiEpisodeEntity>>();
            Elements noteList = doc.select("div.tabs");
            Elements ul = noteList.select("ul.menu0");
            Elements div = noteList.select("div.main0");
            for (Element ulElement : ul) {
                title.add(ulElement.select("li.on").text());
            }
            for(Element divElement:div){
                Elements li=divElement.select("li");
                ArrayList<BangumiEpisodeEntity> bangumiEpisodeEntities=new ArrayList<BangumiEpisodeEntity>();
                for (Element liElement : li) {
                    BangumiEpisodeEntity bangumiEpisodeEntity=new BangumiEpisodeEntity();
                    bangumiEpisodeEntity.setNum(liElement.select("a").text());
                    bangumiEpisodeEntity.setUrl(liElement.select("a").attr("abs:href"));
                    bangumiEpisodeEntities.add(bangumiEpisodeEntity);
                }
                child.add(bangumiEpisodeEntities);
            }

番劇詳情頁面的UI部分,上半部分用的是glide加載圖片,下半部分用的是ExpandableListView展示各個資源來源下的番劇信息。

播放頁面

這個部分就比較頭大了,我原本的想法是獲取視頻的真實地址,然後直接用播放器播放,豈不是美滋滋。然而,我仔細研究了一下他的html頁面,發現他視頻播放時這樣做的:


5.PNG

就是說他的視頻播放使用js代碼動態注入的,然後用flash播放的,對於我這個對JS只了解皮毛的來說研究不透。對於如何獲取視頻的真實地址,希望有大佬能講一下這方面的思路。
最後我就只能用webview直接加載視頻播放頁面了,但是新的問題就又來了,用webview倒是能加載,但是因為是山寨網站,網站下面一堆廣告,看起來很不爽。我就又研究了一下,他的廣告加載方式。。結果廣告也是用js動態注入的,看不明白。於是我就用了一個很挫的方法解決了這個廣告問題,因為廣告都是在頁面的上方,所以我只保留播放器上面的div不就得了。具體實現是這樣的

WebView wv;
wv.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageFinished(WebView view, String url) {
                                view.loadUrl("javascript:function setTop(){var x=document.getElementsByTagName(\"div\");" +
                        "for (var i=8;i<x.length;i++){x[i].style.display=\"none\";}}setTop();");
            }
        });
wv.setWebChromeClient(new WebChromeClient() {
            @Override
            public void onProgressChanged(WebView view, int newProgress) {
                view.loadUrl("javascript:function setTop(){var x=document.getElementsByTagName(\"div\");" +
                        "for (var i=8;i<x.length;i++){x[i].style.display=\"none\";}}setTop();");
            }
        });

在webview加載過程和加載完成后注入js代碼,查找所有div標籤,然後把播放器一下所有的div標籤都屏蔽掉。

最後附上項目github地址:github