1. 程式人生 > >網絡爬蟲技術Jsoup——爬到一切你想要的(轉)

網絡爬蟲技術Jsoup——爬到一切你想要的(轉)

append nload ntp 信任 can 網絡爬蟲 ets bst contain

轉自:http://blog.csdn.net/ccg_201216323/article/details/53576654

本文由我的微信公眾號(bruce常)原創首發,
並同步發表到csdn博客,歡迎轉載,2016年12月11日。

概述:

本周五,接到一個任務,要使用爬蟲技術來獲取某點評網站裏面關於健身場館的數據,之前從未接觸過爬蟲技術,於是就從網上搜了一點學習資料,本篇文章就記錄爬蟲技術Jsoup技術,爬蟲技術聽名稱很牛叉,其實沒什麽難點,慢慢的用心學習就會了。

Jsoup介紹:

Jsoup 是一個 Java 的開源HTML解析器,可直接解析某個URL地址、HTML文本內容,Jsoup官網jar包下載地址。

Jsoup主要有以下功能:
1. 從一個URL,文件或字符串中解析HTML
2. 使用DOM或CSS選擇器來查找、取出數據
3. 對HTML元素、屬性、文本進行操作
4. 清除不受信任的HTML (來防止XSS攻擊)

使用Jsoup爬蟲技術你需要的能力有:

  1. 我們是用安卓開發的,首先肯定要有一定的安卓開發能力,會寫簡單的頁面。
  2. Jsoup中用到了Javascript語言,沒有此語言能力在獲取數據的時候就比較吃力,這是此爬蟲技術的重中之重。
  3. 查閱文檔與解決問題的能力和技巧(有點廢話)

上面三條中對於一個安卓開發者來說,最難的就是熟練使用Javascript語言,小編就遇到了這個問題,小編還有一定的javascript基礎,系統的學習過此語言,但是在使用中還是很吃力的,問同學、問朋友、問同事,最後還是靠自己來獲取自己想要的數據。

爬蟲技術沒那麽難,思路就是這麽的簡單

  1. 得到自己想要爬取數據的url.
  2. 通過Jsoup的jar包中的方法將Html解析成Document,
  3. 使用Document中的一些列get、first、children等方法獲取自己想要的數據,如圖片地址、名稱、時間。
  4. 將得到的數據封裝成自己的實體類。
  5. 將實體中的數據在頁面加載出來。

實戰,獲取**點評網站中的場館數據:

先奉上效果圖,沒有圖不說話:

技術分享

這就是今天要實現的效果,左邊圖片是場館的logo,右邊上方是場館的名稱,下邊是場館的地址信息,點擊進去可以根據超鏈接地址跳轉新的頁面,頁面的Url地址小編已經拿到,但可能是因為重定向的問題,webview沒有加載出來,有興趣的可以輸入鏈接地址來驗證。

首先:新建一個空的項目.

上面的效果,只要接觸過安卓開發的都能寫出來,所以不是本篇文章的重點,這裏就不過多說明,大家可以使用ListView或者RecyclerView來實現,我這裏用ListView。

小編這裏是為了加入側邊欄所以使用的是DrawerLayout,但後來沒有用到,所以也就沒有側邊欄的效果,不過後期如有時間會加上去的,上一頁下一頁是為了簡單的模仿瀏覽器中的操作,此效果只能顯示前9頁數據,網頁鏈接中有50頁的數據,為什麽沒有實現呢?

很簡單,因為50頁的鏈接地址不是一次性返回的,小編為了方便,只獲取了前9頁數據的url,畢竟是為了抓取數據顯示而已。

其次:主程序設計

  1. 通過網頁得到**點評健身場館的url地址是:http://www.dianping.com/search/category/2/45
  2. 抓取數據是一個耗時的操作,需要在一個線程中完成,這裏使用 new Thread(runnable).start()方式,在runnable代碼中獲取場館的logo、名稱、地址如下:
