使用 PubSubHubbub 制作 RSS 定时器 —— Laravel RSS (三)

本文字数:5627,大概需要 11.25 分钟。

接下来还有待于继续优化,如向 https://feed43.com/ 那样,输入 Web URL 就能生成 RSS Feed,又能根据实际需要自己设定更新时间等。 摘自:《花 2 小时撸一个 RSS 生成器》https://mp.weixin.qq.com/s/mRjoKgkq1PoqlVgOw8oRYw

今天试着完成如何可以根据实际需要自己设定更新时间间隔时长。

订阅源更新解释

由于我们使用 xpath 方式去抓取网站的内容,这些网站更新了内容,但它们不会实时告诉你它们更新了;所以「RSS 阅读器如何做到所谓的的「更新」呢?」

要获取某个订阅源的文章更新,最基础而朴素的方法就是定时访问其 RSS 地址,检查对应的 XML 文件有无变化。

同时,第二个问题出现了,虽然可以检查 RSS 是否有更新,但怎么去通知我们的「订阅者」(如:IFTTT) 呢?

虽然 Google Reader 已经关闭了,但它留下了一份遗产 ——PubSubHubbub 协议。在 PubSubHubbub 协议下,每当内容发布者(Publisher)发布新的内容时,都会主动通知一个被称为 Hub 的第三方服务器,Hub 随即通过发送 HTTP POST 请求的方式,将更新情况和文章内容「推送」给曾经向其订阅过该内容源的订户(Subscriber),从而真正实现了「即时」更新。

实践中,RSS 服务往往扮演着上述三方关系中的订户角色,从而不再需要为了及时获取内容反复刷新订阅源,而只要等着 Hub 传来内容源主动通知的更新,「坐享其成」就行了。这大大降低了成本,也彻底消除了更新不及时的问题。至于这一协议中的枢纽——Hub 服务器,Google 则在当年自己搭建了一个,而且至今还在运营;另外,一家名叫 Superfeedr 的公司也公开提供 Hub 服务。

需要注意的是,所谓的「实时」只是相对的,通知的发送不可能快过 RSS 服务抓取到订阅源更新的时间,而我们已经知道后者往往存在不可避免的时间差。因此,实时推送功能的作用只是提醒我们不要错过关心的内容;要真正做到分钟级的先知先觉,当今媒体生态下恐怕还是直接瞄准社交网络更为靠谱。 以上文字内容更多来自:《2018 年主流 RSS 服务选哪家?Feedly、Inoreader 和 NewsBlur 全面横评》https://sspai.com/post/44420

实现更新功能

有了 PubSubHubbub 协议来支撑我们「自动更新」能力,那我们就可以完善我们的代码。

添加 interval 字段

php artisan make:migration add_interval_to_xpaths_table --table=xpaths

默认间隔时间是 2个小时:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddIntervalToXpathsTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('xpaths', function (Blueprint $table) {
            $table->integer("interval")->default(2);
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('xpaths', function (Blueprint $table) {
            //
        });
    }
}
php artisan migrate

添加时间间隔选择框:

$form->select('interval', '更新间隔时间')->options(
    [
        1 => '一个小时', 
        2 => '两个小时',
        4 => '四个小时', 
        8 => '八个小时',
        12 => '半天',
    ]
);

添加 Link Header

由于 Superfeedr 收费,所以使用了这个试试:http://phubb.cweiske.de/

按照要求,需要添加两个 Link 到 RSS Header:

<link href="{{ url("/feed/$xpath->id") }}" rel="self" type="application/atom+xml"/>
<link rel="hub" href="http://phubb.cweiske.de/hub.php" />

添加定时器

接下来我们就需要根据每个 RSS 自定义的时间间隔,利用 Laravel 的任务调度功能。

具体参考:https://laravel-china.org/docs/laravel/5.5/scheduling

使用调度器时,只需将以下 Cron 项目添加到服务器。

* * * * * php /path-to-your-project/artisan schedule:run >> /dev/null 2>&1

这个 Cron 会每分钟调用一次 Laravel 命令调度器。执行 schedule:run 命令时, Laravel 会根据你的调度运行预定任务。

每个更新时间间隔的 RSS 会比较多,而且都需要网络请求,所以这里的采用「队列任务调度」,而且是以每小时去执行一次:

$schedule->job(new AutoUpdateRss(new EloquentRssRepository()))->hourly();

创建任务

到了真正核心的地方了!

这里我们使用 database 这个队列驱动,首先需要创建一个数据表来存储任务。可以用 queue:table 这个 Artisan 命令来创建这个数据表的迁移。当迁移创建好以后,就可以用 migrate 这条命令来创建数据表:

php artisan queue:table
php artisan migrate

修改 Default Queue Driverdatabase

'default' => env('QUEUE_DRIVER', 'database'),

生成任务类

php artisan make:job AutoUpdateRss

在 AutoUpdateRss 类执行查询满足条件的 xpaths,实时去触发 Hub 服务器,告知「订阅者」该更新了。

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Repositories\RssRepositoryContract;

