iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0

每天手動刷新租屋網站相當耗時。這篇文章將引導你使用自動化工具 n8n,建立一個專屬的通知機器人。它會每天定時抓取 591 租屋網的最新物件,篩選出符合你需求的房源,並自動發送到你的 Discord 頻道,讓你不再錯過任何一個理想中的家

workflow

步驟一:設定排程觸發

  • 來到儀表板,新增一個流程「Create Workflow」

    image 0.png

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

    image 1.png

  • 設定想收到通知的時間,比如說每日早上 9 點

    image 2.png

  • 接下來到「設定」裡面調整時區

    image 3.png

  • 把時區的「Timezone」設定為台灣時間

    image 4.png

步驟二:抓取租屋網站資料

  • 下個節點選「HTTP Request」,方法「GET」,URL 假如是以「台北、士林、雅房」的話可以這樣填寫

    https://rent.591.com.tw/list?region=1&section=8&kind=4&order=posttime&orderType=desc
    

    image 5.png

步驟三:解析並提取網頁內容

  • 下個節點選擇「HTML」的「Extract HTML Content」

    image 6.png

  • 填寫資料如下

    • Key: list

    • CSS Selector: .item-info

    • Return Value: HTML

    image 7.png

步驟四:篩選、格式化資料

  • 下個節點選擇「Code」,程式碼如下

    const htmlSnippets = $input.item.json.list;
    
    // 如果沒有任何物件,就回傳一則提示訊息,並中止後續流程
    if (!htmlSnippets || htmlSnippets.length === 0) {
      return { json: { content: "找不到任何租屋物件可以處理。" } };
    }
    
    // ---- Part 1: 篩選與解析資料 ----
    
    const todayItems = [];
    for (const html of htmlSnippets) {
      // 檢查是否為今天更新的物件
      const isUpdatedToday =
        html.includes("小時內更新") || html.includes("分鐘內更新");
    
      if (isUpdatedToday) {
        // 是今天更新的,才進行資料提取
        const titleMatch = html.match(
          /<a class="link v-middle"[^>]+title="([^"]+)"/
        );
        const urlMatch = html.match(/<a class="link v-middle" href="([^"]+)"/);
        const locationMatch = html.match(
          /<i class="house-(?:bus-line|metro|restaurant)[^>]*>[\s\S]*?<span[^>]*>([^<]+)<\/span><strong[^>]*>([^<]+)<\/strong>/
        );
    
        // 確保所有需要的資訊都成功抓取到
        if (titleMatch && urlMatch) {
          todayItems.push({
            title: titleMatch[1],
            url: urlMatch[1],
            location: locationMatch
              ? `${locationMatch[1].trim()}${locationMatch[2].trim()}`
              : "未提供",
          });
        }
      }
    }
    
    // ---- Part 2: 格式化為 Discord 訊息 ----
    
    // 如果過濾後沒有今天更新的物件
    if (todayItems.length === 0) {
      return { json: { content: `士林區在過去24小時內沒有新的租屋物件。` } };
    }
    
    const DISCORD_CHAR_LIMIT = 1900; // 保留一點緩衝
    const todayDate = new Date().toLocaleDateString("zh-TW");
    let message = `**【士林區今日新上架租屋 - ${todayDate}】**\n\n`;
    
    for (const item of todayItems) {
      const itemString =
        `----------------------------------------\n` +
        `**標題:** ${item.title}\n` +
        `**地點:** ${item.location}\n` +
        `**網址:** ${item.url}\n\n`;
    
      // 檢查加上這筆資料後是否會超過字元上限
      if (message.length + itemString.length > DISCORD_CHAR_LIMIT) {
        message += `...還有更多物件,因訊息長度限制未完全顯示。`;
        break; // 超過則停止增加
      }
    
      message += itemString;
    }
    
    // ---- Part 3: 回傳最終結果 ----
    
    // 回傳一個適合 Discord 節點使用的物件格式
    return { json: { content: message } };
    

