本示例使用經典的 Rome 庫解析 RSS/Atom 訂閱源,依賴 JDOM 作為 XML 處理底層。
<dependencies>
<!-- Rome 1.x 版本 -->
<dependency>
<groupId>com.rometools</groupId>
<artifactId>rome</artifactId>
<version>1.18.1</version><!-- 請檢查最新版 -->
</dependency>
<!-- JDOM 2.x -->
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom2</artifactId>
<version>2.0.6.1</version>
</dependency>
</dependencies>
import com.rometools.rome.feed.synd.SyndEntry;
import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.io.SyndFeedInput;
import com.rometools.rome.io.XmlReader;
import java.net.URL;
import java.util.List;
public class RssParserDemo {
public static void main(String[] args) throws Exception {
String feedUrl = "https://rss.sina.com.cn/news/marquee/ddt.xml";
// 設置代理(如需要)
System.setProperty("http.proxyHost", "mywebcache.com");
System.setProperty("http.proxyPort", "8080");
// 解析訂閱源
SyndFeedInput input = new SyndFeedInput();
SyndFeed feed = input.build(new XmlReader(new URL(feedUrl)));
// 輸出頻道信息
System.out.println("頻道標題: " + feed.getTitle());
System.out.println("頻道鏈接: " + feed.getLink());
System.out.println("頻道描述: " + feed.getDescription());
System.out.println("發布語言: " + feed.getLanguage());
System.out.println("最後更新: " + feed.getPublishedDate());
System.out.println("文章數量: " + feed.getEntries().size());
// 遍歷文章
List entries = feed.getEntries();
for (int i = 0; i < entries.size(); i++) {
SyndEntry entry = entries.get(i);
System.out.println("\n--- 第 " + (i+1) + " 條 ---");
System.out.println("標題: " + entry.getTitle());
System.out.println("鏈接: " + entry.getLink());
System.out.println("發布時間: " + entry.getPublishedDate());
System.out.println("作者: " + entry.getAuthor());
System.out.println("摘要: " + entry.getDescription().getValue());
// 獲取分類/標籤
entry.getCategories().forEach(cat ->
System.out.println("分類: " + cat.getName()));
}
}
}
import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.io.FeedException;
import com.rometools.rome.io.SyndFeedInput;
import com.rometools.rome.io.XmlReader;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.InputStream;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
public class AdvancedRssParser {
private static final OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.followRedirects(true)
.build();
public static SyndFeed parseFeed(String urlString) throws Exception {
Request request = new Request.Builder()
.url(urlString)
.header("User-Agent", "Mozilla/5.0 (compatible; RSSReader/1.0)")
.build();
try (Response response = HTTP_CLIENT.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new RuntimeException("HTTP " + response.code() + ": " + response.message());
}
String contentType = response.header("Content-Type", "");
if (!contentType.contains("xml") && !contentType.contains("rss") && !contentType.contains("atom")) {
System.err.println("警告: Content-Type 可能非訂閱源格式: " + contentType);
}
try (InputStream is = response.body().byteStream()) {
// XmlReader 自動檢測編碼
return new SyndFeedInput().build(new XmlReader(is));
}
} catch (FeedException e) {
throw new RuntimeException("訂閱源格式解析失敗: " + e.getMessage(), e);
}
}
public static void main(String[] args) {
try {
SyndFeed feed = parseFeed("https://rss.sina.com.cn/news/marquee/ddt.xml");
System.out.println("解析成功: " + feed.getTitle() + " (" + feed.getEntries().size() + " 條文章)");
} catch (Exception e) {
e.printStackTrace();
}
}
}
<%@ page import="com.rometools.rome.feed.synd.*, com.rometools.rome.io.*, java.net.*, java.util.*" %>
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<head><title>新浪新聞 RSS 閱讀器</title></head>
<body>
<h1><%= feed.getTitle() %></h1>
<p>更新時間: <%= feed.getPublishedDate() %></p>
<table border="1" cellpadding="5" width="100%">
<tr><th>序號</th><th>標題</th><th>發布時間</th><th>摘要</th></tr>
<%
List<SyndEntry> entries = feed.getEntries();
for (int i = 0; i < entries.size(); i++) {
SyndEntry entry = entries.get(i);
%>
<tr>
<td><%= i + 1 %></td>
<td><a href="<%= entry.getLink() %>" target="_blank"><%= entry.getTitle() %></a></td>
<td><%= entry.getPublishedDate() %></td>
<td><%= entry.getDescription() != null ? entry.getDescription().getValue() : "" %></td>
</tr>
<% } %>
</table>
</body>
</html>
Rome 的 XmlReader 會自動從 XML 聲明、HTTP 響應頭、BOM 標記檢測編碼。若仍有亂碼,可強制指定:
// 強制 UTF-8
XmlReader reader = new XmlReader(new URL(feedUrl), "UTF-8");
// 簡單內存緩存示例
private static final Map FEED_CACHE = new ConcurrentHashMap<>();
public static SyndFeed getFeedWithCache(String url, long ttlMinutes) {
CacheEntry cached = FEED_CACHE.get(url);
if (cached != null && System.currentTimeMillis() - cached.timestamp < ttlMinutes * 60 * 1000) {
return cached.feed;
}
SyndFeed feed = parseFeed(url);
FEED_CACHE.put(url, new CacheEntry(feed, System.currentTimeMillis()));
return feed;
}
static class CacheEntry {
final SyndFeed feed;
final long timestamp;
CacheEntry(SyndFeed feed, long timestamp) {
this.feed = feed; this.timestamp = timestamp;
}
}
ExecutorService executor = Executors.newFixedThreadPool(5);
List> futures = urls.stream()
.map(url -> CompletableFuture.supplyAsync(() -> {
try { return parseFeed(url); }
catch (Exception e) { throw new CompletionException(e); }
}, executor))
.collect(Collectors.toList());
// 等待所有完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
futures.forEach(f -> System.out.println(f.join().getTitle()));
| 類/接口 | 用途 |
|---|---|
| SyndFeed | 統一的訂閱源模型(兼容 RSS 0.9x/1.0/2.0, Atom 0.3/1.0) |
| SyndEntry | 單篇文章/條目 |
| SyndContent | 內容模塊(摘要、全文),含 type(text/html) 與 value |
| SyndCategory | 分類/標籤 |
| SyndPerson | 作者/貢獻者信息 |
| SyndFeedInput | 解析器入口 |
| WireFeedInput | 底層特定格式解析器(不建議直接使用) |
Rome 庫提供了統一的高層 API 屏蔽了 RSS/Atom 格式差異,配合現代 HTTP 客戶端(OkHttp)與緩存策略,可構建健壯的訂閱源聚合應用。關鍵點:異常處理、編碼自動檢測、緩存降低源站壓力、並發抓取提升效率。