Runnable runnable = new Runnable() {
        @Override
        public void run() {
            Connection conn = Jsoup.connect(url);
            // 修改http包中的header,偽裝成瀏覽器進行抓取
            conn.header("User-Agent", userAgent);
            Document doc = null;
            try {
                doc = conn.get();
            } catch (IOException e) {
                e.printStackTrace();
            }
            //獲取場館的數據
            Element elementDiv = doc.getElementById("shop-all-list");
            Elements elementsUl = elementDiv.getElementsByTag("ul");
            Elements elements = elementsUl.first().getElementsByTag("li");
            for (Element element : elements) {
                Elements elements1 = element.children();
                String targetUrl = elements1.get(0).getElementsByTag("a").attr("href");

                String img = elements1.get(0).getElementsByTag("img").first().attr("data-src");
                if (img.contains(".jpg")) {
                    int a = img.indexOf(".jpg");
                    img = img.substring(0, a + 4);
                }

                String radiumName = elements1.get(1).child(0).getElementsByTag("h4").text();
                String address0 = elements1.get(1).child(2).getElementsByTag("a").get(1).text();

                String address1 = elements1.get(1).child(2).getElementsByClass("addr").text();

                RadiumBean radiumBean = new RadiumBean();
                radiumBean.setImg(img);
                radiumBean.setName(radiumName);
                radiumBean.setAddress(address0 + " " + address1);
                list.add(radiumBean);
            }
            // 執行完畢後給handler發送一個空消息
            Message message = new Message();
            message.arg1 = Integer.parseInt(curPage);
            handler.sendMessage(message);

        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  1. 通過Jsoup.connect()方法,根據目標地址url來得到Connection對象,
  2. 將我們的app偽裝成瀏覽器,防止人家後臺發現我們在爬取人家的數據,這需要修改修改http包中的header,來設置User-Agent,此值可以在谷歌瀏覽器中輸入“about:version”來查看,也可以訪問此地址查看。
  3. 通過Connection對象的get()方法來獲得整個頁面源代碼所在的Document
  4. 通過分析源代碼,使用Document的對象來得到我們想要的數據,上面程序中img待變場館logo的url,radiumName是小編得到的場館的名稱,address0和address1是小編得到的場館地址的信息,這裏通過組合來使用。
  5. 構造我們ListView所用到的數據
  6. 通過Handle來更新頁面信息,curPage(當前頁)稍後說明。
  1. 在得到數據後頁面加載顯示
if (!list.isEmpty()) {
            MyAdapter adapter = new MyAdapter(list, MainActivity.this);
            info_list_view.setAdapter(adapter);
        }
  • 1
  • 2
  • 3
  • 4

4.點擊跳轉到場館的詳情頁,這裏本想用Webview加載的,但是可能是網頁重定向的問題,webview也能加載出來,但一會就顯示無法連接網絡,所以場館詳情頁就顯示出了我們得到的場館詳情頁的url。

基本的抓取數據、加載數據流程就是這樣的,但是僅僅靠上面的數據還是不能完全實現我們的效果的。

完善頁面,實現上下頁翻頁功能。

  1. 頁面在爬取數據的時候顯示一個ProgressDialog來提示用戶。
ProgressDialog dialog = new ProgressDialog(this);
            dialog.setMessage("正在抓取數據...");
            dialog.setCancelable(false);
            dialog.show();
  • 1
  • 2
  • 3
  • 4

數據加載完畢,關閉此dialog。

 dialog.dismiss();
  • 1

2.ProgresDialog加載前做是否有網絡的判斷,有網的時候才顯示ProgressDialog,無網絡的時候給出提示。

 public boolean isNetworkAvailable(Activity activity) {
        Context context = activity.getApplicationContext();
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (cm == null)
            return false;
        else {   // 獲取所有NetworkInfo對象
            NetworkInfo[] networkInfo = cm.getAllNetworkInfo();
            if (networkInfo != null && networkInfo.length > 0) {
                for (int i = 0; i < networkInfo.length; i++)
                    if (networkInfo[i].getState() == NetworkInfo.State.CONNECTED)
                        return true;  // 存在可用的網絡連接
            }
        }
        return false;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

3.完善runnable,抓取當前頁碼、上一頁、下一頁的鏈接地址。

// 獲取頁數的鏈接
            if (firstLoad) {
                Elements elementsPages = doc.getElementsByClass("content-wrap");
                Elements elementsPageA = elementsPages.first().getElementsByClass("shop-wrap").first().child(1).getElementsByTag("a");
                for (int i = 0; i < elementsPageA.size() - 2; i++) {
                    Element element = elementsPageA.get(i);
                    Element element1 = element.getElementsByClass("cur").first();
                    Map<String, Object> map = new HashMap<>();
                    if (element1 != null) {
                        curPage = element1.text();
                        map.put("page", "" + (i + 1));
                        map.put("url", url);
                        mMapList.add(map);
                    } else {
                        map.put("page", "" + (i + 1));
                        map.put("url", element.attr("href"));
                        mMapList.add(map);
                    }

                }
            }
            firstLoad = false;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

因為在網頁中,第一次進入返回了前9頁和第50頁的數據,這裏只取前9頁的數據,firstLoad代表第一次加載,mMapList用來存放頁碼和頁面跳轉時候的url,對js中的代碼不明白的朋友們,要好好學學js,這裏小編就不介紹js了,至於我為什麽知道取這些字段,那是小編盯著網頁源程序代碼看了半天看出來的。

  1. 這個時候就用到了之前runnable中的Message對象中的curPage

curPage代表當前頁碼,從1開始………………在handle接收到消息後顯示此頁碼信息。

tvCurrentPage.setText("" + msg.arg1);
  • 1
  1. 模仿網頁的上一頁下一頁,我們需要處理TextView的點擊事件。

下一頁事件:

if (curPage.equals("" + (mMapList.size()))) {
                    Toast.makeText(this, "末頁", Toast.LENGTH_SHORT).show();
                } else {
                    curPage = "" + (Integer.parseInt(curPage) + 1);
                    url = "http://www.dianping.com" + mMapList.get(Integer.parseInt(curPage) - 1).get("url").toString();
                    switchOver();
                    tvCurrentPage.setText(curPage);
                }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

上一頁事件:

if (curPage.equals("1")) {
                    Toast.makeText(this, "首頁", Toast.LENGTH_SHORT).show();
                } else {
                    curPage = "" + (Integer.parseInt(curPage) - 1);

                    if (curPage.equals(1)) {
                        url = "http://www.dianping.com/search/category/2/45";
                    } else {

                        url = "http://www.dianping.com" + mMapList.get(Integer.parseInt(curPage) - 1).get("url").toString();
                    }
                    switchOver();
                    tvCurrentPage.setText(curPage);
                }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

經過小編測試,在點擊下一頁的時候沒有bug,在點擊上一頁的時候,會出現doc為null,從而奔潰的bug,小編在努力解決中,但還沒解決掉。

  1. 附上完整的runnable代碼,畢竟這是此程序的關鍵部分。
Runnable runnable = new Runnable() {
        @Override
        public void run() {
            Connection conn = Jsoup.connect(url);
            // 修改http包中的header,偽裝成瀏覽器進行抓取
            conn.header("User-Agent", userAgent);
            Document doc = null;
            try {
                doc = conn.get();
            } catch (IOException e) {
                e.printStackTrace();
            }

            // 獲取頁數的鏈接
            if (firstLoad) {
                Elements elementsPages = doc.getElementsByClass("content-wrap");
                Elements elementsPageA = elementsPages.first().getElementsByClass("shop-wrap").first().child(1).getElementsByTag("a");
                for (int i = 0; i < elementsPageA.size() - 2; i++) {
                    Element element = elementsPageA.get(i);
                    Element element1 = element.getElementsByClass("cur").first();
                    Map<String, Object> map = new HashMap<>();
                    if (element1 != null) {
                        curPage = element1.text();
                        map.put("page", "" + (i + 1));
                        map.put("url", url);
                        mMapList.add(map);
                    } else {
                        map.put("page", "" + (i + 1));
                        map.put("url", element.attr("href"));
                        mMapList.add(map);
                    }

                }
            }
            firstLoad = false;
            //獲取場館的數據
            Element elementDiv = doc.getElementById("shop-all-list");
            Elements elementsUl = elementDiv.getElementsByTag("ul");
            Elements elements = elementsUl.first().getElementsByTag("li");
            for (Element element : elements) {
                Elements elements1 = element.children();
                String targetUrl = elements1.get(0).getElementsByTag("a").attr("href");

                String img = elements1.get(0).getElementsByTag("img").first().attr("data-src");
                if (img.contains(".jpg")) {
                    int a = img.indexOf(".jpg");
                    img = img.substring(0, a + 4);
                }

                String radiumName = elements1.get(1).child(0).getElementsByTag("h4").text();
                String address0 = elements1.get(1).child(2).getElementsByTag("a").get(1).text();

                String address1 = elements1.get(1).child(2).getElementsByClass("addr").text();
//                StringBuilder stringBuilder = new StringBuilder();
//
//                if (elements1.get(2).child(0).children().size()>0){
//                    String  youhui = "";
//                    if (!"".equals(elements1.get(2).child(0).child(0).getElementsByClass("more").text())){
//                        youhui = elements1.get(2).child(0).getElementsByTag("a").get(1).attr("title");
//                    }else {
//                        youhui = elements1.get(2).child(0).getElementsByTag("a").get(1).attr("title");
//
//                    }
//
//                    stringBuilder.append(youhui+"+++");
//                }
                RadiumBean radiumBean = new RadiumBean();
                radiumBean.setTargetUrl("http://www.dianping.com" + targetUrl);
                radiumBean.setImg(img);
                radiumBean.setName(radiumName);
                radiumBean.setAddress(address0 + " " + address1);
                list.add(radiumBean);
            }
            // 執行完畢後給handler發送一個空消息
            Message message = new Message();
            message.arg1 = Integer.parseInt(curPage);
            handler.sendMessage(message);
        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79

有不明白的可以對照完整的runnable代碼來理解。

通過上面的步驟,我們已經完成了抓取、加載、上下頁切換的效果。但但請看下面。

通過小編的切身體驗,發現jsoup爬蟲獲取數據時候的幾個需要註意的地方。
1. 個人要會js,再強調一遍,不會js,上面我寫的js的程序應該會非常的迷糊,即便會的人,因為每個人寫的也不一樣,也是不好看懂的。
2. 我們在爬取數據的時候所用的class id 等字段一旦發生變化,那就得不到相應的標簽了,頁面就會發生奔潰,這一點也是致命的一點把。
3. 要想非常逼真的實現網頁中的效果,那你就要好好的看看網頁的源代碼了,網頁代碼有很大的靈活性,需要你仔細分析記錄規律。

測試程序已經上傳到了github,有需要的可以下載源程序。

下載地址:點我點我點我

網絡爬蟲技術Jsoup——爬到一切你想要的(轉)