JAVA爬蟲挖取CSDN博客文章


前言##

之前寫過一篇用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. (HttpClient.java:211)
    at sun.net.www.http.HttpClient.New(HttpClient.java:308)
    at sun.net.www.http.HttpClient.New(HttpClient.java:326)
    at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1202)
    at sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1138)
    at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1032)
    at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:966)
    at org.jsoup.helper.HttpConnection$Response.execute(HttpConnection.java:651)
    at org.jsoup.helper.HttpConnection$Response.execute(HttpConnection.java:628)
    at org.jsoup.helper.HttpConnection.execute(HttpConnection.java:260)
    at org.jsoup.helper.HttpConnection.get(HttpConnection.java:249)
    at com.shizongger.javaspider.Main1.main(Main1.java:32)

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共勉!


免責聲明!

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



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