Java實現爬蟲給App提供數據(Jsoup 網絡爬蟲)

需求 ##

近期基於 Material Design 重構了本身的新聞 App,數據來源是個問題。javascript

有前人分析了知乎日報、鳳凰新聞等 API,依據相應的 URL 可以獲取新聞的 JSON 數據。爲了鍛鍊寫代碼能力,筆者打算爬蟲新聞頁面,本身獲取數據構建 API。php

本文連接 http://blog.csdn.net/never_cxb/article/details/50524571 轉載請註明出處html

效果圖

下圖是原站點的頁面 前端

blog.csdn.net/never_cxb

爬蟲獲取了數據,展現到 APP 手機端java

blog.csdn.net/never_cxb

爬蟲思路

Created with Raphaël 2.1.0 開始 基於Get請求獲取URL對於的網頁Html 利用Jsoup把Html解析爲Document 利用Dom的getElementsById等方法獲取標題、公佈時間、內容等 依據標題、公佈時間、內容構建javabean給APP使用 結束

關於 App 的實現過程可以參看這幾篇文章,本文主要解說一下怎樣爬蟲數據。android

Jsoup 簡單介紹

Jsoup 是一個 Java 的開源HTML解析器,可直接解析某個URL地址、HTML文本內容。git

Jsoup主要有如下功能:github

- 從一個URL,文件或字符串中解析HTML。

- 使用DOM或CSS選擇器來查找、取出數據;

- 對HTML元素、屬性、文本進行操做;

- 清除不受信任的HTML (來防止XSS攻擊)

到官網下載相應的Jsoup依賴包 http://jsoup.org/downloadweb

爬蟲過程

Get 請求獲取網頁 HTML

新聞網頁Html的DOM樹例如如下所看到的:sql

http://blog.csdn.net/never_cxb

如下這段代碼依據指定的 url,用代碼獲取get 請求返回的 html 源碼。

public static String doGet(String urlStr) throws CommonException {
    URL url;
    String html = "";
    try {
        url = new URL(urlStr);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setRequestMethod("GET");
        connection.setConnectTimeout(5000);
        connection.setDoInput(true);
        connection.setDoOutput(true);
        if (connection.getResponseCode() == 200) {
            InputStream in = connection.getInputStream();
            html = StreamTool.inToStringByByte(in);
        } else {
            throw new CommonException("新聞服務器返回值不爲200");
        }
    } catch (Exception e) {
        e.printStackTrace();
        throw new CommonException("get請求失敗");
    }
    return html;
}

InputStream in = connection.getInputStream();將獲得輸入流轉化爲字符串是個廣泛需求,咱們將其抽象出來,寫一個工具方法。

public class StreamTool {
    public static String inToStringByByte(InputStream in) throws Exception {
        ByteArrayOutputStream outStr = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        StringBuilder content = new StringBuilder();
        while ((len = in.read(buffer)) != -1) {
            content.append(new String(buffer, 0, len, "UTF-8"));
        }
        outStr.close();
        return content.toString();
    }
}

解析 HTML 獲取標題

利用 google 瀏覽器的審查元素,找出新聞標題對於的html 代碼:

<div id="article_title">
  <h1>
    <a href="http://see.xidian.edu.cn/html/news/7428.html">
      關於舉辦《經典音樂做品讚揚與人文審美》講座的通知
    </a>
  </h1>
</div>

咱們需要從上面的 HTML 中找出id="article_title"的部分,使用 getElementById(String id) 方法

String htmlStr = HttpTool.doGet(urlStr);

// 將獲取的網頁 HTML 源碼轉化爲 Document
Document doc = Jsoup.parse(htmlStr);

Element articleEle = doc.getElementById("article");
// 標題
Element titleEle = articleEle.getElementById("article_title");
String titleStr = titleEle.text();

獲取公佈日期、信息來源

相同找出對於的 HTML 代碼

