身為網站管理者,定期追蹤網站的效能和 SEO 表現至關重要。然而,手動檢查既耗時又容易忘記。今天,我們將利用 n8n 這個強大的自動化工具,串接 Google PageSpeed Insights API,打造一個能定時回報網站健康狀況的自動化流程,並將報告發送到 Discord,讓你輕鬆掌握網站的最新狀態
n8n 本身沒有內建的 Lighthouse 節點,但幸運的是,Google 提供了一個名為 PageSpeed Insights API 的免費服務,其底層就是使用 Lighthouse 引擎來進行分析,我們可以透過 n8n 的 HTTP Request 節點來呼叫這個 API,取得完整的分析報告。
申請 API 金鑰
https://developers.google.com/speed/docs/insights/v5/get-started?authuser=1&hl=zh-tw
把彈出視窗的「YOUR API KEY」複製起來

[排程觸發] -> [設定網站清單] -> [呼叫 API 檢查] -> [取得分數] -> [傳送結果]
排程觸發:設定一個固定的時間,例如每週日早上,自動啟動流程
設定網站清單:定義要檢查的一個或多個網站網址
呼叫 API 檢查:依序將清單中的網址傳送給 PageSpeed Insights API 進行分析
擷取關鍵分數:從 API 回傳的完整報告中,只抓出我們關心的效能(Performance)和 SEO 分數
彙整並傳送結果:將所有網站的分數整理成一份易讀的訊息,並發送到指定的 Discord 頻道
來到儀表板,新增一個流程「Create Workflow」

初始節點選擇排程「On a schedule」

選擇「Cron」來下指令

選擇一個時間,像我是設定每個禮拜日的早上 10 點

下個節點選擇「Edit Fields (Set)」來設定要檢查的網站清單

點選中間的「Add Field」

欄位名稱設定為「urls」,格式選擇「Array」,內容是包含網址的陣列,例如:
["https://www.hexschool.com", "https://5xcampus.com/"];

下個節點選擇「Split Out」

把上個節點的「urls」變數抓到欄位裡面做拆分

接著選擇「HTTP Request」

方法選擇為「GET」,URL 設定為下
https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url={{ $json.urls }}&strategy=desktop&category=performance&category=seo&key=YOUR_API_KEY
接著點選正上方的「Execute step」來測試跑看看,等待一段時間可以收到整包資料

下個節點再選擇「Edit Fields (Set)」來抓出需要的資料

點選中間的「Add Field」

欄位名稱設定如下
url
{
  {
    $json.lighthouseResult.requestedUrl;
  }
}
performanceScore
{
  {
    Math.round($json.lighthouseResult.categories.performance.score * 100);
  }
}
seoScore
{
  {
    Math.round($json.lighthouseResult.categories.seo.score * 100);
  }
}

下個節點選擇「Code」來整理訊息

程式碼如下
let messageBody = "";
// 走訪所有從上一個節點傳來的項目
for (const item of items) {
  const url = item.json.url;
  const performance = item.json.performanceScore;
  const seo = item.json.seoScore;
  // 如果 messageBody 不是空的,就先加上分隔線
  if (messageBody !== "") {
    messageBody += "\n----------------------------------\n";
  }
  // 組合單一網站的報告內容
  messageBody += `**網站:** ${url}\n`;
  messageBody += `**效能分數 (Performance):** \`${performance}\` / 100\n`;
  messageBody += `**SEO 分數:** \`${seo}\` / 100`;
}
// 最後,回傳一個包含完整訊息的「單一」項目
return [
  {
    json: {
      summaryMessage: messageBody,
    },
  },
];
下個節點選擇「Discord」來傳送訊息


「Connection Type」選擇「Webhook」,憑證的串接在之前的文章有撰寫過,這邊就不重複惹

Message 把上個節點的變數抓進來

回到畫布點選正下方的「Execute workflow」來測試看看

Discord 有收到訊息代表成功啦

由於有設定排程,所以要記得調整設定的時區

選擇台灣時區

接著記得到最上方切換為「Active」來啟用這個流程

