本文目录导读:

在 PHP 中实现订阅源(RSS/Atom)功能,通常涉及两种情况:生成订阅源(作为发布者)和读取订阅源(作为订阅者),下面分别介绍。
生成订阅源(作为发布者)
如果你想让用户订阅你的网站内容,需要用 PHP 生成 RSS 或 Atom 格式的 XML 文件。
手动生成 RSS 2.0 示例
<?php类型为 XML
header('Content-Type: application/rss+xml; charset=utf-8');
// 数据库或数组中获取最新文章
$articles = [
[
'title' => '文章标题 1',
'link' => 'https://example.com/article/1',
'description' => '这是文章摘要...',
'pubDate' => date('D, d M Y H:i:s O', strtotime('2025-01-01 10:00:00')),
'author' => '作者名'
],
// 更多文章...
];
// 生成 RSS XML
echo '<?xml version="1.0" encoding="UTF-8"?>';
?>
<rss version="2.0">
<channel>你的网站标题</title>
<link>https://example.com</link>
<description>网站描述</description>
<language>zh-cn</language>
<?php foreach ($articles as $article): ?>
<item>
<title><?php echo htmlspecialchars($article['title']); ?></title>
<link><?php echo $article['link']; ?></link>
<guid><?php echo $article['link']; ?></guid>
<description><?php echo htmlspecialchars($article['description']); ?></description>
<pubDate><?php echo $article['pubDate']; ?></pubDate>
<author><?php echo $article['author']; ?></author>
</item>
<?php endforeach; ?>
</channel>
</rss>
使用 PHP 库生成
推荐使用 Laminas Feed 或 SimplePie(读取用)。
用 Laminas Feed 生成:
composer require laminas/laminas-feed
<?php
use Laminas\Feed\Writer\Feed;
$feed = new Feed();
$feed->setTitle('网站标题');
$feed->setLink('https://example.com');
$feed->setDescription('网站描述');
// 添加条目
$entry = $feed->createEntry();
$entry->setTitle('文章标题');
$entry->setLink('https://example.com/article/1');
$entry->setDateModified(time());
$entry->setDescription('#39;);
$feed->addEntry($entry);
// 输出 RSS 2.0
echo $feed->export('rss');
读取订阅源(作为订阅者)
如果你想读取别人的 RSS/Atom 源。
使用 SimplePie(推荐)
SimplePie 是 PHP 中最流行的订阅源解析库。
安装:
composer require simplepie/simplepie
基本用法:
<?php
require_once 'vendor/autoload.php';
use SimplePie\SimplePie;
// 设置缓存目录(需要可写)
$feed = new SimplePie();
$feed->set_feed_url('https://example.com/feed.xml');
$feed->set_cache_location('./cache');
$feed->init();
$feed->handle_content_type();
// 检查是否成功
if ($feed->error()) {
echo '读取失败: ' . $feed->error();
exit;
}
// 获取订阅源信息
echo '标题:' . $feed->get_title() . '<br>';
echo '网站:' . $feed->get_link() . '<br>';
echo '描述:' . $feed->get_description() . '<br>';
// 遍历文章
foreach ($feed->get_items() as $item) {
echo '<h2>' . $item->get_title() . '</h2>';
echo '<a href="' . $item->get_link() . '">阅读原文</a><br>';
echo '发布时间:' . $item->get_date('Y-m-d H:i') . '<br>';
echo $item->get_description() . '<br><hr>';
}
使用 cURL + DOMDocument(原生方法)
<?php
function fetchRss($url) {
// 使用 cURL 获取内容
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$xml = curl_exec($ch);
curl_close($ch);
return $xml;
}
// 解析 XML
$xmlStr = fetchRss('https://example.com/feed');
$xml = simplexml_load_string($xmlStr, 'SimpleXMLElement', LIBXML_NOCDATA);
// 使用命名空间
$namespaces = $xml->getNamespaces(true);
$content = $xml->channel;
foreach ($content->item as $item) {
echo '标题:' . $item->title . '<br>';
echo '链接:' . $item->link . '<br>';
echo '描述:' . $item->description . '<br>';
// 如果有扩展命名空间
if (isset($item->children($namespaces['content']))) {
$contentExt = $item->children($namespaces['content']);
echo '全文:' . html_entity_decode((string)$contentExt->encoded) . '<br>';
}
echo '<hr>';
}
实用技巧
统一封装读取函数
<?php
class FeedReader {
private $feed;
public function __construct($url) {
$this->feed = new SimplePie();
$this->feed->set_feed_url($url);
$this->feed->enable_cache(true);
$this->feed->set_cache_duration(3600); // 1小时缓存
$this->feed->init();
}
public function getItems($limit = 20) {
$items = [];
foreach ($this->feed->get_items(0, $limit) as $item) {
$items[] = [
'title' => $item->get_title(),
'link' => $item->get_link(),
'description' => $item->get_description(),
'date' => $item->get_date('Y-m-d H:i:s'),
'author' => $item->get_author() ? $item->get_author()->get_name() : '',
'image' => $this->extractImage($item->get_content()),
'categories' => $this->getCategories($item)
];
}
return $items;
}
private function extractImage($content) {
if (preg_match('/<img[^>]+src="([^"]+)"/', $content, $matches)) {
return $matches[1];
}
return '';
}
private function getCategories($item) {
$cats = $item->get_categories();
return $cats ? array_map(function($cat) {
return $cat->get_label();
}, $cats) : [];
}
}
// 使用
$reader = new FeedReader('https://example.com/feed');
$articles = $reader->getItems(10);
foreach ($articles as $article) {
echo $article['title'] . ' (' . $article['date'] . ')<br>';
}
使用 WordPress 方式(如果使用 WP)
// 获取订阅源
$rss = fetch_feed('https://example.com/feed');
if (!is_wp_error($rss)) {
$maxitems = $rss->get_item_quantity(5);
$rss_items = $rss->get_items(0, $maxitems);
foreach ($rss_items as $item) {
echo '<a href="' . $item->get_permalink() . '">';
echo $item->get_title();
echo '</a>';
}
}
安全建议
- 过滤输出:所有动态内容输出前使用
htmlspecialchars()防止 XSS。 - 设置头信息:正确设置
Content-Type为application/rss+xml或text/xml。 - 缓存:读取外部订阅源一定用缓存避免每次都请求。
- 超时处理:设置 cURL 超时,避免卡死页面。
- 错误处理:检查 XML 解析结果,处理无效 XML。
- CDATA 处理如果含特殊字符,用 CDATA 包裹:
<description><![CDATA[<?php echo $article['description']; ?>]]></description>
最佳实践
- 生成时:确保每个 item 都有唯一
<guid>。 - 生成时:
pubDate使用 RFC 822 格式(date(DATE_RFC2822))。 - 读取时:注意处理编码问题(UTF-8)。
- 读取时:处理图片懒加载(部分订阅源用
data-src)。 - 避免过度调用:大量外部订阅源请求可能导致被封或加载缓慢。
如果需要更高级功能(如 WebSub 实时推送),可以考虑使用 WebSub/PubSubHubbub 协议配合实现。