JAVA爬蟲挖取CSDN博客文章

来源:http://www.cnblogs.com/shizongger/archive/2017/02/10/6385052.html
-Advertisement-
Play Games

之前寫過一篇用jsoup爬取csdn博客的文章JAVA爬蟲挖取CSDN博客文章 ,當時博主還在上一家公司實習,由於公司辦公網路需要代理才能訪問外網,那一篇的代碼邏輯與代理密切相關,可能有些不熟悉jsoup怎麼使用的朋友看了會感覺越看越糊塗,且當時以為爬取所有文章需要用到分頁,可能會誤導讀者。所以今天... ...


前言

之前寫過一篇用jsoup爬取csdn博客的文章JAVA爬蟲挖取CSDN博客文章,當時博主還在上一家公司實習,由於公司辦公網路需要代理才能訪問外網,那一篇的代碼邏輯與代理密切相關,可能有些不熟悉jsoup怎麼使用的朋友看了會感覺越看越糊塗,且當時以為爬取所有文章需要用到分頁,可能會誤導讀者。所以今天再次整理那個篇博客的思路,在沒有代理的網路的環境下實現代碼功能,如果你的也是處在代理才能訪問外網的網路,那麼參考本文最後一段的解決方案。

思路和步驟

還是以《第一行代碼--安卓》的作者為例,將他在csdn發表的博客信息都挖取出來,因為郭神是我在大學期間比較崇拜的對象之一。郭神的博客地址為郭霖博客
這裡寫圖片描述

在動手實驗之前,假設你已經基本掌握瞭如下的技能:JAVA基礎編程,簡單的正則表達式,JS或者jQuery的編程能力,此外還學過http協議的一些知識。如果你還未掌握正則表達式,可以去我的JAVA正則表達式詳解博客看看,如果你還沒有掌握jQuery的基礎知識,可以去我的jQuery實戰專欄動手實驗一番。如果上訴技能你都掌握了,那麼就只差一個jsoup了,這個哥們是幹嘛使的呢?用一句話來描述:這哥們能使Java程式像jQuery那樣的語法來操作html的Dom節點元素,jsoup也有像jQuery一樣的選擇器功能,比如getElementById,getElemementsByClass等語法與JavaScript像極了,此外jsoup還有select選擇器功能。所以,這裡只要稍微掌握jsoup語法就可以像JS操作Dom一樣的用Java來處理請求到的html網頁。jsoup的中文官方教程地址http://www.open-open.com/jsoup/

工欲善其事必先利其器。開始之前,你應該有一定的工具,比如一款熟悉的ide,用來調試和查看變數。一個web調試工具,如火狐的firebug之類的。總之,就是有一個java web程式員日常開發和調試使用的工具就差不多了。

第一步:新建一個Java Se項目。這個項目如果是一個Maven項目,那麼需要添加如下的依賴配置:

<dependency>
  <groupId>org.jsoup</groupId>
  <artifactId>jsoup</artifactId>
  <version>1.10.2</version>
</dependency>

如果這個項目不是Maven項目,那麼你需要下載jsoup-1.10.2.jar包,然後在你的項目右鍵新建一個lib目錄,把下載的jar包放進去,並把jar包添加到classpath。
這裡寫圖片描述

第二步:分析代碼編寫思路:先創建一個Conection鏈接,這個鏈接包含向CSDN伺服器發起請求的url地址,userAgent參數,timeOut參數已經Http請求類型等。接著向伺服器發送get請求Document doc = conn.get();註意:很多網路原因導致這一步出現異常,比如timeOut時間設置太短也會報錯,這裡我設置為5000毫秒。如果這一步你的程式沒有報錯,說明已經獲取了請求的html文檔。然後我們可以將html的body標簽裡面的內容賦值給遍歷Element數據類型的實例body。處理body標簽的內容jsoup正式出場,除了jsoup還會夾雜簡單的正則內容。

