{
  "nodes": [
    {
      "id": "cf7497dc-a934-41d5-9860-e27599d3bf90",
      "name": "When clicking 'Execute workflow'",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -832,
        -64
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "f0e7adde-83ce-428d-9590-f1f35b6c0ba3",
      "name": "Get Keywords from Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -544,
        -64
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1VXVbyqRPPHgO0tyoxpEajCyPX1DZdY3hRu_jCOdDriE/edit#gid=0",
          "cachedResultName": "Keywords"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1VXVbyqRPPHgO0tyoxpEajCyPX1DZdY3hRu_jCOdDriE",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1VXVbyqRPPHgO0tyoxpEajCyPX1DZdY3hRu_jCOdDriE/edit?usp=drivesdk",
          "cachedResultName": "Competitor tracker"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "2b8e096f-cd26-4886-9cc2-78d67b49c76d",
      "name": "URL Encode Keywords",
      "type": "n8n-nodes-base.code",
      "position": [
        -272,
        -64
      ],
      "parameters": {
        "jsCode": "const encode = encodeURIComponent;\nfor (const item of $input.all()) {\n  item.json.Keyword = encode(item.json.Keyword);\n}\nreturn $input.all();"
      },
      "typeVersion": 2
    },
    {
      "id": "2c743dde-c8f3-4ce8-9a11-89d95accffc3",
      "name": "Fetch Google Search Results",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        80,
        -96
      ],
      "parameters": {
        "url": "=https://api.scrape.do/?token={{$vars.SCRAPEDO_TOKEN}}&url={{ encodeURIComponent('https://www.google.com/search?q=' + $json.Keyword) }}&geoCode={{ $json['Target Country'] || 'us' }}&render=true",
        "options": {
          "timeout": 60000,
          "redirect": {
            "redirect": {}
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
            }
          ]
        }
      },
      "typeVersion": 4.2,
      "alwaysOutputData": true
    },
    {
      "id": "581d6f6b-b4b7-460b-8d99-f61de812f2eb",
      "name": "Extract Competitor Data from HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        368,
        -96
      ],
      "parameters": {
        "jsCode": "// Parse the HTML response from Scrape.do\nconst html = $input.first().json.data || $input.first().json.body || '';\n\nconst results = [];\n\n// Simple regex patterns to extract search results\n// Pattern for organic results\nconst resultPattern = /<div[^>]*class=\\\"[^\\\"]*Ww4FFb[^\\\"]*\\\"[^>]*>([\\s\\S]*?)<\\/div>/gi;\nconst matches = html.matchAll(resultPattern);\n\nlet position = 1;\nfor (const match of matches) {\n  const resultHtml = match[1];\n  \n  // Extract title (h3 tag)\n  const titleMatch = resultHtml.match(/<h3[^>]*>([^<]*)<\\/h3>/i);\n  const title = titleMatch ? titleMatch[1].replace(/<[^>]*>/g, '').trim() : '';\n  \n  // Extract URL\n  const urlMatch = resultHtml.match(/href=\\\"([^\\\"]+)\\\"/i);\n  const url = urlMatch ? urlMatch[1] : '';\n  \n  // Extract description\n  const descMatch = resultHtml.match(/<div[^>]*class=\\\"[^\\\"]*VwiC3b[^\\\"]*\\\"[^>]*>([^<]*)</i);\n  const description = descMatch ? descMatch[1].replace(/<[^>]*>/g, '').trim() : '';\n  \n  if (title && url) {\n    results.push({\n      position: position++,\n      websiteTitle: title,\n      websiteUrl: url,\n      websiteDescription: description || 'No description available',\n      keyword: $input.first().json.originalKeyword || $input.first().json.Keyword || ''\n    });\n  }\n  \n  // Stop after 10 organic results\n  if (position > 10) break;\n}\n\n// If no results found with the above method, try a simpler approach\nif (results.length === 0) {\n  // Look for any links that appear to be search results\n  const linkPattern = /<a[^>]*href=\\\"(https?:\\/\\/[^\\\"]+)\\\"[^>]*>([^<]+)<\\/a>/gi;\n  const linkMatches = html.matchAll(linkPattern);\n  \n  position = 1;\n  for (const linkMatch of linkMatches) {\n    const url = linkMatch[1];\n    const title = linkMatch[2].replace(/<[^>]*>/g, '').trim();\n    \n    // Filter out Google's own links\n    if (url && !url.includes('google.com') && !url.includes('googleapis.com')) {\n      results.push({\n        position: position++,\n        websiteTitle: title || 'No title',\n        websiteUrl: url,\n        websiteDescription: 'Description not available',\n        keyword: $input.first().json.originalKeyword || $input.first().json.Keyword || ''\n      });\n      \n      if (position > 10) break;\n    }\n  }\n}\n\n// Return results or a default if none found\nreturn results.length > 0 ? results.map(r => ({json: r})) : [{json: {\n  position: 1,\n  websiteTitle: 'No results found',\n  websiteUrl: 'N/A',\n  websiteDescription: 'Please check the HTML response',\n  keyword: $input.first().json.originalKeyword || $input.first().json.Keyword || '',\n  htmlLength: html.length\n}}];"
      },
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "fe0422c4-be3e-4a41-aa36-0e631c4450b4",
      "name": "Append Results to Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        640,
        -96
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1113333162,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1VXVbyqRPPHgO0tyoxpEajCyPX1DZdY3hRu_jCOdDriE/edit#gid=1113333162",
          "cachedResultName": "Competitor Results"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1VXVbyqRPPHgO0tyoxpEajCyPX1DZdY3hRu_jCOdDriE",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1VXVbyqRPPHgO0tyoxpEajCyPX1DZdY3hRu_jCOdDriE/edit?usp=drivesdk",
          "cachedResultName": "Competitor tracker"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "1fbe216c-35dc-4eb1-bed9-ef130ffa15b5",
      "name": "Workflow Start",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -880,
        96
      ],
      "parameters": {
        "height": 192,
        "content": "This workflow is triggered manually by clicking 'Execute workflow'.\n\nIt fetches keywords and target countries from a Google Sheet to perform SERP analysis using Scrape.do API."
      },
      "typeVersion": 1
    },
    {
      "id": "6dc607b7-d5f4-4fcd-809e-9545e1488972",
      "name": "Read Keywords",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -576,
        112
      ],
      "parameters": {
        "height": 192,
        "content": "Reads data from the specified Google Sheet.\n\nExpects columns:\n- 'Keyword' (search term)\n- 'Target Country' (2-letter country code like 'us', 'uk', 'de')\n\n**Remember to replace:**\n- YOUR_GOOGLE_SHEET_ID\n- YOUR_GOOGLE_SHEETS_CREDENTIAL_ID"
      },
      "typeVersion": 1
    },
    {
      "id": "9868274a-1c01-459b-bdf9-47cf1ef14d7a",
      "name": "Encode Keywords",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -304,
        80
      ],
      "parameters": {
        "height": 192,
        "content": "Encodes the 'Keyword' value using `encodeURIComponent`.\n\nThis is necessary to ensure keywords with special characters are correctly formatted for the URL."
      },
      "typeVersion": 1
    },
    {
      "id": "a9158b83-ce5a-45d2-908c-9a504990dd71",
      "name": "Fetch SERP Data with Scrape.do",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16,
        64
      ],
      "parameters": {
        "content": "**Scrape.do Configuration:**\n\n- **API Endpoint**: https://api.scrape.do/\n- **Parameters**:\n  - `token`: Your Scrape.do API token\n  - `url`: The Google search URL (encoded)\n  - `geoCode`: Target country code\n  - `render`: Set to true for JavaScript rendering\n\n**Remember to replace 'YOUR_SCRAPEDO_TOKEN'**\n\nGet your token at: https://dashboard.scrape.do/"
      },
      "typeVersion": 1
    },
    {
      "id": "4b9de746-a1ae-4e43-91d6-3b42524f7ddf",
      "name": "Extract Competitor Data",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        320,
        80
      ],
      "parameters": {
        "content": "Parses the HTML response from Scrape.do to extract:\n- Website titles\n- URLs\n- Descriptions\n- Position in search results\n\nThe code looks for organic search results and filters out ads and Google's own links."
      },
      "typeVersion": 1
    },
    {
      "id": "b51511f4-0d7c-4411-a5a4-36cc976fcf6a",
      "name": "Append to Results sheet",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        80
      ],
      "parameters": {
        "content": "Appends parsed SERP rows into the 'Results' sheet. Columns expected:\n- keyword\n- position\n- websiteTitle\n- websiteUrl\n- websiteDescription\n\nIf the sheet does not exist, create a tab named 'Results' in the same spreadsheet."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "URL Encode Keywords": {
      "main": [
        [
          {
            "node": "Fetch Google Search Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Keywords from Sheet": {
      "main": [
        [
          {
            "node": "URL Encode Keywords",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Google Search Results": {
      "main": [
        [
          {
            "node": "Extract Competitor Data from HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking 'Execute workflow'": {
      "main": [
        [
          {
            "node": "Get Keywords from Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Competitor Data from HTML": {
      "main": [
        [
          {
            "node": "Append Results to Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}