class AutoUpdateRss implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    private $rssRC;

    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(RssRepositoryContract $rssRC)
    {
        $this->rssRC = $rssRC;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        $xpaths = $this->rssRC->query();
        if (empty($xpaths) || count($xpaths) == 0) {
            return;
        }
        $this->rssRC->update($xpaths);
    }
}

Laravel 的队列系统介绍,看这:https://laravel-china.org/docs/laravel/5.5/queues

查询方法

public function query() {
    $allXpaths = Xpath::all();

    $now = Carbon::now();

    $xpaths = $allXpaths->filter(function ($value, $key) use ($now) {
        $diff = $now->diffInHours(Carbon::parse($value->created_at));
        return $diff % $value->interval == 0;
    });

    return $xpaths;
}

通知 Hub 方法

public function update($xpaths) {
    $client = new Client();

    $requests = function ($xpaths) {
        $uri = 'http://phubb.cweiske.de/hub.php';
        foreach($xpaths as $xpath) {
            yield new Request('POST', $uri, [
                    'form_params' => [
                        'hub.mode' => 'publish',
                        'hub.url' => url("/feed/$xpath->id")
                    ]
                ]
            );
        }
    };

    $pool = new Pool($client, $requests($xpaths), [
        'concurrency' => 5,
        'fulfilled' => function ($response, $index) {
            // this is delivered each successful response
        },
        'rejected' => function ($reason, $index) {
            // this is delivered each failed request
        },
    ]);

    // Initiate the transfers and create a promise
    $promise = $pool->promise();

    // Force the pool of requests to complete.
    $promise->wait();
}

这里我们使用 Guzzle http://guzzle-cn.readthedocs.io/zh_CN/latest/quickstart.html,利用多线程异步请求,提高效率。

// 安装 guzzle 插件
composer require guzzlehttp/guzzle

Guzzle 更多的使用,参考:《推荐一个 PHP 网络请求插件 Guzzle》https://mp.weixin.qq.com/s/w2I8hUmHu0UgjgbSMPEKpg

测试

万事俱备,只剩下测试,跑跑效果了,我们把时间改成每隔五分钟,去更新 RSS,我们还是利用「IFTTT 连接钉钉」的方法,看看效果:

总结

代码粗糙,但五脏俱全了,这过程主要使用了几个核心技术和工具:

  1. Laravel 的任务调度
  2. Laravel 的队列
  3. Guzzle 网络请求插件
  4. phubb - PHP PubSubHubbub server

让我们的 RSS Feed 有了定时器更新功能,下一步可以开始试着写写前端,做一个网站工具,让更多的人使用。

未完待续

原文发布于微信公众号 - coding01(coding01)

原文发表时间:2018-05-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java技术分享

java系统高并发的解决方案

一个小型的网站,比如个人网站,可以使用最简单的html静态页面就实现了,配合一些图片达到美化效果,所有的页面均存放在一个目录下,这样的网站对系统架构、性能的要...

9009
来自专栏雨过天晴

初窥dep

6858
来自专栏芋道源码1024

【追光者系列】HikariCP连接池监控指标实战

1.3K4
来自专栏CDA数据分析师

Python部署手记:django, gunicorn, virtualenv, circus, nginx

手记,以免下次配置再入坑。有些细节未做详细描述,如果有问题,可以评论或私信我。 初次尝试搭python服务器,强撸python3,花样作死。过程中出现各种错误,...

3907
来自专栏FreeBuf

扒一扒浏览器的安全机制

*本文原创作者:梅孜,本文属FreeBuf原创奖励计划,未经许可禁止转载 随着互联网深入人们的生活,浏览器的发展更加丰富多彩,其种类多样,版本更新速度也日益提...

2199
来自专栏chenssy

【追光者系列】HikariCP连接池监控指标实战

该指标持续飙高,说明DB连接池中基本已无空闲连接。 拿之前业务方应用pisces不可用的例子来说(如下图所示),当时所有线程都在排队等待,该指标已达172,此时...

2184
来自专栏北京马哥教育

定时任务调度与管理平台JobCenter | crontab替代者

没有JobCenter时我们要面对的: 电商业务链条很长,业务逻辑也较为复杂,需要成百上千种定时任务。窝窝的大多数定时任务其实调用的是本地或远端 Java/PH...

1K11
来自专栏Java架构师学习

十面阿里,屌丝逆袭阿里之路

2143
来自专栏电光石火

idea 创建的maven+spring+mybatis项目整合 报错无法创建bean

最近在做一个由maven构建的spring+spring mvc+mybatis项目,刚开始的时候是用自己的电脑Win10环境下的eclipse写的,托管到了码...

1988
来自专栏大内老A

谈谈分布式事务之四: 两种事务处理协议OleTx与WS-AT

在年前写一个几篇关于分布式事务的文章,实际上这些都是为了系统介绍WCF事务处理体系而提供的相关的背景和基础知识。今天发最后一篇,介绍分布式事务采用的两种协议,即...

2078

扫码关注云+社区

领取腾讯云代金券