接下來還有待於繼續優化,如向 https://feed43.com/ 那樣,輸入 Web URL 就能生成 RSS Feed,又能根據實際需要自己設定更新時間等。
摘自:《花 2 小時擼一個 RSS 生成器》mp.weixin.qq.com/s/mRjoKgkq1…
今天試著完成如何可以根據實際需要自己設定更新時間間隔時長。
訂閱源更新解釋
由於我們使用 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 全面橫評》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 收費,所以使用了這個試試: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 的任務排程功能。
使用排程器時,只需將以下 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 Driver
為 database
:
'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 的佇列系統介紹,看這:laravel-china.org/docs/larave…
查詢方法
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 guzzle-cn.readthedocs.io/zh_CN/lates…,利用多執行緒非同步請求,提高效率。
// 安裝 guzzle 外掛
composer require guzzlehttp/guzzle
複製程式碼
Guzzle 更多的使用,參考:《推薦一個 PHP 網路請求外掛 Guzzle》mp.weixin.qq.com/s/w2I8hUmHu…
測試
萬事俱備,只剩下測試,跑跑效果了,我們把時間改成每隔五分鐘,去更新 RSS,我們還是利用「IFTTT 連線釘釘」的方法,看看效果:
總結
程式碼粗糙,但五臟俱全了,這過程主要使用了幾個核心技術和工具:
- Laravel 的任務排程
- Laravel 的佇列
- Guzzle 網路請求外掛
- phubb - PHP PubSubHubbub server
讓我們的 RSS Feed 有了定時器更新功能,下一步可以開始試著寫寫前端,做一個網站工具,讓更多的人使用。
具體程式碼已同步 github:github.com/fanly/lrss
未完待續