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


轉自: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. 將實體中的數據在頁面加載出來。

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

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

image

這就是今天要實現的效果,左邊圖片是場館的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,有需要的可以下載源程序。

下載地址:點我點我點我


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM