這篇文章將帶你一步步建立一個自動化工作流程,每月自動從 IMDB Top 250 排行榜中隨機挑選一部電影,並將推薦訊息發送到你的 Discord 頻道。讓我們開始吧
定時觸發:設定在每個月的固定時間自動啟動
抓取網頁:從 IMDB Top 250 的官方頁面撈取電影榜單資料
解析資料:從網頁原始碼中提取出結構化的電影清單
隨機挑選:從 250 部電影中隨機選出 1 部
發送通知:將選出的電影資訊格式化後,透過 Webhook 發送到指定的 Discord 頻道
來到儀表板,新增一個流程「Create Workflow」
初始節點選擇排程「On a schedule」
設定為每個月的 1 號上午 9 點觸發
接下來到「設定」裡面調整時區
把時區的「Timezone」設定為台灣時間
下個節點選擇「HTTP Request」
方法為「GET」,網址如下
https://www.imdb.com/chart/top/
接著把「Send Headers」切換打開,填入底下欄位
Name:User-Agent
Value:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36
下個節點選擇「HTML」的「Extract HTML Content」
資料填寫如下:
Key:movie_json_ld
CSS Selector:script[type="application/ld+json"]
Return Value:HTML
下個節點選擇「Code」來轉換資料格式
程式碼填入底下的內容
try {
const movieData = JSON.parse($json.movie_json_ld);
// 提取電影清單
const movies = movieData.itemListElement.map((item) => {
const movie = item.item;
return {
// 基本資訊
title: movie.name,
alternateName: movie.alternateName,
description: movie.description,
url: movie.url,
// 評分資訊
rating: {
value: movie.aggregateRating.ratingValue,
count: movie.aggregateRating.ratingCount,
bestRating: movie.aggregateRating.bestRating,
worstRating: movie.aggregateRating.worstRating,
},
// 其他資訊
contentRating: movie.contentRating,
genre: movie.genre,
duration: movie.duration,
image: movie.image,
// 從 URL 提取 IMDB ID
imdbId: movie.url.match(/tt\d+/)[0],
// 將時長轉換為分鐘數
durationMinutes: movie.duration
? parseInt(movie.duration.match(/(\d+)H/)?.[1] || 0) * 60 +
parseInt(movie.duration.match(/(\d+)M/)?.[1] || 0)
: null,
};
});
return {
totalMovies: movies.length,
movies: movies,
metadata: {
source: "IMDB Top 250",
extractedAt: new Date().toISOString(),
originalUrl: movieData.url,
},
};
} catch (error) {
// 錯誤處理
return {
error: "解析失敗",
details: error.message,
originalData: $json,
};
}
現在我們有了完整的電影清單,接著就是從中隨機選 1 部
下個節點還是選擇「Code」,程式碼填寫如下
// 取得上一個節點傳來的資料
const inputData = $input.item.json;
// 從資料中取得電影列表陣列
const movies = inputData.movies;
// 產生一個隨機索引值,範圍是 0 到 (電影總數 - 1)
const randomIndex = Math.floor(Math.random() * movies.length);
// 根據隨機索引值,從陣列中挑選出一部電影
const randomMovie = movies[randomIndex];
// 回傳這部隨機挑選的電影
return randomMovie;
下個節點選擇 Discord 的「Send a message」來傳送訊息
「Connection Type」選擇「Webhook」,憑證的串接在之前的文章有撰寫過,這邊就不重複惹
可以在「Embeds」直接把上個節點的屬性抓過來使用
試跑下去就能在 Discord 看到類似這樣的訊息
完成後的流程長這樣,也要記得到上方切換為「Active」來啟用哦
最後也附上完整的 JSON
{
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "months",
"triggerAtHour": 9
}
]
}
},
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [0, 0],
"id": "1422463c-1086-4e96-8ee7-0c9d8a84a286",
"name": "Schedule Trigger"
},
{
"parameters": {
"url": "https://www.imdb.com/chart/top/",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "User-Agent",
"value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [220, 0],
"id": "d1f56f65-af3f-471d-a0dd-021e0b5fcda5",
"name": "HTTP Request"
},
{
"parameters": {
"operation": "extractHtmlContent",
"extractionValues": {
"values": [
{
"key": "movie_json_ld",
"cssSelector": "script[type=\"application/ld+json\"]",
"returnValue": "html"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.html",
"typeVersion": 1.2,
"position": [440, 0],
"id": "ef987ad1-c339-459b-9dfa-de38b5f1f803",
"name": "HTML"
},
{
"parameters": {
"jsCode": "try {\n const movieData = JSON.parse($json.movie_json_ld);\n \n // 提取電影清單\n const movies = movieData.itemListElement.map(item => {\n const movie = item.item;\n \n return {\n // 基本資訊\n title: movie.name,\n alternateName: movie.alternateName,\n description: movie.description,\n url: movie.url,\n \n // 評分資訊\n rating: {\n value: movie.aggregateRating.ratingValue,\n count: movie.aggregateRating.ratingCount,\n bestRating: movie.aggregateRating.bestRating,\n worstRating: movie.aggregateRating.worstRating\n },\n \n // 其他資訊\n contentRating: movie.contentRating,\n genre: movie.genre,\n duration: movie.duration,\n image: movie.image,\n \n // 從 URL 提取 IMDB ID\n imdbId: movie.url.match(/tt\\d+/)[0],\n \n // 將時長轉換為分鐘數\n durationMinutes: movie.duration ? \n parseInt(movie.duration.match(/(\\d+)H/)?.[1] || 0) * 60 + \n parseInt(movie.duration.match(/(\\d+)M/)?.[1] || 0) : null\n };\n });\n \n return {\n totalMovies: movies.length,\n movies: movies,\n metadata: {\n source: \"IMDB Top 250\",\n extractedAt: new Date().toISOString(),\n originalUrl: movieData.url\n }\n };\n \n} catch (error) {\n // 錯誤處理\n return {\n error: \"解析失敗\",\n details: error.message,\n originalData: $json\n };\n}\n"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [660, 0],
"id": "874e0d62-5759-46e9-9eb6-9044439e5c44",
"name": "List"
},
{
"parameters": {
"jsCode": "// 取得上一個節點傳來的資料\nconst inputData = $input.item.json;\n\n// 從資料中取得電影列表陣列\nconst movies = inputData.movies;\n\n// 產生一個隨機索引值,範圍是 0 到 (電影總數 - 1)\nconst randomIndex = Math.floor(Math.random() * movies.length);\n\n// 根據隨機索引值,從陣列中挑選出一部電影\nconst randomMovie = movies[randomIndex];\n\n// 回傳這部隨機挑選的電影\nreturn randomMovie;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [880, 0],
"id": "51d5fd45-a75f-4781-aa3b-0d68eb9141f5",
"name": "Code"
},
{
"parameters": {
"authentication": "webhook",
"options": {},
"embeds": {
"values": [
{
"description": "={{ $json.description }}",
"title": "={{ $json.title }} | {{ $json.alternateName }}",
"url": "={{ $json.url }}",
"image": "={{ $json.image }}"
}
]
}
},
"type": "n8n-nodes-base.discord",
"typeVersion": 2,
"position": [1100, 0],
"id": "76b494c2-87db-4c1a-9a10-629036a13382",
"name": "Discord",
"webhookId": "[REDACTED_WEBHOOK_ID]"
}
],
"connections": {
"Schedule Trigger": {
"main": [
[
{
"node": "HTTP Request",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request": {
"main": [
[
{
"node": "HTML",
"type": "main",
"index": 0
}
]
]
},
"HTML": {
"main": [
[
{
"node": "List",
"type": "main",
"index": 0
}
]
]
},
"List": {
"main": [
[
{
"node": "Code",
"type": "main",
"index": 0
}
]
]
},
"Code": {
"main": [
[
{
"node": "Discord",
"type": "main",
"index": 0
}
]
]
}
},
"pinData": {},
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "[REDACTED_INSTANCE_ID]"
}
}