AutomationFlowsWeb Scraping › Automated Multi-platform Game Deals Tracker with Deku Deals & Gmail Alerts

Automated Multi-platform Game Deals Tracker with Deku Deals & Gmail Alerts

ByPiotr Sobolewski @piotrsobolewski on n8n.io

How it works

Cron / scheduled trigger★★★★☆ complexity11 nodesHTTP RequestHtml ExtractSqliteItem ListsGmail
Web Scraping Trigger: Cron / scheduled Nodes: 11 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #6666 — we link there as the canonical source.

This workflow follows the Gmail → HTTP Request recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "nodes": [
    {
      "name": "Daily Check (8 AM)",
      "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": "Fetch Deku Deals Page",
      "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": "Extract Each Deal Card",
      "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": "Parse Deal Details (Function Code)",
      "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: Ensure Table Exists",
      "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: Check if Notified",
      "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": "Split into Notified/New",
      "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": "If (New Deals Found)",
      "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: Insert New Deals",
      "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": "Format Notification Message",
      "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 = \"\ud83c\udfae **New Game Deals on Deku Deals!** \ud83c\udfae\\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": "Send Email Notification",
      "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": "\ud83c\udfae New Game Deals Alert! (Deku Deals)",
        "toEmail": "user@example.com",
        "fromEmail": "user@example.com"
      },
      "credentials": {
        "gmailApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    }
  ],
  "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"
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

How it works

Source: https://n8n.io/workflows/6666/ — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

Track Changes Of Product Prices. Uses htmlExtract, functionItem, httpRequest, writeBinaryFile. Scheduled trigger; 25 nodes.

Html Extract, Function Item, HTTP Request +5
Web Scraping

This workflow automatically tracks changes on specific websites, typically in e-commerce where you want to get information about price changes. Basic knowledge of HTML and JavaScript Execute Command n

Html Extract, Function Item, HTTP Request +5
Web Scraping

This workflow pushes Stripe charges to HubSpot contacts. It uses the Stripe API to get all charges and the HubSpot API to update the contacts. The workflow will create a new HubSpot property to store

Stripe, Item Lists, HubSpot +1
Web Scraping

Freelancers, founders, households, and side-hustlers who work with several bank accounts but want one, always-up-to-date budget inside Maybe Finance—no more CSV exports or copy-paste. Schedule Trigger

HTTP Request, N8N Nodes Resend, Item Lists
Web Scraping

This workflow syncs Zendesk tickets to Pipedrive contact owners.

Function Item, HTTP Request, Zendesk +2