RSS (Really Simple Syndication) 是內容聚合與分發的標準格式,廣泛應用於部落格、新聞站、播客等場景。結合 PHP 服務端解析與前端 AJAX 載入,可實現無刷新切換訂閱源、即時顯示最新文章。本文提供完整前後端代碼、解析邏輯詳解與現代化改良建議。
fetch() 異步請求後端 API,動態渲染列表。q 參數對應源映射,使用 DOMDocument 解析 XML,輸出 HTML 片段。<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<title>RSS 閱讀器 Demo</title>
<style>
body {font-family: "Microsoft JhengHei", sans-serif; max-width: 800px; margin: 2rem auto; padding: 0 1rem;}
select {padding: 0.5rem; font-size: 1rem; margin-bottom: 1rem; width: 100%; max-width: 300px;}
.feed-item {border-bottom: 1px solid #eee; padding: 1rem 0;}
.feed-item:last-child {border-bottom: none;}
.feed-title {font-size: 1.1rem; font-weight: bold; color: #2c3e50; text-decoration: none;}
.feed-title:hover {color: #3498db;}
.feed-desc {color: #555; margin-top: 0.3rem; line-height: 1.6;}
.feed-meta {font-size: 0.85rem; color: #999; margin-top: 0.3rem;}
.loading {text-align: center; color: #999; padding: 2rem;}
.error {color: #e74c3c; text-align: center; padding: 1rem;}
</style>
</head>
<body>
<h1>RSS 異步閱讀器</h1>
<label for="feed-select">選擇訂閱源:</label>
<select id="feed-select">
<option value="">-- 請選擇 --</option>
<option value="cnn">CNN Top Stories</option>
<option value="bbc">BBC World News</option>
<option value="pc">PC World</option>
</select>
<div id="output" class="loading">請選擇訂閱源開始載入...</div>
<script>
const select = document.getElementById('feed-select');
const output = document.getElementById('output');
const feeds = {
cnn: 'https://rss.cnn.com/rss/cnn_topstories.rss',
bbc: 'https://feeds.bbci.co.uk/news/world/rss.xml',
pc: 'https://www.pcworld.com/index.rss'
};
select.addEventListener('change', async () => {
const key = select.value;
if (!key) {
output.innerHTML = '請選擇訂閱源';
output.className = 'loading';
return;
}
output.innerHTML = '載入中...';
output.className = 'loading';
try {
const resp = await fetch(`rss.php?q=${key}`, {credentials: 'same-origin'});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const html = await resp.text();
output.innerHTML = html;
output.className = '';
} catch (e) {
output.innerHTML = `載入失敗:${e.message}`;
output.className = 'error';
}
});
</script>
</body>
</html>
<?php
// rss.php
header('Content-Type: text/html; charset=utf-8');
$feeds = [
'cnn' => 'https://rss.cnn.com/rss/cnn_topstories.rss',
'bbc' => 'https://feeds.bbci.co.uk/news/world/rss.xml',
'pc' => 'https://www.pcworld.com/index.rss',
];
$key = $_GET['q'] ?? '';
if (!isset($feeds[$key])) {
http_response_code(400);
exit('無效的訂閱源');
}
$url = $feeds[$key];
// 使用 libxml 解析,支援命名空間、CDATA
$xml = @simplexml_load_file($url);
if ($xml === false) {
http_response_code(502);
exit('無法解析遠端 RSS,請稍後再試');
}
// 通道資訊
$channelTitle = (string)($xml->channel->title ?? '無標題');
$channelLink = (string)($xml->channel->link ?? '#');
$channelDesc = (string)($xml->channel->description ?? '');
echo "<div class='feed-header'>";
echo "<h2><a href='{$channelLink}' target='_blank' rel='noopener'>{$channelTitle}</a></h2>";
echo "<p class='feed-meta'>{$channelDesc}</p>";
echo "</div>";
// 遍歷前 5 篇文章
$items = $xml->xpath('//channel/item');
$count = 0;
foreach ($items as $item) {
if ($count++ >= 5) break;
$title = (string)($item->title ?? '無標題');
$link = (string)($item->link ?? '#');
$desc = (string)($item->description ?? '');
$pubDate = isset($item->pubDate) ? date('Y-m-d H:i', strtotime((string)$item->pubDate)) : '';
// 移除 CDATA 標記、截斷摘要
$desc = strip_tags($desc);
$desc = mb_strimwidth($desc, 0, 200, '...');
echo "<div class='feed-item'>";
echo "<a class='feed-title' href='{$link}' target='_blank' rel='noopener'>{$title}</a>";
if ($pubDate) echo "<div class='feed-meta'>發布時間:{$pubDate}</div>";
if ($desc) echo "<div class='feed-desc'>{$desc}</div>";
echo "</div>";
}
simplexml_load_file 直接從 URL 載入並解析,程式碼簡潔。若需更精細控制(如命名空間註冊、XPath 查詢、錯誤處理),可改用 DOMDocument:
$doc = new DOMDocument();
$doc->load($url);
$xpath = new DOMXPath($doc);
$xpath->registerNamespace('atom', 'https://www.w3.org/2005/Atom');
$items = $xpath->query('//channel/item | //atom:entry');
頻繁請求外部 RSS 會造成對方伺服器負擔,且拖慢回應。建議加入本地快取:
$cacheFile = sys_get_temp_dir() . "/rss_{$key}.xml";
$cacheTtl = 300; // 5 分鐘
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < $cacheTtl)) {
$xmlContent = file_get_contents($cacheFile);
} else {
$xmlContent = file_get_contents($url);
if ($xmlContent) file_put_contents($cacheFile, $xmlContent);
}
$xml = simplexml_load_string($xmlContent);
stream_context_create(['http'=>['timeout'=>10]])Content-Type、嘗試 libxml_use_internal_errors(true) 捕獲解析錯誤。mb_convert_encoding 統一轉 UTF-8。後端不應直接信任用戶傳入的任意 URL,必須維護白名單映射(如本例 $feeds 陣列),防止伺服器端請求偽造 (SSRF) 攻擊內網服務。
PHP + AJAX 讀取 RSS 是經典的異步內容聚合案例。核心在於:後端白名單代理外部 XML、解析輸出結構化資料、前端異步渲染。加上快取、錯誤處理、編碼轉換、SSRF 防護,即可構建穩健可用的 RSS 閱讀服務。