最近偷窥大佬【似水流年】的友链RSS聚合页,在流年兄的大力指导和支持下终于完美上线。
展示:朋友的圈 – 品味苏州 (pwsz.com) 导航简称(友圈)。
此页面聚合了本站友链中经常更新的朋友们的rss数据,终于不用再每次一个个点击网站查看了,据说每年可以节省一个好友鼠标,科技啊,呵~让人们开源节流!
经过大佬的悉心指导,让我也弄懂了一些原理,简单讲讲:
1、创建一个php文件,比如 rss.php ,放在页面模板的文件夹下,据说有的是在主题的 page文件夹,而我的就直接是主题的根目录,又要深深吐槽下island主题作者大伟哥的不靠谱,连个文件夹都不给人家!
<?php
/*
Template Name: 似水流年-RSS 朋友圈
*/
date_default_timezone_set('Asia/Shanghai');
get_header();
// 引入 SimplePie 类
require_once(ABSPATH. WPINC. '/class-simplepie.php');
// 主内容区域
echo '<div id="primary" class="content-area">';
echo '<main id="main" class="site-main" role="main">';
echo '<p style="color: red; font-size: 14px; margin-top: 10px; margin-bottom: 20px; text-align: center; display: flex; align-items: center; justify-content: center;">以下友情链接网站最新内容每 8 小时获取更新一次</p>';
// 获取并缓存友情链接的最新文章数据
function get_cached_friend_link_rss_data() {
$cache_key = 'friend_link_rss_data';
// 尝试从对象缓存中获取数据
$cached_data = wp_cache_get($cache_key);
if ($cached_data) {
return $cached_data;
}
$all_articles = array();
$links = get_bookmarks(array(
'orderby' => 'name',
'category' => 0,
'order' => 'ASC',
'hide_invisible' => 1,
'categorize' => 0,
'title_li' => '',
'echo' => 0
));
foreach ($links as $link) {
if ($link->link_rss &&!empty($link->link_rss)) {
$domain = parse_url($link->link_url, PHP_URL_HOST);
if (!$domain) {
continue;
}
$feed_url = $link->link_rss;
$cache_file = get_stylesheet_directory(). '/rsscache/'. $domain. '.xml';
// 如果缓存文件不存在或超过 8 小时,且 RSS 内容有更新,才更新缓存
if (!file_exists($cache_file) || time() - filemtime($cache_file) > 8 * HOUR_IN_SECONDS) {
$rss_content = get_rss_content($feed_url);
if ($rss_content) {
$existing_content = file_exists($cache_file)? file_get_contents($cache_file) : false;
if ($existing_content!== $rss_content) {
file_put_contents($cache_file, $rss_content);
}
}
}
$rss_items = get_rss_items($cache_file);
foreach ($rss_items as $item) {
$item_data = array(
'link_name' => $link->link_name,
'link_logo' => $link->link_image,
'link_url' => $link->link_url,
'item_title' => $item->get_title(),
'item_link' => $item->get_permalink(),
'item_date' => $item->get_date('Y-m-d H:i:s'),
'item_author' => $item->get_author()->get_name(),
'item_excerpt' => $item->get_description(),
'item_content' => $item->get_content()
);
$all_articles[$link->link_name][] = $item_data;
}
}
}
// 将数据存入对象缓存
wp_cache_set($cache_key, $all_articles, null, 8 * HOUR_IN_SECONDS);
return $all_articles;
}
// 获取最新的友情链接文章
function get_latest_friend_link_articles() {
$latest_articles = get_cached_friend_link_rss_data();
$all_articles = array();
foreach ($latest_articles as $link_name => $articles) {
foreach ($articles as $article) {
$all_articles[] = $article;
}
}
usort($all_articles, function ($a, $b) {
$date1 = date('Y-m-d', strtotime($a['item_date']));
$date2 = date('Y-m-d', strtotime($b['item_date']));
if ($date1 === $date2) {
return strcmp($a['link_name'], $b['link_name']);
} else {
return strtotime($b['item_date']) - strtotime($a['item_date']);
}
});
$sorted_articles = array();
foreach ($all_articles as $article) {
$sorted_articles[$article['link_name']][] = $article;
}
foreach ($sorted_articles as $link_name => $articles) {
usort($articles, function ($a, $b) {
return strtotime($b['item_date']) - strtotime($a['item_date']);
});
$sorted_articles[$link_name] = array_slice($articles, 0, 5);
}
return $sorted_articles;
}
// 获取并保存最新的 RSS 内容
function get_rss_content($feed_url) {
$response = wp_safe_remote_get($feed_url, array('sslverify' => true));
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
return wp_remote_retrieve_body($response);
} else {
$response = wp_safe_remote_get($feed_url, array('sslverify' => false));
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
return wp_remote_retrieve_body($response);
} else {
return '无法从提供 RSS 的 URL 获取 RSS 内容。';
}
}
}
// 使用 SimplePie 获取 RSS 项
function get_rss_items($cache_file) {
$rss = new SimplePie();
$rss->set_feed_url($cache_file);
$rss->init();
$rss->handle_content_type();
$rss->set_output_encoding('UTF-8');
return $rss->get_items();
}
// 获取并显示友情链接的最新文章
$latest_articles = get_latest_friend_link_articles();
foreach ($latest_articles as $link_name => $articles) {
?>
<div class="friend-link-container" style="border: 1px dashed #000; padding: 10px; margin-bottom: 10px; display: flex; align-items: center;">
<!-- 左侧显示友情链接的网站 Logo 和名称 -->
<div class="friend-link-info" style="width: 20%; text-align: center;">
<img src="<?php echo esc_url($articles[0]['link_logo']);?>" alt="<?php echo esc_attr($link_name);?> Logo" style="border-radius: 50%; width: 60px; height: 60px;">
<a href="<?php echo esc_url($articles[0]['link_url']);?>" target="_blank"><p style="margin: 0; padding: 0;"><?php echo esc_html($link_name);?></p></a>
</div>
<!-- 右侧显示最新更新的前五篇文章 -->
<div class="friend-link-articles" style="width: 80%; padding-left: 10px;">
<?php foreach ($articles as $article) :?>
<p style="margin: 0; padding: 0;">
<a href="<?php echo esc_url($article['item_link']);?>" target="_blank"><?php echo esc_html($article['item_title']);?></a>(<?php echo esc_html(date('Y-m-d', strtotime($article['item_date'])));?>)
</p>
<?php endforeach;?>
</div>
</div>
<?php
}
// 侧边栏
get_sidebar();
echo '</main><!-- #main -->';
echo '</div><!-- #primary -->';
get_footer();
?>
2、创建一个新页面,填写标题,比如:朋友圈,右侧的模板里,选择刚创建的页面模板“RSS 朋友圈”,保存。以后访问这个页面,就是RSS聚合页。
3、链接里,在高级一栏中填写“图片地址”和“RSS地址”,如不填,则不会出现在聚合页,填写后,就会自动采集RSS数据,8小时更新一次。
4、在主题的根目录,创建一个新的文件夹:rsscache,采集到的RSS数据会保存在这里文件夹里,8小时后再次采集时覆盖保存。
简单四步就搞定了。但是我的博客比较奇葩,实在是太老了(又要吐槽大伟哥,钱收了、手甩了),中间出现不少小bug,流年大佬不辞辛苦搞到半夜,一遍遍的帮我排查、修改代码,最终完美呈现,心中感激无以言表,哈哈哈~再次感谢优秀80后代表流年大佬~
2024.12.16 似水流年更新,优化如下:
减少不必要的缓存获取尝试,利用 Swoole 的协程等功能来实现并发获取 RSS 内容。
同时发起多个 HTTP 请求去获取不同友情链接的 RSS 内容,减少等待时间。
减少排序时的时间格式化操作,用array_multisort 等更高效的排序函数代替usort 这种相对通用但在大数据量下效率不算特别高的排序方式。
设置合理的重试次数、采用指数退避等重试间隔策略,增强在网络不稳定等情况下获取 RSS 内容的成功率。
注意:这个要求PHP版本大于7.4,并且安装redis、swoole或者swoole5扩展。另外 // 头部图片 是我的博客自改的,通用版用老代码中 // 主内容区域 那段代替即可。
<?php
/*
Template Name: RSS 朋友圈
*/
date_default_timezone_set('Asia/Shanghai');
get_header();
// 引入 SimplePie 类
require_once(ABSPATH. WPINC. '/class-simplepie.php');
// 头部图片
echo '<div id="header_slider" class=" fadeInDown animated ">';
echo '<div class="slider"><ul class="lightSlider"><a href="https://pwsz.com/hobby/4493.html#lqmxp"target="_black" title="送你一张明信片"><img width="759" height="289" src="https://pwsz.com/myimg/pydq_banner.jpg" class="attachment-large-image size-large-image wp-post-image" alt="" decoding="async" fetchpriority="high" /></a></ul></div>';
// 获取并缓存友情链接的最新文章数据
function get_cached_friend_link_rss_data() {
$cache_key = 'friend_link_rss_data';
$last_update_time_key = 'last_update_time_'.$cache_key;
$last_update_time = wp_cache_get($last_update_time_key);
if ($last_update_time && (time() - $last_update_time < 7 * HOUR_IN_SECONDS)) {
return wp_cache_get($cache_key);
}
$all_articles = array();
$links = get_bookmarks(array(
'orderby' => 'name',
'category' => 0,
'order' => 'ASC',
'hide_invisible' => 1,
'categorize' => 0,
'title_li' => '',
'echo' => 0
));
$feed_urls = [];
foreach ($links as $link) {
if ($link->link_rss &&!empty($link->link_rss)) {
$domain = parse_url($link->link_url, PHP_URL_HOST);
if (!$domain) {
continue;
}
$feed_urls[] = $link->link_rss;
}
}
// 利用Swoole协程并发获取RSS内容
$rss_contents = get_rss_content_concurrently($feed_urls);
$index = 0;
foreach ($links as $link) {
if ($link->link_rss &&!empty($link->link_rss)) {
$domain = parse_url($link->link_url, PHP_URL_HOST);
if (!$domain) {
continue;
}
$feed_url = $link->link_rss;
$cache_file = get_stylesheet_directory(). '/rsscache/'. $domain. '.xml';
// 获取到的对应RSS内容
$rss_content = $rss_contents[$index];
$index++;
// 如果缓存文件不存在或超过 8 小时,且 RSS 内容有更新,才更新缓存
if (!file_exists($cache_file) || time() - filemtime($cache_file) > 8 * HOUR_IN_SECONDS) {
if ($rss_content) {
$existing_content = file_exists($cache_file)? file_get_contents($cache_file) : false;
if ($existing_content!== $rss_content) {
file_put_contents($cache_file, $rss_content);
}
}
}
$rss_items = get_rss_items($cache_file);
foreach ($rss_items as $item) {
$item_data = array(
'link_name' => $link->link_name,
'link_logo' => $link->link_image,
'link_url' => $link->link_url,
'item_title' => $item->get_title(),
'item_link' => $item->get_permalink(),
'item_date' => $item->get_date('Y-m-d H:i:s'),
'formatted_date' => date('Y-m-d', strtotime($item->get_date('Y-m-d H:i:s'))),
'item_author' => $item->get_author()->get_name(),
'item_excerpt' => $item->get_description(),
'item_content' => $item->get_content()
);
$all_articles[$link->link_name][] = $item_data;
}
}
}
// 将数据存入对象缓存,并设置本次更新时间缓存
wp_cache_set($cache_key, $all_articles, null, 8 * HOUR_IN_SECONDS);
wp_cache_set($last_update_time_key, time());
return $all_articles;
}
// 利用Swoole协程并发获取RSS内容的函数,完善错误处理和重试机制
function get_rss_content_concurrently($feed_urls) {
$results = [];
// 拆分配置项,明确各个参数对应的值
$host = '127.0.0.1';
$port = 80;
$timeout = 1.0; // 设置连接超时时间,单位秒,可根据实际调整
$write_timeout = 1.0; // 设置写超时时间,单位秒,可根据实际调整
$ssl = false; // 这里先设置为不使用SSL连接,可根据实际需求修改为true,如果需要SSL加密的话
$client = new Swoole\Coroutine\Http\Client($host, $port, $ssl);
$client->set([
'timeout' => $timeout,
'write_timeout' => $write_timeout,
]);
if (php_sapi_name() === 'cli') {
// 如果是PHP CLI模式,执行协程相关代码
$max_retries = 3; // 设置最大重试次数
$retry_delay = 1; // 初始重试延迟时间(秒)
foreach ($feed_urls as $index => $feed_url) {
$retry_count = 0;
do {
$response = null;
go(function () use ($index, $feed_url, &$response, $client) {
$response = $client->get($feed_url);
});
while ($response === null) {
Swoole\Coroutine::sleep(0.1);
}
if ($response->statusCode === 200) {
$results[$index] = $response->body;
break; // 获取成功则跳出重试循环
} else {
$retry_count++;
if ($retry_count < $max_retries) {
// 指数退避增加重试延迟时间
$retry_delay *= 2;
Swoole\Coroutine::sleep($retry_delay);
} else {
$results[$index] = '无法从提供 RSS 的 URL 获取 RSS 内容。';
break;
}
}
} while ($retry_count < $max_retries);
}
$client->close();
} else {
// 如果是非CLI模式(比如Web服务器环境等),采用同步方式获取RSS内容,可参考原代码中的同步获取逻辑进行完善
foreach ($feed_urls as $feed_url) {
$response = wp_safe_remote_get($feed_url, array('sslverify' => false));
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
$results[] = wp_remote_retrieve_body($response);
} else {
$results[] = '无法从提供 RSS 的 URL 获取 RSS 内容。';
}
}
}
return $results;
}
// 获取最新的友情链接文章,改进排序算法
function get_latest_friend_link_articles() {
$latest_articles = get_cached_friend_link_rss_data();
$all_articles = [];
foreach ($latest_articles as $link_name => $articles) {
foreach ($articles as $article) {
$all_articles[] = $article;
}
}
// 先按照格式化日期排序(整体文章排序)
$formatted_date_column = array_column($all_articles, 'formatted_date');
$link_name_column = array_column($all_articles, 'link_name');
array_multisort($formatted_date_column, SORT_DESC, $link_name_column, SORT_ASC, $all_articles);
$sorted_articles = [];
foreach ($all_articles as $article) {
$sorted_articles[$article['link_name']][] = $article;
}
foreach ($sorted_articles as $link_name => $articles) {
// 对每个友情链接下的文章按照原始日期再排序,取前五篇
$date_column = array_column($articles, 'item_date');
array_multisort($date_column, SORT_DESC, $articles);
$sorted_articles[$link_name] = array_slice($articles, 0, 5);
}
return $sorted_articles;
}
// 获取并保存最新的 RSS 内容
function get_rss_content($feed_url) {
$response = wp_safe_remote_get($feed_url, array('sslverify' => true));
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
return wp_remote_retrieve_body($response);
} else {
$response = wp_safe_remote_get($feed_url, array('sslverify' => false));
if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) {
return wp_remote_retrieve_body($response);
} else {
return '无法从提供 RSS 的 URL 获取 RSS 内容。';
}
}
}
// 使用SimplePie获取RSS项
function get_rss_items($cache_file) {
$rss = new SimplePie();
$rss->set_feed_url($cache_file);
$rss->init();
$rss->handle_content_type();
$rss->set_output_encoding('UTF-8');
return $rss->get_items();
}
// 获取并显示友情链接的最新文章
$latest_articles = get_latest_friend_link_articles();
foreach ($latest_articles as $link_name => $articles) {
?>
<div class="friend-link-container" style="border: 1px dashed #000; padding: 10px; margin-bottom: 10px; display: flex; align-items: center;">
<!-- 左侧显示友情链接的网站 Logo 和名称 -->
<div class="friend-link-info" style="width: 20%; text-align: center;">
<img src="<?php echo esc_url($articles[0]['link_logo']);?>" alt="<?php echo esc_attr($link_name);?> Logo" style="border-radius: 50%; width: 60px; height: 60px;">
<a href="<?php echo esc_url($articles[0]['link_url']);?>" target="_blank"><p style="margin: 0; padding: 0;"><?php echo esc_html($link_name);?></p></a>
</div>
<!-- 右侧显示最新更新的前五篇文章 -->
<div class="friend-link-articles" style="width: 80%; padding-left: 10px;">
<?php foreach ($articles as $article) :?>
<p style="margin: 0; padding: 0;">
<a href="<?php echo esc_url($article['item_link']);?>" target="_blank"><?php echo esc_html($article['item_title']);?></a>(<?php echo esc_html($article['formatted_date']);?>)
</p>
<?php endforeach;?>
</div>
</div>
<?php
}
// 侧边栏
get_sidebar();
echo '</main><!-- #main -->';
echo '</div><!-- #primary -->';
get_footer();
?>
看到你这个功能之后,我也学着做了一个rss订阅,本想订阅一下你的站点,但feed文件好像无法识别,只能作罢。
首先感谢厚爱,其次我这里可以订阅的哦,地址是 https://pwsz.com/feed/
feed 格式好像还有点问题,验证不通过。
哎~奇了怪了,这个不应该啊,wordpress标准的feed,或者你试下这个
https://pwsz.com/sitemap.xml
这个网站可以检测是否标准的feed:https://validator.w3.org/feed/。不过我下载下来之后,从自己网站上重新链接就变成合格的了。
好可怕,用了你提供的那个检测网站,我博客还有射覆网,都没通过feed检测 我也不懂了
这个我要学学,好省事
老哥也使用上咯
都是背靠大佬们的指点
思路不错,受教了
这个思路也不错,收藏了。
每个5条,这个思路可以借鉴一下
三人行,必有我师焉
你这个看起来更精致
各有特色
客气了
应该的,大佬费心了
搞完足迹搞友圈,下来再搞个啥好呢?哈哈
不搞了 暂时也没啥要弄的,弄这两个玩意都累趴下了
这个的确省事一些
方便的很
比较好奇rss.php中的代码?
放上去了。。有点长
最近闲逛也发现朋友圈这个订阅确实挺方便。
https://github.com/willow-god/Friend-Circle-Lite
你这个太高级了
关键你这教程只写了个“创建一个php文件”也没说里面放啥代码,咱也没法照葫芦画瓢
放上去了,太长了,本来不想放的
好人一生平安