AutomationFlowsWeb Scraping › Scrape Google Maps Leads to Airtable with GPT

Scrape Google Maps Leads to Airtable with GPT

Original n8n title: Scrape Google Maps Leads (email, Phone, Website) Using Apify + Gpt + Airtable

ByBaptiste Fort @baptistefort on n8n.io

This workflow is for marketers, sales teams, and local businesses who want to quickly collect leads (business name, phone, website, and email) from Google Maps and store them in Airtable.

Event trigger★★★★☆ complexityAI-powered17 nodesHTTP RequestOpenAIAirtable
Web Scraping Trigger: Event Nodes: 17 Complexity: ★★★★☆ AI nodes: yes Added:

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

This workflow follows the Airtable → 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
{
  "id": "pmEmSvoAXahHkDeM",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Scrape Google Maps Leads Email, Phone, Website using Apify + GPT + Airtable",
  "tags": [],
  "nodes": [
    {
      "id": "14b47bce-f9fa-49cf-b8e9-263fd7d781d3",
      "name": "Fetch Business Data from Google Maps (Apify)",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        240,
        0
      ],
      "parameters": {
        "url": "URL APIFY",
        "method": "POST",
        "options": {},
        "jsonBody": "{\n    \"includeWebResults\": false,\n    \"language\": \"fr\",\n    \"locationQuery\": \"Paris, France\",\n    \"maxCrawledPlacesPerSearch\": 5000,\n    \"maxImages\": 0,\n    \"maximumLeadsEnrichmentRecords\": 0,\n    \"scrapeContacts\": false,\n    \"scrapeDirectories\": false,\n    \"scrapeImageAuthors\": false,\n    \"scrapePlaceDetailPage\": false,\n    \"scrapeReviewsPersonalData\": true,\n    \"scrapeTableReservationProvider\": false,\n    \"searchStringsArray\": [\n        \"centre de formation\"\n    ],\n    \"skipClosedPlaces\": false\n}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    },
    {
      "id": "07c0d3e0-0a93-4cc7-900e-5f23a46a9f50",
      "name": "Clean Google Maps Data",
      "type": "n8n-nodes-base.set",
      "position": [
        480,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "e0ee1e00-da28-49c7-b8ad-b1cf983b1004",
              "name": "Nom du centre de formation",
              "type": "string",
              "value": "={{ $json.title }}"
            },
            {
              "id": "a1d6f737-2c45-4c90-950e-4ef356f788bd",
              "name": "Cat\u00e9gorie",
              "type": "string",
              "value": "={{ $json.categoryName }}"
            },
            {
              "id": "9ff0e712-4f36-4006-86c5-1d40c5675c4a",
              "name": "Adresse",
              "type": "string",
              "value": "={{ $json.address }}"
            },
            {
              "id": "b53c8805-9b69-498b-9347-b0d4fca6099c",
              "name": "Ville",
              "type": "string",
              "value": "={{ $json.city }}"
            },
            {
              "id": "87573cd5-3f99-4712-ba2c-260ddc6f43b2",
              "name": "Code postale",
              "type": "string",
              "value": "={{ $json.postalCode }}"
            },
            {
              "id": "e4fb0044-38d5-4fe5-a9ee-1da98c34567c",
              "name": "Site internet",
              "type": "string",
              "value": "={{ $json.website }}"
            },
            {
              "id": "c23a3ba2-9e62-4d77-a6c9-afc0b43235ad",
              "name": "Num\u00e9ro de t\u00e9l\u00e9phone",
              "type": "string",
              "value": "={{ $json.phone }}"
            },
            {
              "id": "7d4a6a71-d01f-4a7d-82ce-a8ac5396c87a",
              "name": "Nombre d'avis",
              "type": "string",
              "value": "={{ $json.reviewsCount }}"
            },
            {
              "id": "29994cd7-d775-480f-9901-6269dc2a4787",
              "name": "Note sur 5",
              "type": "string",
              "value": "={{ $json.totalScore }}"
            },
            {
              "id": "6de303d2-3d35-4458-b4cc-ab1ec09929e4",
              "name": "Lien google",
              "type": "string",
              "value": "={{ $json.url }}"
            },
            {
              "id": "d1c7cc55-55f0-4e66-a00f-415a2c69b743",
              "name": "",
              "type": "string",
              "value": ""
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "39439d62-c9ca-4f2b-bd37-6850cf14875c",
      "name": "Manual Workflow Execution",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        0,
        0
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "1f9442b7-799e-4334-8126-5d501ef59e9e",
      "name": "Iterate Through Each Business Contact",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        752,
        0
      ],
      "parameters": {
        "options": {},
        "batchSize": 10
      },
      "typeVersion": 3
    },
    {
      "id": "e1f2abe8-ae2c-4d91-b117-0bee95d5155c",
      "name": "Extract Only Website URLs",
      "type": "n8n-nodes-base.set",
      "position": [
        992,
        16
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ccc11066-fad4-4931-acf6-6c45e5e7b117",
              "name": "Site internet",
              "type": "string",
              "value": "={{ $json['Site internet'] }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "0e2c8c55-ece3-4816-aaaa-bf293d2ea31a",
      "name": "Fetch Raw HTML Content from Business Website",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1280,
        16
      ],
      "parameters": {
        "url": "={{ $json['Site internet'] }}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "794ed0f3-8a69-405a-a985-ac94d31e7bd3",
      "name": "Extract Business Email from Website HTML (GPT-4)",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        1472,
        16
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini",
          "cachedResultName": "GPT-4.1-MINI"
        },
        "options": {},
        "messages": {
          "values": [
            {
              "content": "=Look at this website content and extract only the email I can contact this business. In your output, provide only the email and nothing else. Ideally, this email should be of the business owner, so if you have 2 or more options, try for most authoritative one. If you don't find any email, output 'Null'.\n\nExemplary output of yours:\n\nname@examplewebsite.com\n\n{{ $json.data }}"
            }
          ]
        }
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.8
    },
    {
      "id": "03a525b4-448e-4259-bcd1-d4369dc6e902",
      "name": "Save Cleaned Lead Data into Airtable",
      "type": "n8n-nodes-base.airtable",
      "position": [
        1872,
        16
      ],
      "parameters": {
        "base": {
          "__rl": true,
          "mode": "list",
          "value": "app7nzaQOYoq0cF1n",
          "cachedResultUrl": "https://airtable.com/app7nzaQOYoq0cF1n",
          "cachedResultName": "GOOGLE MAP SCRAPPING"
        },
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "tblJIrBU19hfFYdL9",
          "cachedResultUrl": "https://airtable.com/app7nzaQOYoq0cF1n/tblJIrBU19hfFYdL9",
          "cachedResultName": "Data"
        },
        "columns": {
          "value": {
            "URL": "={{ $('Clean Google Maps Data').item.json.URL }}",
            "Email": "={{ $json.message.content }}",
            "Title": "={{ $('Clean Google Maps Data').item.json.Title }}",
            "Street": "={{ $('Clean Google Maps Data').item.json.Address }}",
            "Website": "={{ $('Clean Google Maps Data').item.json.Website }}",
            "Phone Number": "={{ $('Clean Google Maps Data').item.json.Phone }}"
          },
          "schema": [
            {
              "id": "Title",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Street",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Street",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Website",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Website",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Phone Number",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Phone Number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Email",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "Email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "URL",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "URL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "create"
      },
      "credentials": {
        "airtableTokenApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "0856829d-5ae2-4af5-abb2-2f44b693eda4",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -64,
        -320
      ],
      "parameters": {
        "color": 2,
        "height": 496,
        "content": "### Who is it for?  \nThis workflow is perfect for **marketers, sales teams, agencies, and local businesses** who want to save time by automating **lead generation from Google Maps**.  \n\nIt\u2019s ideal for **real estate agencies, restaurants, service providers, and any local niche** that needs a clean database of fresh contacts, including emails, websites, and phone numbers.  "
      },
      "typeVersion": 1
    },
    {
      "id": "65e8fca8-b0dd-4243-a2b8-91874f80d870",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        176,
        -928
      ],
      "parameters": {
        "color": 4,
        "height": 1104,
        "content": "## Step 1 \u2013 Scraping Google Maps with Apify\n\nStart simply:\n\n- Open your n8n workflow and choose the trigger: \u201cExecute Workflow\u201d (manual trigger).  \n- Add an **HTTP Request node** (POST method).  \n- Go to [Apify Google Maps Extractor](https://apify.com/compass/google-maps-extractor).  \n- Fill in the fields:  \n\n  - Keyword: e.g., \"real estate agency\" (or restaurant, plumber...)  \n  - Location: \"Paris, France\"  \n  - Number of results: 50 (or more)  \n  - Optional: filters (with/without a website, by categories\u2026)  \n\n- Click **Run** to test the scraper.  \n- Then go to **API \u2192 Endpoints \u2192 Run Actor synchronously and get dataset items**.  \n- Copy the URL and paste it into your HTTP Request node (URL field).  \n- Enable:  \n\n  - Body Content Type \u2192 JSON  \n  - Specify Body Using JSON  \n\n- Go back to Apify, click the JSON tab, copy everything, and paste it into the JSON field of your HTTP Request.  \n"
      },
      "typeVersion": 1
    },
    {
      "id": "50426f1c-ef9b-4273-b58b-aa50c4ad2b4e",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        416,
        -416
      ],
      "parameters": {
        "color": 3,
        "width": 272,
        "height": 592,
        "content": "## Step 2 \u2013 Cleaning Things Up (Edit Fields)\n\nRaw data is cool, but messy.  \n\n- Add an **Edit Fields node** (Manual Mapping mode).  \n- Keep only these fields:  \n\nTitle \u2192 {{ $json.title }}\nAddress \u2192 {{ $json.address }}\nWebsite \u2192 {{ $json.website }}\nPhone \u2192 {{ $json.phone }}\nURL \u2192 {{ $json.url }}"
      },
      "typeVersion": 1
    },
    {
      "id": "ff53aa61-c496-4102-a652-5cc8c6729784",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        688,
        -400
      ],
      "parameters": {
        "color": 5,
        "height": 576,
        "content": "## Step 3 \u2013 Handling Each Contact Individually (Loop Over Items)\n\nNext, we process each contact one by one.  \n\n- Add the **Loop Over Items node**  \n- Set **Batch Size** to 20 (or more, depending on your needs).  \n\nThis step avoids traffic jams in the workflow."
      },
      "typeVersion": 1
    },
    {
      "id": "b444966c-2665-424d-918c-6ca2546743ed",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1184,
        -320
      ],
      "parameters": {
        "color": 7,
        "width": 256,
        "height": 496,
        "content": "## Step 5 \u2013 Scraping Each Website (HTTP Request)\n\n- Add another **HTTP Request node**  \n- Configure it as:  \n\nMethod: GET\nURL: {{ $json.website }}\n\nThis returns the raw HTML content of each website."
      },
      "typeVersion": 1
    },
    {
      "id": "79bf33aa-1a55-430b-a54f-1c240bb2f0f8",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1440,
        -608
      ],
      "parameters": {
        "width": 288,
        "height": 784,
        "content": "## Step 6 \u2013 Extracting Emails with ChatGPT\n\n- Add an **OpenAI (Message a Model) node**  \n- Configure as follows:  \n  - Model: GPT-4-1-mini (or higher)  \n  - Operation: Message a Model  \n  - Simplify Output: ON  \n\n**Prompt to copy-paste:**  \n\nLook at this website content and extract only the email I can contact this business.\nIn your output, provide only the email and nothing else.\nIdeally, this email should be of the business owner, so if you have 2 or more options,\ntry for the most authoritative one. If you don't find any email, output 'Null'.\n\nExemplary output of yours:\nname@examplewebsite.com\n\n{{ $json.data }}"
      },
      "typeVersion": 1
    },
    {
      "id": "0ccceeaf-5e73-4939-bb04-c82b484684f6",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1728,
        -544
      ],
      "parameters": {
        "color": 6,
        "width": 336,
        "height": 720,
        "content": "## Step 7 \u2013 Neatly Store Everything in Airtable\n\nAlmost done!  \n\n- Add an **Airtable \u2192 Create Record node**  \n- Map the fields like this:  \n\n| **Airtable Field** | **Content**                     | **n8n Variable**                           |\n| ------------------ | ------------------------------- | ------------------------------------------ |\n| Title              | Business name                   | `{{ $('Edit Fields').item.json.Title }}`   |\n| Street             | Full address                    | `{{ $('Edit Fields').item.json.Address }}` |\n| Website            | Website URL                     | `{{ $('Edit Fields').item.json.Website }}` |\n| Phone Number       | Phone number                    | `{{ $('Edit Fields').item.json.Phone }}`   |\n| Email              | Email retrieved by AI           | `{{ $json.message.content }}`              |\n| URL                | Google Maps link                | `{{ $('Edit Fields').item.json.URL }}`     |\n\nNow, you have a tidy Airtable database filled with fresh leads.  "
      },
      "typeVersion": 1
    },
    {
      "id": "4ea1da54-e1d2-4ddc-b503-0397715e5fb5",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        928,
        -256
      ],
      "parameters": {
        "color": 6,
        "width": 256,
        "height": 432,
        "content": "## Step 4 \u2013 Isolating Websites (Edit Fields again)\n\n- Add another **Edit Fields node** (Manual Mapping).  \n- Keep only:  \n\nWebsite \u2192 {{ $json.website }}"
      },
      "typeVersion": 1
    },
    {
      "id": "c0deaef6-ca0f-4e62-92b3-9ea1e6a40ab8",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        768,
        -832
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 288,
        "content": "![gif](https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExdnFscmdzNnZna2ppaWVjcWJneGQyYThqb2liOHZ6YzV0enI2ZTV3OSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/MC6eSuC3yypCU/giphy.gif)"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "27b24f55-047e-47c3-9c30-e22cfe4a71cd",
  "connections": {
    "Clean Google Maps Data": {
      "main": [
        [
          {
            "node": "Iterate Through Each Business Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Only Website URLs": {
      "main": [
        [
          {
            "node": "Fetch Raw HTML Content from Business Website",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Workflow Execution": {
      "main": [
        [
          {
            "node": "Fetch Business Data from Google Maps (Apify)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Cleaned Lead Data into Airtable": {
      "main": [
        []
      ]
    },
    "Iterate Through Each Business Contact": {
      "main": [
        [],
        [
          {
            "node": "Extract Only Website URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Business Data from Google Maps (Apify)": {
      "main": [
        [
          {
            "node": "Clean Google Maps Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Raw HTML Content from Business Website": {
      "main": [
        [
          {
            "node": "Extract Business Email from Website HTML (GPT-4)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Business Email from Website HTML (GPT-4)": {
      "main": [
        [
          {
            "node": "Save Cleaned Lead Data into Airtable",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

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

This workflow is for marketers, sales teams, and local businesses who want to quickly collect leads (business name, phone, website, and email) from Google Maps and store them in Airtable.

Source: https://n8n.io/workflows/5743/ — 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

This workflow is Part 2 of the HR Client Acquisition system and builds on the lead discovery pipeline from the previous workflow:

Google Sheets, HTTP Request, OpenAI +2
Web Scraping

Instead of guessing or relying on shallow placeholders, it scrapes real website content, summarizes it intelligently, and feeds that context into an LLM to produce outreach that feels relevant and hum

Chain Llm, Output Parser Structured, HTTP Request +3
Web Scraping

Product - SERP Analysis (Serper + Firecrawl). Uses formTrigger, httpRequest, googleSheets, openAi. Event-driven trigger; 40 nodes.

Form Trigger, HTTP Request, Google Sheets +1
Web Scraping

Product - SERP Analysis (Serper & Crawl4AI). Uses formTrigger, httpRequest, googleSheets, openAi. Event-driven trigger; 39 nodes.

Form Trigger, HTTP Request, Google Sheets +1
Web Scraping

Product - SERP Analysis (SerpAPI + Crawl4AI). Uses formTrigger, httpRequest, googleSheets, openAi. Event-driven trigger; 38 nodes.

Form Trigger, HTTP Request, Google Sheets +1