自动化多平台游戏优惠追踪
中级
这是一个Personal Productivity领域的自动化工作流,包含 11 个节点。主要使用 If, Cron, Gmail, Sqlite, Function 等节点。 使用 Deku Deals 和 Gmail 提醒自动追踪多平台游戏优惠
前置要求
- •Google 账号和 Gmail API 凭证
- •可能需要目标 API 的认证凭证
分类
工作流预览
可视化展示节点连接关系,支持缩放和平移
无法加载工作流预览
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"nodes": [
{
"name": "每日检查(上午 8 点)",
"type": "n8n-nodes-base.cron",
"notes": {
"text": "### 1. Daily Check (8 AM)\n\nThis `Cron` node triggers the workflow automatically every **day at 8:00 AM** (based on your n8n server's local time zone). This allows for frequent checks for new deals.\n\n**To change the schedule:** Adjust the 'Hour' and 'Minute' fields to your preferred time and frequency.",
"position": "right"
},
"position": [
240,
300
],
"parameters": {
"mode": "everyDay",
"value": {
"hour": [
8
],
"minute": [
0
]
},
"options": {}
},
"typeVersion": 1
},
{
"name": "获取 Deku Deals 页面",
"type": "n8n-nodes-base.httpRequest",
"notes": {
"text": "### 2. Fetch Deku Deals Page\n\nThis `HTTP Request` node downloads the HTML content of Deku Deals' 'Most Popular' page.\n\n**Setup:**\n1. **URL:** The URL is pre-filled for Deku Deals' 'Most Popular' deals. You can change this to `/deals` or another specific section if you prefer.\n2. **Response Format:** Ensure this is set to `string` (for HTML content).\n\n**Considerations:**\n* Deku Deals is generally well-structured for scraping. If their layout changes, the `HTML Extract` node will need updates.",
"position": "right"
},
"position": [
480,
300
],
"parameters": {
"url": "https://www.dekudeals.com/most-popular",
"options": {},
"responseFormat": "string"
},
"typeVersion": 3
},
{
"name": "提取每个交易卡片",
"type": "n8n-nodes-base.htmlExtract",
"notes": {
"text": "### 3. Extract Each Deal Card\n\nThis `HTML Extract` node first extracts each individual 'game card' HTML block. This makes it easier to process each deal independently in the next step.\n\n**Setup:**\n1. **HTML:** This field is already set to `{{ $node[\"Fetch Deku Deals Page\"].json.data }}`.\n2. **Selector:** `div.game-card` (This targets the main container for each deal).\n3. **Attribute:** `html` (to get the full HTML content of each card).\n\n**If the layout changes:** You might need to update `div.game-card` to the new wrapper for individual game deals.",
"position": "right"
},
"position": [
720,
300
],
"parameters": {
"html": "={{ $node[\"Fetch Deku Deals Page\"].json.data }}",
"extractOperations": [
{
"options": {},
"selector": "div.game-card",
"attribute": "html",
"operation": "extract",
"propertyName": "dealHtml"
}
]
},
"typeVersion": 1
},
{
"name": "解析交易详情(函数代码)",
"type": "n8n-nodes-base.function",
"notes": {
"text": "### 4. Parse Deal Details (Function Code)\n\nThis `Function` node dives into the HTML of each deal card to extract the specific details (title, price, link, platform, etc.). It also creates a `dealUniqueId` for tracking.\n\n**Setup:**\n* This node uses JavaScript with `jsdom` (an n8n built-in library for parsing HTML) to select individual elements within each `dealHtml` item.\n* **Crucially, the selectors here (`div.name > a`, `div.price.current`, etc.) are also subject to website changes.** You might need to update them if the layout of `div.game-card` itself changes.\n\n**Output:** Each item will now have fields like `dealUniqueId`, `gameTitle`, `gameLink`, `platforms`, `currentPrice`, `originalPrice`, `discount`.",
"position": "right"
},
"position": [
960,
300
],
"parameters": {
"options": {},
"function": "const { JSDOM } = require('jsdom');\n\nconst deals = [];\n\nfor (const item of items) {\n const dom = new JSDOM(item.json.dealHtml);\n const doc = dom.window.document;\n\n const titleElement = doc.querySelector('div.name > a');\n const linkElement = doc.querySelector('div.name > a');\n const currentPriceElement = doc.querySelector('div.price-wrapper > div.price.current');\n const originalPriceElement = doc.querySelector('div.price-wrapper > div.price.original');\n const discountElement = doc.querySelector('div.price-wrapper > div.price.discount');\n const platformElements = doc.querySelectorAll('div.platforms > span.platform');\n\n const gameTitle = titleElement ? titleElement.textContent.trim() : 'N/A';\n const gameLink = linkElement ? `https://www.dekudeals.com${linkElement.getAttribute('href')}` : 'N/A';\n const currentPrice = currentPriceElement ? currentPriceElement.textContent.trim() : 'N/A';\n const originalPrice = originalPriceElement ? originalPriceElement.textContent.trim() : 'N/A';\n const discount = discountElement ? discountElement.textContent.trim() : 'N/A';\n const platforms = Array.from(platformElements).map(p => p.textContent.trim()).join(', ');\n\n // Create a unique ID for the deal for tracking in SQLite\n // Combination of title, platform, and current price should be fairly unique\n const dealUniqueId = `${gameTitle}|${platforms}|${currentPrice}`;\n\n deals.push({\n json: {\n dealUniqueId: dealUniqueId,\n gameTitle: gameTitle,\n gameLink: gameLink,\n platforms: platforms,\n currentPrice: currentPrice,\n originalPrice: originalPrice,\n discount: discount\n }\n });\n}\n\nreturn deals;"
},
"typeVersion": 1
},
{
"name": "SQLite:确保表存在",
"type": "n8n-nodes-base.sqlite",
"notes": {
"text": "### 5. SQLite: Ensure Table Exists\n\nThis `SQLite` node ensures that a local database table named `notified_deals` exists. This table will store the unique IDs of deals you've already been notified about.\n\n**Setup:**\n* **Database:** `dekudeals` (this creates a file `dekudeals.db` in your n8n data directory).\n* **Query:** `CREATE TABLE IF NOT EXISTS notified_deals (...)` as shown.\n\n**No further action needed**; this node will run automatically. On subsequent runs, it will simply confirm the table exists.",
"position": "right"
},
"position": [
1200,
220
],
"parameters": {
"query": "CREATE TABLE IF NOT EXISTS notified_deals (deal_id TEXT PRIMARY KEY, game_title TEXT, platforms TEXT, current_price TEXT, original_price TEXT, discount TEXT, deal_link TEXT, notified_date TEXT)",
"database": "dekudeals"
},
"typeVersion": 1
},
{
"name": "SQLite:检查是否已通知",
"type": "n8n-nodes-base.sqlite",
"notes": {
"text": "### 6. SQLite: Check if Deal Already Notified\n\nThis `SQLite` node checks if each extracted deal (using its `dealUniqueId`) is already present in your `notified_deals` database.\n\n**Setup:**\n* **Database:** `dekudeals`\n* **Query:** `SELECT deal_id FROM notified_deals WHERE deal_id = '{{ $json.dealUniqueId }}'` (It looks for a match for the current deal's unique ID).\n\n**Output:** If the deal is found, this node will output an item. If not, it will output no item, which is crucial for the 'Item Lists' node below.",
"position": "right"
},
"position": [
1440,
300
],
"parameters": {
"query": "SELECT deal_id FROM notified_deals WHERE deal_id = '{{ $json.dealUniqueId }}'",
"database": "dekudeals"
},
"typeVersion": 1
},
{
"name": "拆分为已通知/新交易",
"type": "n8n-nodes-base.itemLists",
"notes": {
"text": "### 7. Split into Notified/New\n\nThis `Item Lists` node takes the results from the 'SQLite: Check if Notified' node and splits them into two paths:\n* **Original items without a matching ID in the database:** These are **NEW** deals (`Output 1`).\n* **Original items with a matching ID in the database:** These are **ALREADY NOTIFIED** deals (`Output 2`).\n\n**No configuration needed**; it automatically separates the items based on whether the `SQLite` query found a match or not.",
"position": "right"
},
"position": [
1680,
300
],
"parameters": {
"mode": "splitInBatches",
"property": "dealUniqueId"
},
"typeVersion": 1
},
{
"name": "如果(发现新交易)",
"type": "n8n-nodes-base.if",
"notes": {
"text": "### 8. If (New Deals Found)\n\nThis `If` node checks if there are any *new* deals (i.e., items coming from the 'New' path of the 'Split into Notified/New' node).\n\n* **'True' branch:** If new deals are found, the workflow proceeds to insert them into the database and send a notification.\n* **'False' branch:** If no new deals are found, the workflow ends here (no notification needed).\n\n**No configuration needed**; it automatically checks if there are any items.",
"position": "right"
},
"position": [
1920,
220
],
"parameters": {
"conditions": [
{
"value1": "={{ $json.length }}",
"value2": "0",
"operation": "notEqual"
}
]
},
"typeVersion": 1
},
{
"name": "SQLite:插入新交易",
"type": "n8n-nodes-base.sqlite",
"notes": {
"text": "### 9. SQLite: Insert New Deals\n\nThis `SQLite` node inserts the details of the newly found deals into your `notified_deals` database. This ensures you won't be notified about them again on subsequent runs.\n\n**Setup:**\n* **Database:** `dekudeals`\n* **Query:** The `INSERT INTO` query is pre-filled, saving all the extracted deal details.\n\n**No further action needed**; it automatically stores the new deal information.",
"position": "right"
},
"position": [
2160,
140
],
"parameters": {
"query": "INSERT INTO notified_deals (deal_id, game_title, platforms, current_price, original_price, discount, deal_link, notified_date) VALUES ('{{ $json.dealUniqueId }}', '{{ $json.gameTitle }}', '{{ $json.platforms }}', '{{ $json.currentPrice }}', '{{ $json.originalPrice }}', '{{ $json.discount }}', '{{ $json.gameLink }}', '{{ new Date().toISOString() }}')",
"database": "dekudeals"
},
"typeVersion": 1
},
{
"name": "格式化通知消息",
"type": "n8n-nodes-base.function",
"notes": {
"text": "### 10. Format Notification Message\n\nThis `Function` node takes the new deal details and formats them into a clear, readable message for your notification (e.g., email or Telegram).\n\n**Customization:**\n* You can change the introductory text, add more emojis, or adjust the display format of each deal. It dynamically adds 'Was' and 'Discount' info only if available.\n\n**No configuration needed if your property names match the previous node's output.**",
"position": "right"
},
"position": [
2400,
140
],
"parameters": {
"options": {},
"function": "let message = \"🎮 **New Game Deals on Deku Deals!** 🎮\\n\\n\";\n\nfor (const item of items) {\n message += `**${item.json.gameTitle}** (${item.json.platforms})\\n` +\n ` Current Price: **${item.json.currentPrice}**` +\n (item.json.originalPrice !== 'N/A' ? ` (Was: ${item.json.originalPrice})` : '') +\n (item.json.discount !== 'N/A' ? ` | Discount: ${item.json.discount}` : '') +\n `\\n Claim/View Deal: ${item.json.gameLink}\\n\\n`;\n}\n\nmessage += \"Check out all deals: https://www.dekudeals.com/most-popular\";\n\nreturn [{ json: { notificationMessage: message } }];"
},
"typeVersion": 1
},
{
"name": "发送邮件通知",
"type": "n8n-nodes-base.gmail",
"notes": {
"text": "### 11. Send Email Notification\n\nThis `Gmail` node sends the formatted notification message about the new game deals.\n\n**Setup:**\n1. **Gmail Credential:** Select your Gmail API credential.\n2. **From Email:** Enter your Gmail address (must match the authenticated account).\n3. **To Email:** **IMPORTANT: Change `YOUR_RECIPIENT_EMAIL@example.com` to your actual email address!**\n4. **Subject & Text:** These fields pull the formatted message from the previous node.\n\n**To switch to Telegram/Slack/Discord:** Simply replace this node with your preferred notification service's node, and map the `notificationMessage` to its text field. You'll need to set up credentials for that service.",
"position": "right"
},
"position": [
2640,
140
],
"parameters": {
"text": "={{ $json.notificationMessage }}",
"options": {},
"subject": "🎮 New Game Deals Alert! (Deku Deals)",
"toEmail": "YOUR_RECIPIENT_EMAIL@example.com",
"fromEmail": "YOUR_GMAIL_EMAIL@gmail.com"
},
"credentials": {
"gmailApi": {
"id": "YOUR_GMAIL_CREDENTIAL_ID",
"resolve": false
}
},
"typeVersion": 2
}
],
"pinData": {},
"version": 1,
"connections": {
"Daily Check (8 AM)": {
"main": [
[
{
"node": "Fetch Deku Deals Page",
"type": "main"
}
]
]
},
"If (New Deals Found)": {
"main": [
[
{
"node": "SQLite: Insert New Deals",
"type": "main"
}
],
[]
]
},
"Fetch Deku Deals Page": {
"main": [
[
{
"node": "Extract Each Deal Card",
"type": "main"
}
]
]
},
"Extract Each Deal Card": {
"main": [
[
{
"node": "Parse Deal Details (Function Code)",
"type": "main"
}
]
]
},
"Split into Notified/New": {
"main": [
[
{
"node": "If (New Deals Found)",
"type": "main"
}
],
[]
]
},
"SQLite: Insert New Deals": {
"main": [
[
{
"node": "Format Notification Message",
"type": "main"
}
]
]
},
"SQLite: Check if Notified": {
"main": [
[
{
"node": "Split into Notified/New",
"type": "main"
}
]
],
"output": [
{
"type": "item",
"toIndex": 0,
"fromIndex": 0,
"destination": [
{
"node": "Split into Notified/New",
"input": "input2"
}
]
}
]
},
"Format Notification Message": {
"main": [
[
{
"node": "Send Email Notification",
"type": "main"
}
]
]
},
"SQLite: Ensure Table Exists": {
"main": [
[
{
"node": "SQLite: Check if Notified",
"type": "main"
}
]
]
},
"Parse Deal Details (Function Code)": {
"main": [
[
{
"node": "SQLite: Check if Notified",
"type": "main"
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
中级 - 个人效率
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
使用OpenAI和Gmail的每日积极新闻摘要
使用OpenAI和Gmail的每日积极新闻摘要
If
Cron
Gmail
+3
9 节点Piotr Sobolewski
个人效率
自动化网页爬虫:细分职位/产品监控与Telegram警报
自动化网页爬虫:细分职位/产品监控与Telegram警报
If
Cron
Function
+3
6 节点Piotr Sobolewski
市场调研
企业在线形象监测器
使用AI情感分析和多平台追踪的每日企业在线形象监测
Set
Cron
Gmail
+7
17 节点Piotr Sobolewski
市场调研
使用Gmail和GPT摘要自动发送每日邮件摘要
使用Gmail和GPT摘要自动发送每日邮件摘要,每天下午发送
Cron
Gmail
Open Ai
+1
6 节点Piotr Sobolewski
个人效率
将 Google 日历的每周会议安排预览发送到 Gmail
将 Google 日历的每周会议安排预览发送到 Gmail
Cron
Gmail
Function
+1
5 节点Piotr Sobolewski
个人效率
航班价格下降指示器
监控航班价格下降并通过SerpAPI和Gmail发送邮件提醒
If
Cron
Gmail
+2
5 节点Yash Choudhary
个人效率
工作流信息
难度等级
中级
节点数量11
分类1
节点类型8
作者
Piotr Sobolewski
@piotrsobolewskiAI PhD with 7 years experience as a game dev CEO, currently teaching, helping others and building something new.
外部链接
在 n8n.io 查看 →
分享此工作流