<html>
 <head></head>
 <body>
  <div id="article_detail"> 
   <span> 2015-05-28 </span> 
   <span> 來源: </span> 
   <span> 瀏覽次數: <script language="JavaScript" src="http://see.xidian.edu.cn/index.php/news/click/id/7428"> </script> 477 </span> 
  </div>
 </body>
</html>

思路也和上面類似。使用 getElementById(String id) 方法找出id="article_detail"爲Element。再利用getElementsByTag獲取span 部分。因爲一共同擁有3個<span> ... </span>,因此返回的是Elements而不是Element。

// article_detail包含了 2016-01-15 來源: 瀏覽次數:177
Element detailEle = articleEle.getElementById("article_detail");
Elements details = detailEle.getElementsByTag("span");

// 公佈時間
String dateStr = details.get(0).text();

// 新聞來源
String sourceStr = details.get(1).text();

解析瀏覽次數

假設打印出上面的details.get(2).text(),僅僅會獲得

瀏覽次數:

沒有瀏覽次數?爲何呢?

因爲瀏覽次數是JavaScript 渲染出來的。 Jsoup爬蟲可能僅僅提取HTML內容,得不到動態渲染出的數據。

解決方法有兩種

  • 在爬蟲的時候,內置一個瀏覽器內核,運行js渲染頁面後。再抓取。

    這方面相應的工具備Selenium、HtmlUnit或者PhantomJs。可以查看這篇文章 《抓取前端渲染的頁面》 http://webmagic.io/docs/zh/posts/chx-cases/js-render-page.html

  • 因此分析JS請求,找到相應數據的請求url

假設你訪問上面的 urlhttp://see.xidian.edu.cn/index.php/news/click/id/7428。會獲得如下的結果

document.write(478)

這個478就是咱們需要的瀏覽次數。咱們對上面的url作get 請求,獲得返回的字符串。利用正則找出當中的數字。

// 訪問這個新聞頁面。瀏覽次數會+1,次數是 JS 渲染的
String jsStr = HttpTool.doGet(COUNT_BASE_URL + currentPage);
int readTimes = Integer.parseInt(jsStr.replaceAll("\\D+", ""));
// 或者使用如下這個正則方法
// String readTimesStr = jsStr.replaceAll("[^0-9]", "");

解析新聞內容

筆者原本是獲取新聞內容純文字的形式,但後來發現 Android 端也可以顯示 CSS 格式,因此後來內容保留了 HTML 格式。

Element contentEle = articleEle.getElementById("article_content");
// 新聞主體內容
String contentStr = contentEle.toString();
// 假設用 text()方法。新聞主體內容的 html 標籤會丟失
// 爲了在 Android 上用 WebView 顯示 html,用toString()
// String contentStr = contentEle.text();

解析圖片 Url

注意一個網頁上大大小小的圖片很是多,爲了僅僅獲取新聞正文中的內容。咱們最好首先定位到新聞內容的Element。而後再利用getElementsByTag(「img」)篩選出圖片。

Element contentEle = articleEle.getElementById("article_content");
// 新聞主體內容
String contentStr = contentEle.toString();
// 假設用 text()方法,新聞主體內容的 html 標籤會丟失
// 爲了在 Android 上用 WebView 顯示 html,用toString()
// String contentStr = contentEle.text();

Elements images = contentEle.getElementsByTag("img");
String[] imageUrls = new String[images.size()];
for (int i = 0; i < imageUrls.length; i++) {
    imageUrls[i] = images.get(i).attr("src");
}

新聞實體類 JavaBean

上面獲取了新聞的標題、公佈日期、閱讀次數、新聞內容等等,咱們天然需要構造一個 javabean。把獲取的內容封裝進實體類中。

public class ArticleItem {

    private int index;
    private String[] imageUrls;
    private String title;
    private String publishDate;
    private String source;
    private int readTimes;
    private String body;

    public ArticleItem(int index, String[] imageUrls, String title, String publishDate, String source, int readTimes,
            String body) {
        this.index = index;
        this.imageUrls = imageUrls;
        this.title = title;
        this.publishDate = publishDate;
        this.source = source;
        this.readTimes = readTimes;
        this.body = body;
    }

