「使用者或搜尋引擎爬蟲請求 sitemap.xml 的時候,即時查資料庫組出 XML 回傳,不是很直覺嗎?」
即時組出來確實直覺,但如果網站的頁面跟新聞數量多,每次請求都要重新查一次全站的頁面、新聞、快速連結,這個回應會變慢,而且每一次爬蟲來訪,都要重新承擔一次這個查詢成本。今天要看的是這個系統怎麼用排程機制,把「產生 sitemap 內容」跟「回應 sitemap 請求」拆成兩件事。
Schedule facade)怎麼設定排程任務Schedule::job() 跟 Schedule::command() 兩種排程方式的差異Laravel 的排程設定集中在 routes/console.php:
Schedule::job(FetchNewsJob::class)->hourly()->withoutOverlapping();
Schedule::job(SyncNewsJob::class)->hourly()->withoutOverlapping();
Schedule::command('telescope:prune')->daily();
Schedule::command('sitemap:generate')->daily();
Schedule::job() 排程的是一個 Queue Job(明天後天會細講),Schedule::command() 排程的是一支 Artisan Command。sitemap:generate 這支指令被設定成每天執行一次——不是每次有人請求 sitemap.xml 才即時產生,而是每天固定時間先算好,存起來備用。
class GenerateSitemap extends Command
{
protected $signature = 'sitemap:generate';
public function handle(): void
{
UrlFacade::forceScheme('https');
$sitemap = Sitemap::create();
$this->addHomePage($sitemap);
$this->addPages($sitemap);
$this->addNews($sitemap);
$this->addQuickLinkCategories($sitemap);
$this->addStaticPages($sitemap);
Storage::put('sitemap.xml', $sitemap->render());
$this->info('Sitemap generated successfully.');
}
}
這支 Command 用 spatie/laravel-sitemap 套件組出整個網站地圖——把首頁、所有已發佈的頁面、所有已發佈的新聞、快速連結分類頁、還有幾個固定的靜態頁面,各自組成一個 URL 條目,設定對應的更新頻率跟優先度,最後把整份 XML 內容存進 Storage,而不是直接回傳給誰。
注意 handle() 一開始有一行 UrlFacade::forceScheme('https')。排程指令是在背景執行的,不是透過一次真實的 HTTP 請求觸發,這代表 Laravel 沒有一個「當下這次請求是不是 https」的線索可以參考,route() helper 組網址時預設可能會用 http。手動強制 scheme 為 https,確保產生出來的 sitemap 裡的網址都是正確的協定,不會因為排程執行環境的差異而產生錯誤的連結。
以新聞跟頁面為例,各自用對應的已發佈 scope(前幾天看過的 invokable scope class)過濾,只把真正該公開的內容加進地圖:
private function addNews(Sitemap $sitemap): void
{
News::query()
->tap(new NewsPublished)
->get()
->each(function (News $news) use ($sitemap) {
$sitemap->add(
Url::create($news->url)
->setLastModificationDate($news->updated_at)
->setPriority(0.6)
->setChangeFrequency(Url::CHANGE_FREQUENCY_MONTHLY)
);
});
}
頁面用 whereNotIn('template', [...]) 額外排除掉「別名」「外部連結」「登入」這幾種特殊型別的頁面(還記得 Day 4 講的 PageType 嗎?這裡就是它被消費的另一個地方)——這幾種頁面本身不該出現在搜尋引擎的網站地圖裡,因為它們不是真正的內容頁。
把這支 Command 跟 Day 12 要看的「回應 sitemap.xml 請求」的邏輯放在一起看,會發現這個系統刻意把兩件事分開負責:
sitemap:generate):負責查資料庫、組內容、存檔案,跑在排程裡,跟任何一次真實的使用者請求無關這樣拆開的好處很直接:不管一天之內有多少次 sitemap.xml 的請求(搜尋引擎爬蟲通常會頻繁造訪),回應邏輯都只是讀一個已經存在的靜態檔案,完全不會對資料庫造成額外負擔。 真正查資料庫組內容這件「重」的事,一天只發生一次,而且是在排程的時間點、不是在使用者等待回應的當下。
public function xml()
{
$sitemap = Sitemap::create();
// 每次都重新查詢所有已發佈的新聞、頁面...
return response($sitemap->render(), 200, ['Content-Type' => 'application/xml']);
}
爬蟲越常來訪,資料庫查詢負擔越重,而且內容其實不會秒級變化,即時查詢換來的「即時性」意義不大。
// 排程:定期產生
Schedule::command('sitemap:generate')->daily();
// Controller:只讀取,不查資料庫
你的專案裡有沒有一個「內容其實不需要每次請求都即時算」的功能,目前卻是即時查詢組出來的?排程 + 快取檔案這種模式,適合套用進去嗎?
Schedule::command()/Schedule::job() 可以設定週期性自動執行的任務sitemap:generate 每天排程執行一次,查詢所有已發佈內容,組出完整 sitemap 存進檔案forceScheme('https') 這類手動設定是背景執行常見的細節明天要看這個系統一個真實發生過的 bug——sitemap.xml 明明產生出來了,Google Search Console 卻讀不到,根因藏在一個意想不到的地方。