用firebug可以看到。首頁與博客文章相關的內容在這個div class="list_item_new"標簽下麵,而這個div下麵包含3個div,分別是:div id="article_toplist" class="list"表示置頂的文章,div id="article_list" class="list"博文列表所在的div,div id="papelist" class="pagelist"底下分頁信息所在的div。拋開置頂這個div,我們只關註文章列表的div和分頁信息div。如果你仔細的分析,那麼會發現我們關心的每篇文章而每篇文章的標簽如下div:`

這裡寫圖片描述
每篇文章占據的div,完整的html元素如下:

<div class="list_item article_item">
        <div class="article_title">   
         <span class="ico ico_type_Original"></span>


    <h1>
        <span class="link_title"><a href="/guolin_blog/article/details/51336415">
        Android提醒微技巧,你真的瞭解Dialog、Toast和Snackbar嗎?            
        </a></span>
    </h1>
</div>

        <div class="article_description">
Dialog和Toast所有人肯定都不會陌生的,這個我們平時用的實在是太多了。而Snackbar是Design Support庫中提供的新控制項,有些朋友可能已經用過了,有些朋友可能還沒去瞭解。但是你真的知道什麼時候應該使用Dialog,什麼時候應該使用Toast,什麼時候應該使用Snackbar嗎?本篇文章中我們就來學習一下這三者使用的時機,另外還會介紹一些額外的技巧...        </div>
            <div class="article_manage">
             <span class="link_postdate">2016-07-26 07:55</span>
    
   
        <span class="link_view" title="閱讀次數"><a href="/guolin_blog/article/details/51336415" title="閱讀次數">閱讀</a>(7458)</span>
        <span class="link_comments" title="評論次數"><a href="/guolin_blog/article/details/51336415#comments" title="評論次數" onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_pinglun'])">評論</a>(45)</span>

    </div>

        <div class="clear"></div>
    </div>

仔細分析一下,這個div中涵蓋了文章的簡介,閱讀次數,連接地址等等,總之,這個div才是重頭戲要獲取的數據都在這呢。
這裡寫圖片描述

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

/**
 * java爬取csdn博客的簡單的案例,如果你只爬取某個博主的首頁文章,那麼參考本程式員
 * 如果你想爬取某位博主的所有文章,請參考Main.java
 * @author shizongger
 * @date 2017/02/09
 */
public class Main1 {
    
    //需要進行爬取得博客首頁
//  private static final String URL = "http://blog.csdn.net/guolin_blog";
    private static final String URL = "http://blog.csdn.net/sinyu890807/article/list/2";
    
    public static void main(String[] args) throws IOException {
        
        //獲取url地址的http鏈接Connection
        Connection conn = Jsoup.connect(URL)    //博客首頁的url地址
                .userAgent("Mozilla/5.0 (Windows NT 6.1; rv:47.0) Gecko/20100101 Firefox/47.0") //http請求的瀏覽器設置
                .timeout(5000)   //http連接時長
                .method(Connection.Method.GET);  //請求類型是get請求,http請求還是post,delete等方式
        //獲取頁面的html文檔
        Document doc = conn.get();
        Element body = doc.body();
        
        //將爬取出來的文章封裝到Artcle中,並放到ArrayList裡面去
        List<Article> resultList = new ArrayList<Article>();

        Element articleListDiv = body.getElementById("article_list");
        Elements articleList = articleListDiv.getElementsByClass("list_item");
        for(Element article : articleList){
            Article articleEntity = new Article();
            Element linkNode = (article.select("div h1 a")).get(0);         
            Element desptionNode = (article.getElementsByClass("article_description")).get(0);
            Element articleManageNode = (article.getElementsByClass("article_manage")).get(0);

            articleEntity.setAddress(linkNode.attr("href"));
            articleEntity.setTitle(linkNode.text());
            articleEntity.setDesption(desptionNode.text());
            articleEntity.setTime(articleManageNode.select("span:eq(0").text());

            resultList.add(articleEntity);
        }
        
        //遍歷輸出ArrayList裡面的爬取到的文章
        System.out.println("文章總數:" + resultList.size());
        for(Article article : resultList) {
            System.out.println("文章絕對路勁地址:http://blog.csdn.net" + article.getAddress());
        }
    }
    
}

現在可以將當前頁數的文章挖掘出來了,但是郭神的技術文章不止一頁啊,還要進一步分頁挖掘。以前我是想Java的分頁思路是怎麼寫的,我們可以逆著它的分頁思路來。但是現在感覺與它怎麼分頁無關,但是如果你瞭解Java分頁那麼更好的理解接下來怎麼做。

要想爬取它的所有文章,可以對他的博客每一個頁面分別進行請求。
首頁地址可以是:

http://blog.csdn.net/guolin_blog

也可以是:

http://blog.csdn.net/guolin_blog/article/list/1

那麼第二頁以後的url地址如下:

http://blog.csdn.net/guolin_blog/article/list/index

index表示請求的頁數。

現在的任務就是來抓取總頁數了。來來來,我們用firebug看一看。
這裡寫圖片描述

<div id="papelist" class="pagelist">
<span> 100條  共7頁</span><a href="/sinyu890807/article/list/1">首頁</a> <a href="/sinyu890807/article/list/3">上一頁</a> <a href="/sinyu890807/article/list/1">1</a> <a href="/sinyu890807/article/list/2">2</a> <a href="/sinyu890807/article/list/3">3</a> <strong>4</strong> <a href="/sinyu890807/article/list/5">5</a> <a href="/sinyu890807/article/list/6">...</a> <a href="/sinyu890807/article/list/5">下一頁</a> <a href="/sinyu890807/article/list/7">尾頁</a> 
    </div>

可以看到總頁數位於上訴div下的第一個span標簽,幸運的是這個div有一個獨一無二的id號,而這個span與div的關係是父節點與子節點的關係,獲取圖中紅圈內字元串的代碼是"body.getElementById("papelist").select("span:eq(0)").text();"。而span標簽裡面的內容" 100條 共7頁"是漢字,空格和數字混合組成,這時候正則表達式閃亮登場。為了選取"共x頁"裡面的x的值,正則的語法關鍵代碼是:String regex = ".+共(\d+)頁";

至此,就可以將郭霖的csdn技術博客都可以獲取了。此時你只需要將得到的信息都封裝好,在需要的時候調用就行了。
完整代碼如下:
Article.java

/**
 * 文章的JavaBean.
 * date:2017-02-09
 */
public class Article {
  
    /**
     * 文章鏈接的相對地址
     */
    private String address;

    /**
     * 文章標題
     */
    private String title;

    /**
     * 文章簡介
     */
    private String desption;

    /**
     * 文章發表時間
     */
    private String time;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDesption() {
        return desption;
    }

    public void setDesption(String desption) {
        this.desption = desption;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }
}

Main.java

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.jsoup.*;
import org.jsoup.nodes.*;
import org.jsoup.select.*;

import com.shizongger.javaspider.Article;

/**
 * @author shizongger
 * @date 2017/02/09
 */
public class Main {
    
    private static final String URL = "http://blog.csdn.net/guolin_blog";

    public static void main(String[] args) throws IOException {
        Connection conn = Jsoup.connect(URL)
                .userAgent("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:50.0) Gecko/20100101 Firefox/50.0")
                .timeout(5000)
                .method(Connection.Method.GET);
        Document doc = conn.get();
        Element body = doc.body();

        //獲取總頁數
        String totalPageStr = body.getElementById("papelist").select("span:eq(0)").text();
        String regex = ".+共(\\d+)頁";
        totalPageStr = totalPageStr.replaceAll(regex, "$1");
        int totalPage = Integer.parseInt(totalPageStr);
        int pageNow = 1;

        List<Article> articleList = new ArrayList<Article>();
        for(pageNow = 1; pageNow <= totalPage; pageNow++){
            articleList.addAll(getArtitcleByPage(pageNow));
        }
        
        //遍歷輸出博主所有的文章
        for(Article article : articleList) {
            System.out.println("文章標題:" + article.getTitle());
            System.out.println("文章絕對路勁地址:http://blog.csdn.net" + article.getAddress());
            System.out.println("文章簡介:" + article.getDesption());
            System.out.println("發表時間:" + article.getTime());
        }
    }

    public static List<Article> getArtitcleByPage(int pageNow) throws IOException{

        Connection conn = Jsoup.connect(URL + "/article/list/" + pageNow)
                .userAgent("Mozilla/5.0 (Windows NT 6.1; rv:47.0) Gecko/20100101 Firefox/47.")
                .timeout(5000)
                .method(Connection.Method.GET);
        Document doc = conn.get();
        Element body = doc.body();
        List<Article> resultList = new ArrayList<Article>();

        Element articleListDiv = body.getElementById("article_list");
        Elements articleList = articleListDiv.getElementsByClass("list_item");
        for(Element article : articleList){
            Article articleEntity = new Article();
            Element linkNode = (article.select("div h1 a")).get(0);         
            Element desptionNode = (article.getElementsByClass("article_description")).get(0);
            Element articleManageNode = (article.getElementsByClass("article_manage")).get(0);

            articleEntity.setAddress(linkNode.attr("href"));
            articleEntity.setTitle(linkNode.text());
            articleEntity.setDesption(desptionNode.text());
            articleEntity.setTime(articleManageNode.select("span:eq(0").text());

            resultList.add(articleEntity);
        }
        return resultList;
    }
}

兩個註意之處

  1. Conection的timeOut不宜過短。如果把timeOut設置為20毫秒,則會報錯。
    Exception in thread "main" java.net.SocketTimeoutException: connect timed out
    at java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
    at java.net.Socket.connect(Socket.java:589)
    at sun.net.NetworkClient.doConnect(NetworkClient.java:175)
    at sun.net.www.http.HttpClient.openServer(HttpClient.java:432)
    at sun.net.www.http.HttpClient.openServer(HttpClient.java:527)
    at sun.net.www.http.HttpClient.

2.當你處於內網時,需要代理才能發送http請求,在Jsoup.connect(URL)之前,你必須設置代理。

        System.setProperty("http.maxRedirects", "50");
        System.getProperties().setProperty("proxySet", "true");
        String ip = "代理伺服器地址";
        System.getProperties().setProperty("http.proxyHost", ip);
        System.getProperties().setProperty("http.proxyPort", "代理的埠");

本文可運行案例托管在免積分下載地址,本文目的用於技術交流,望與天下Coder共勉!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1 from bs4 import BeautifulSoup 2 import requests 3 from random import choice 4 import csv 5 6 headers1 = {'User-Agent':'spider'} 7 headers2 = {'User-... ...
  • 轉換流 1、轉換流:將位元組流轉換成字元流,轉換之後就可以一個字元一個字元的往程式寫內容了,並且可以調用字元節點流的write(String s)方法,還可以在外面套用BufferedReader()和BufferedWriter,並使用它們的readLine 和 newLine方法。 2、有兩種轉換 ...
  • <?php//生成隨機數 和 時間函數//echo rand();//echo "<br>";//echo rand(0,10);//echo time();//時間戳//2017-02-10 08:46:12date_default_timezone_set("Asia/Shanghai");// ...
  • 總結一下對象的創建過程,假設有一個名為Dog的類: 1. 即使沒有顯示地使用static關鍵字,構造器實際上也是靜態的方法,因此,當首次創建類型為Dog的對象時(構造器可以看成靜態方法),或者Dog類的靜態方法/靜態域首次被訪問時,java解釋器必須查找類的路徑,以定位Dog.class文件。 2. ...
  • Redisson分散式鎖 之前的基於註解的鎖有一種鎖是基本redis的分散式鎖,鎖的實現我是基於redisson組件提供的RLock,這篇來看看redisson是如何實現鎖的。 不同版本實現鎖的機制並不相同 引用的redisson最近發佈的版本3.2.3,不同的版本可能實現鎖的機制並不相同,早期版本 ...
  • 要求: 1 #_*_coding:utf-8_*_ 2 #第一部分:sql解析 3 import os,time 4 def sql_parse(sql): 5 ''' 6 sql_parse >insert_parse,delete_parse,update_parse,select_parse ...
  • 先初始化主類中的靜態數據,如果要用其他類來定義對象,則初始化對應的其他類。 實例化對象時,先初始化定義為static的數據,再初始化定義為非static的數據,最後調用構造函數。 通過一個小程式,瞭解靜態數據是如何初始化的: 初始化順序:要執行main,必須先載入StaticInitializati ...
  • 一、新建Maven Module測試站點 \ 二、配置Application Server 1.File->Setting,打開設置面板; 2.選中Application Servers,點擊+,設置tomcat路徑,指向本機Tomcat所在目錄。如下圖所示: 三、設置Run Configurati ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...