現在,這個 n8n 流程就會像個忠實的檢查員,每週準時為你檢查網站的健康狀況,並自動回報給你
最後附上這個流程的 JSON
{
  "name": "lighthouse",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtHour": 10
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [0, 0],
      "id": "19013ae7-392e-46bf-8c17-0cd55aa84149",
      "name": "Schedule Trigger"
    },
    {
      "parameters": {
        "url": "=https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url={{ $json.urls }}&strategy=desktop&category=performance&category=seo&key=<YOUR_GOOGLE_API_KEY>",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [660, 0],
      "id": "d1692a12-8ebd-479e-b08e-c0eeef9cc1bf",
      "name": "HTTP Request"
    },
    {
      "parameters": {
        "fieldToSplitOut": "urls",
        "options": {}
      },
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [440, 0],
      "id": "bab43bd3-f13c-4a6c-9926-ae5ed49b8dfa",
      "name": "Split Out"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "6a3aea36-198c-461c-abfc-1408cc314aa6",
              "name": "urls",
              "value": "=[\"https://www.hexschool.com\", \"https://5xcampus.com/\"]",
              "type": "array"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [220, 0],
      "id": "52c5e6a4-01e8-4e47-b855-c533f02b9e26",
      "name": "List"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "b726f03a-faa2-4210-9468-95df2db00422",
              "name": "url",
              "value": "={{ $json.lighthouseResult.requestedUrl }}",
              "type": "string"
            },
            {
              "id": "80cce080-3191-471a-b9a6-2c2b492e18b2",
              "name": "performanceScore",
              "value": "={{ Math.round($json.lighthouseResult.categories.performance.score * 100) }}",
              "type": "string"
            },
            {
              "id": "1b014eb7-3531-46d8-8344-0c0949223f8f",
              "name": "seoScore",
              "value": "={{ Math.round($json.lighthouseResult.categories.seo.score * 100) }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [880, 0],
      "id": "f41b65ae-9237-4e43-9fb1-23d5235525bc",
      "name": "Edit Fields"
    },
    {
      "parameters": {
        "jsCode": "let messageBody = \"\";\n\n// 走訪所有從上一個節點傳來的項目\nfor (const item of items) {\n  const url = item.json.url;\n  const performance = item.json.performanceScore;\n  const seo = item.json.seoScore;\n\n  // 如果 messageBody 不是空的,就先加上分隔線\n  if (messageBody !== \"\") {\n    messageBody += \"\\n----------------------------------\\n\";\n  }\n\n  // 組合單一網站的報告內容\n  messageBody += `**網站:** ${url}\\n`;\n  messageBody += `**效能分數 (Performance):** \\`${performance}\\` / 100\\n`;\n  messageBody += `**SEO 分數:** \\`${seo}\\` / 100`;\n}\n\n// 最後,回傳一個包含完整訊息的「單一」項目\nreturn [{\n  json: {\n    summaryMessage: messageBody\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [1100, 0],
      "id": "0d232e8b-7b96-457f-8361-f8197b2398e5",
      "name": "Code"
    },
    {
      "parameters": {
        "authentication": "webhook",
        "content": "={{ $json.summaryMessage }}",
        "options": {}
      },
      "type": "n8n-nodes-base.discord",
      "typeVersion": 2,
      "position": [1320, 0],
      "id": "1464b030-d148-4d02-a0ac-671db31a9b91",
      "name": "Discord",
      "webhookId": "<YOUR_WEBHOOK_ID>",
      "credentials": {
        "discordWebhookApi": {
          "id": "<YOUR_CREDENTIAL_ID>",
          "name": "Discord Webhook account"
        }
      }
    }
  ],
  "pinData": {},
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "List": {
      "main": [
        [
          {
            "node": "Split Out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code": {
      "main": [
        [
          {
            "node": "Discord",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "timezone": "Asia/Taipei",
    "callerPolicy": "workflowsFromSameOwner",
    "executionTimeout": -1
  },
  "versionId": "<WORKFLOW_VERSION_ID>",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "id": "<WORKFLOW_ID>",
  "tags": []
}