之前寫過一篇用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;
}
}
兩個註意之處
- 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共勉!