    @Override
    public String toString() {
        return "ArticleItem [index=" + index + ",\n imageUrls=" + Arrays.toString(imageUrls) + ",\n title=" + title
                + ",\n publishDate=" + publishDate + ",\n source=" + source + ",\n readTimes=" + readTimes + ",\n body=" + body
                + "]";
    }


}

測試

public static ArticleItem getNewsItem(int currentPage) throws CommonException {
    // 依據後綴的數字,拼接新聞 url
    String urlStr = ARTICLE_BASE_URL + currentPage + ".html";

    String htmlStr = HttpTool.doGet(urlStr);

    Document doc = Jsoup.parse(htmlStr);

    Element articleEle = doc.getElementById("article");
    // 標題
    Element titleEle = articleEle.getElementById("article_title");
    String titleStr = titleEle.text();

    // article_detail包含了 2016-01-15 來源: 瀏覽次數:177
    Element detailEle = articleEle.getElementById("article_detail");
    Elements details = detailEle.getElementsByTag("span");

    // 公佈時間
    String dateStr = details.get(0).text();

    // 新聞來源
    String sourceStr = details.get(1).text();

    // 訪問這個新聞頁面。瀏覽次數會+1,次數是 JS 渲染的
    String jsStr = HttpTool.doGet(COUNT_BASE_URL + currentPage);
    int readTimes = Integer.parseInt(jsStr.replaceAll("\\D+", ""));
    // 或者使用如下這個正則方法
    // String readTimesStr = jsStr.replaceAll("[^0-9]", "");

    Element contentEle = articleEle.getElementById("article_content");
    // 新聞主體內容
    String contentStr = contentEle.toString();
    // 假設用 text()方法,新聞主體內容的 html 標籤會丟失
    // 爲了在 Android 上用 WebView 顯示 html。用toString()
    // String contentStr = contentEle.text();

    Elements images = contentEle.getElementsByTag("img");
    String[] imageUrls = new String[images.size()];
    for (int i = 0; i < imageUrls.length; i++) {
        imageUrls[i] = images.get(i).attr("src");
    }

    return new ArticleItem(currentPage, imageUrls, titleStr, dateStr, sourceStr, readTimes, contentStr);

}

public static void main(String[] args) throws CommonException {
    System.out.println(getNewsItem(7928));
}

輸出信息

ArticleItem [index=7928, imageUrls=[/uploads/image/20160114/20160114225911_34428.png], title=電院2014級開展「讓誠信之花開遍冬日校園」教育活動, publishDate=2016-01-14, source=來源: 電影新聞網, readTimes=200, body=<div id="article_content"> <p style="text-indent:2em;" align="justify"> <strong><span style="font-size:16px;line-height:1.5;">西電新聞網訊</span></strong><span style="font-size:16px;line-height:1.5;">&nbsp;(通信員</span><strong><span style="font-size:16px;line-height:1.5;"> 丁彤 王朱丹</span></strong><span style="font-size:16px;line-height:1.5;">...) 

展望

本文解說了怎樣實現Jsoup 網絡爬蟲。假設文章對您有幫助,感謝捐贈。

微信

近期用 Material Design 重構了本身的新聞 App,新聞數據是利用 Jsoup 實現的。

第1版爬蟲是在手機端實現的(我認可這設計很是很差,既費流量又添加client負擔),後來在新浪雲上實現了一個簡單的 JSP 。過濾了原網頁的圖片、一級欄目等,僅僅返回新聞標題、閱讀次數、新聞內容等等。

本文連接 http://blog.csdn.net/never_cxb/article/details/50524571 轉載請註明出處

後期的打算是把爬蟲這步移到新浪雲上,返回格式化的 JSON 數據給client使用。

可能的話,圖片使用七牛CDN(Content Delivery Network 內容分發網絡)。在雲上利用 Mysql 數據庫緩存新聞信息。

參考文章

相關文章
相關標籤/搜索