每天手動刷新租屋網站相當耗時。這篇文章將引導你使用自動化工具 n8n,建立一個專屬的通知機器人。它會每天定時抓取 591 租屋網的最新物件,篩選出符合你需求的房源,並自動發送到你的 Discord 頻道,讓你不再錯過任何一個理想中的家
來到儀表板,新增一個流程「Create Workflow」
初始節點選擇排程「On a schedule」
設定想收到通知的時間,比如說每日早上 9 點
接下來到「設定」裡面調整時區
把時區的「Timezone」設定為台灣時間
下個節點選「HTTP Request」,方法「GET」,URL 假如是以「台北、士林、雅房」的話可以這樣填寫
https://rent.591.com.tw/list?region=1§ion=8&kind=4&order=posttime&orderType=desc
下個節點選擇「HTML」的「Extract HTML Content」
填寫資料如下
Key: list
CSS Selector: .item-info
Return Value: HTML
下個節點選擇「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 的「Send a message」
「Connection Type」選擇「Webhook」,憑證的串接在之前的文章有撰寫過,這邊就不重複惹
Message 直接填寫如下,接著點選「Execute step」來試跑看看
{
{
$json.content;
}
}
接著會在 Discord 看到類似這樣的訊息,代表成功囉
完成後的畫布長這樣,記得要到上方切換為「Active」
底下也附上完整的 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§ion=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]"
}
}