步驟五:發送訊息到 Discord

  • 下個節點選擇 Discord 的「Send a message」

    image 8.png

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

    image 9.png

  • Message 直接填寫如下,接著點選「Execute step」來試跑看看

    {
      {
        $json.content;
      }
    }
    

    image 10.png

  • 接著會在 Discord 看到類似這樣的訊息,代表成功囉

    image 11.png

  • 完成後的畫布長這樣,記得要到上方切換為「Active」

    image 12.png

  • 底下也附上完整的 JSON 內容

    {
      "nodes": [
        {
          "parameters": {
            "rule": {
              "interval": [
                {
                  "triggerAtHour": 9
                }
              ]
            }
          },
          "type": "n8n-nodes-base.scheduleTrigger",
          "typeVersion": 1.2,
          "position": [0, 0],
          "id": "29e919e2-27b1-428f-a931-cf0f85d028e4",
          "name": "Schedule Trigger"
        },
        {
          "parameters": {
            "url": "https://rent.591.com.tw/list?region=1&section=8&kind=4&order=posttime&orderType=desc",
            "options": {}
          },
          "type": "n8n-nodes-base.httpRequest",
          "typeVersion": 4.2,
          "position": [220, 0],
          "id": "7fac6e24-5c3d-49ab-87a4-12b8fdab7e4c",
          "name": "HTTP Request"
        },
        {
          "parameters": {
            "operation": "extractHtmlContent",
            "extractionValues": {
              "values": [
                {
                  "key": "list",
                  "cssSelector": ".item-info",
                  "returnValue": "html",
                  "returnArray": true
                }
              ]
            },
            "options": {}
          },
          "type": "n8n-nodes-base.html",
          "typeVersion": 1.2,
          "position": [440, 0],
          "id": "16b4a912-dc3c-4220-a8c0-06903c796ca1",
          "name": "HTML"
        },
        {
          "parameters": {
            "jsCode": "const htmlSnippets = $input.item.json.list;\n\n// 如果沒有任何物件,就回傳一則提示訊息,並中止後續流程\nif (!htmlSnippets || htmlSnippets.length === 0) {\n  return { json: { content: \"找不到任何租屋物件可以處理。\" } };\n}\n\n// ---- Part 1: 篩選與解析資料 ----\n\nconst todayItems = [];\nfor (const html of htmlSnippets) {\n  // 檢查是否為今天更新的物件\n  const isUpdatedToday = html.includes('小時內更新') || html.includes('分鐘內更新');\n\n  if (isUpdatedToday) {\n    // 是今天更新的,才進行資料提取\n    const titleMatch = html.match(/<a class=\"link v-middle\"[^>]+title=\"([^\"]+)\"/);\n    const urlMatch = html.match(/<a class=\"link v-middle\" href=\"([^\"]+)\"/);\n    const locationMatch = html.match(/<i class=\"house-(?:bus-line|metro|restaurant)[^>]*>[\\s\\S]*?<span[^>]*>([^<]+)<\\/span><strong[^>]*>([^<]+)<\\/strong>/);\n\n    // 確保所有需要的資訊都成功抓取到\n    if (titleMatch && urlMatch) {\n      todayItems.push({\n        title: titleMatch[1],\n        url: urlMatch[1],\n        location: locationMatch ? `${locationMatch[1].trim()}${locationMatch[2].trim()}` : '未提供'\n      });\n    }\n  }\n}\n\n// ---- Part 2: 格式化為 Discord 訊息 ----\n\n// 如果過濾後沒有今天更新的物件\nif (todayItems.length === 0) {\n  return { json: { content: `士林區在過去24小時內沒有新的租屋物件。` } };\n}\n\nconst DISCORD_CHAR_LIMIT = 1900; // 保留一點緩衝\nconst todayDate = new Date().toLocaleDateString('zh-TW');\nlet message = `**【士林區今日新上架租屋 - ${todayDate}】**\\n\\n`;\n\nfor (const item of todayItems) {\n  const itemString = `----------------------------------------\\n` +\n                     `**標題:** ${item.title}\\n` +\n                     `**地點:** ${item.location}\\n` +\n                     `**網址:** ${item.url}\\n\\n`;\n\n  // 檢查加上這筆資料後是否會超過字元上限\n  if (message.length + itemString.length > DISCORD_CHAR_LIMIT) {\n    message += `...還有更多物件,因訊息長度限制未完全顯示。`;\n    break; // 超過則停止增加\n  }\n\n  message += itemString;\n}\n\n// ---- Part 3: 回傳最終結果 ----\n\n// 回傳一個適合 Discord 節點使用的物件格式\nreturn { json: { content: message } };"
          },
          "type": "n8n-nodes-base.code",
          "typeVersion": 2,
          "position": [660, 0],
          "id": "ca3e2c38-238b-47ac-89a7-09b77c6b6c6c",
          "name": "Code"
        },
        {
          "parameters": {
            "authentication": "webhook",
            "content": "={{ $json.content }}",
            "options": {}
          },
          "type": "n8n-nodes-base.discord",
          "typeVersion": 2,
          "position": [880, 0],
          "id": "48d0d745-9a37-43ff-bd95-b375123f25ea",
          "name": "Discord",
          "webhookId": "[REDACTED_WEBHOOK_ID]",
          "credentials": {}
        }
      ],
      "connections": {
        "Schedule Trigger": {
          "main": [
            [
              {
                "node": "HTTP Request",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "HTTP Request": {
          "main": [
            [
              {
                "node": "HTML",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "HTML": {
          "main": [
            [
              {
                "node": "Code",
                "type": "main",
                "index": 0
              }
            ]
          ]
        },
        "Code": {
          "main": [
            [
              {
                "node": "Discord",
                "type": "main",
                "index": 0
              }
            ]
          ]
        }
      },
      "pinData": {},
      "meta": {
        "templateCredsSetupCompleted": true,
        "instanceId": "[REDACTED_INSTANCE_ID]"
      }
    }
    

上一篇
[Day21]_職缺平台每日整理
系列文
告別重複瑣事: n8n workflow 自動化工作實踐22
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言