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

PHP AJAX 異步讀取 RSS 文檔完整實作教學

     編輯:PHP 相關知識

PHP AJAX 異步讀取 RSS 文檔完整實作教學

RSS (Really Simple Syndication) 是內容聚合與分發的標準格式,廣泛應用於部落格、新聞站、播客等場景。結合 PHP 服務端解析與前端 AJAX 載入,可實現無刷新切換訂閱源、即時顯示最新文章。本文提供完整前後端代碼、解析邏輯詳解與現代化改良建議。

整體架構

  • 前端 (index.html):提供下拉選單選擇 RSS 源,fetch() 異步請求後端 API,動態渲染列表。
  • 後端 (rss.php):接收 q 參數對應源映射,使用 DOMDocument 解析 XML,輸出 HTML 片段。

前端完整代碼 (index.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>

後端解析代碼 (rss.php)

<?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>";
}

關鍵技術點解析

1. simplexml_load_file vs DOMDocument

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');

2. 快取機制(必要)

頻繁請求外部 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);

3. 錯誤處理與容錯

  • 網路超時:stream_context_create(['http'=>['timeout'=>10]])
  • 非 XML 回應:檢查 Content-Type、嘗試 libxml_use_internal_errors(true) 捕獲解析錯誤。
  • 編碼問題:RSS 可能為 GB2312、Big5,需 mb_convert_encoding 統一轉 UTF-8。

4. 安全性:SSRF 防護

後端不應直接信任用戶傳入的任意 URL,必須維護白名單映射(如本例 $feeds 陣列),防止伺服器端請求偽造 (SSRF) 攻擊內網服務。

現代化擴展建議

  • 前端框架化:使用 Vue/React/Alpine.js 管理狀態,支援多源標籤頁、關鍵字過濾、已讀標記。
  • 後端 API 化:輸出 JSON 而非 HTML 片段,前端負責渲染,支援 PWA 離線閱讀。
  • WebSub (PubSubHubbub):訂閱支援 WebSub 的源,實現真正的即時推送,無需輪詢。
  • 全文檢索:整合 MeiliSearch 或 Elasticsearch,支援歷史文章關鍵字搜尋。
  • OPML 匯入/匯出:支援標準訂閱列表格式,方便用戶遷移。

總結

PHP + AJAX 讀取 RSS 是經典的異步內容聚合案例。核心在於:後端白名單代理外部 XML、解析輸出結構化資料、前端異步渲染。加上快取、錯誤處理、編碼轉換、SSRF 防護,即可構建穩健可用的 RSS 閱讀服務。

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