首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

基於 Jsoup 實現 Java 爬蟲為 App 提供新聞數據接口

     編輯:Java 相關知識

項目背景

重構 Material Design 風格新聞 App 時,需要穩定的數據源。參考知乎日報、鳳凰新聞等公開 API 固然方便,但為了鍛鍊技術並掌握數據掌控權,決定自建爬蟲服務:抓取目標網站新聞列表與詳情頁,解析存入數據庫,再暴露 RESTful API 供 App 調用。

技術棧

  • Jsoup:Java 端 HTML 解析神器,支持 DOM、CSS 選擇器、XPath(插件)。
  • HttpURLConnection:JDK 原生網絡請求,無第三方依賴。
  • Fastjson/Gson:實體類序列化為 JSON。
  • 定時任務:@Scheduled 或 Quartz 定時抓取增量數據。

爬蟲核心流程

1. 發送 GET 請求獲取 HTML

public static String doGet(String urlStr) throws CrawlerException {
    HttpURLConnection conn = null;
    try {
        URL url = new URL(urlStr);
        conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(5000);
        conn.setRequestProperty("User-Agent", "Mozilla/5.0 (compatible; NewsBot/1.0)");
        if (conn.getResponseCode() == 200) {
            return IOUtils.toString(conn.getInputStream(), StandardCharsets.UTF_8);
        }
        throw new CrawlerException("HTTP 狀態碼: " + conn.getResponseCode());
    } catch (IOException e) {
        throw new CrawlerException("請求失敗: " + urlStr, e);
    } finally {
        if (conn != null) conn.disconnect();
    }
}

工具類:使用 IOUtils (commons-io) 或手寫流轉字符串,注意編碼處理。

2. 解析新聞詳情頁

目標頁面結構示例:

<div id="article">
  <div id="article_title"><h1><a href="...">標題內容</a></h1></div>
  <div id="article_detail">
    <span>2024-01-15</span>
    <span>來源: 新華網</span>
    <span>閱讀: <script src="/click?id=7428"></script> 477</span>
  </div>
  <div id="article_content"><p>正文內容...<img src="/uploads/1.jpg"/></p></div>
</div>

提取標題、時間、來源

Document doc = Jsoup.parse(html);
Element article = doc.getElementById("article");

// 標題
String title = article.getElementById("article_title").text();

// 詳情區:三個 span
Element detail = article.getElementById("article_detail");
Elements spans = detail.getElementsByTag("span");
String publishDate = spans.get(0).text();           // "2024-01-15"
String source = spans.get(1).text().replace("來源: ", ""); // "新華網"

處理 JS 動態渲染的閱讀數

閱讀數由 JS 請求 /click?id=7428 動態寫入,Jsoup 無法執行 JS。解決方案:直接請求該接口,正則提取數字。

String clickUrl = "https://site.com/click?id=" + newsId;
String jsContent = doGet(clickUrl); // 返回 "document.write(478)"
int readCount = Integer.parseInt(jsContent.replaceAll("\\D+", ""));

提取正文 HTML 與圖片

Element contentEl = article.getElementById("article_content");
String bodyHtml = contentEl.html(); // 保留標籤供 WebView 富文本展示

// 提取圖片 URL(相對路徑需補全域名)
Elements imgs = contentEl.getElementsByTag("img");
List imgUrls = new ArrayList<>();
for (Element img : imgs) {
    String src = img.attr("src");
    if (!src.startsWith("http")) src = BASE_URL + src;
    imgUrls.add(src);
}

3. 實體類設計

@Data
@AllArgsConstructor
@NoArgsConstructor
public class NewsArticle {
    private int id;
    private String title;
    private String publishDate;
    private String source;
    private int readCount;
    private String bodyHtml;
    private List imageUrls;
}

4. 完整抓取入口

public NewsArticle fetchArticle(int newsId) {
    String url = BASE_ARTICLE_URL + newsId + ".html";
    String html = HttpUtil.doGet(url);
    Document doc = Jsoup.parse(html);
    Element article = doc.getElementById("article");
    if (article == null) throw new CrawlerException("文章不存在: " + newsId);

    String title = article.getElementById("article_title").text();
    Element detail = article.getElementById("article_detail");
    Elements spans = detail.getElementsByTag("span");
    String date = spans.get(0).text();
    String source = spans.get(1).text().replace("來源: ", "");

    String clickHtml = HttpUtil.doGet(CLICK_API + newsId);
    int readCount = Integer.parseInt(clickHtml.replaceAll("\\D+", ""));

    Element content = article.getElementById("article_content");
    String body = content.html();
    List images = content.getElementsByTag("img").stream()
        .map(img -> resolveUrl(img.attr("src")))
        .collect(Collectors.toList());

    return new NewsArticle(newsId, title, date, source, readCount, body, images);
}

反爬蟲與工程化建議

  1. 請求頭偽裝:設置 User-Agent、Referer、Cookie。
  2. 頻率控制:Thread.sleep(random(1000, 3000)),使用令牌桶限流。
  3. IP 代理池:大規模抓取需輪換代理 IP,避免封禁。
  4. 異常重試:網絡波動、5xx 錯誤指數退避重試 3 次。
  5. 增量爬取:記錄最新抓取 ID 或時間,定時任務只抓增量。
  6. 存儲去重:數據庫唯一索引或 Redis 布隆過濾器。
  7. 監控告警:抓取成功率、耗時、數據量指標接入 Prometheus + Grafana。

法律與道德邊界

  • 遵守 robots.txt 協議。
  • 不繞過登錄、驗證碼、加密接口獲取非公開數據。
  • 爬取數據僅供內部學習/非商業用途,尊重版權與服務條款。

總結

Jsoup 以輕量、API 友好、選擇器強大成為 Java 爬蟲首選。結合對目標站點 DOM 結構的精準分析、JS 動態數據的接口逆向、工程化的容錯與限流,即可構建穩定可用的新聞數據服務。後續可擴展為分布式爬蟲框架(如 WebMagic、Crawler4j)應對更複雜場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved