How to scrape Google My Business leads for FREE

Before we get into the technical (not too technical) part of this, it’s worth knowing that there is an easy to follow video version of this document going over exactly how to use this tool:

Before you start:

  1. You need a google workspace account for this to work.
  2. This is a free solution, you don’t have to pay for tools like Outscraper, but it is a little harder to use.
  3. DM me if you have any problems with this. I will help you use it.

Step by Step process:

  1. Open a new Google Sheet. (this is the only Google Sheet where your results will return to)
  2. Go to ‘Extensions’ → ‘Apps Script’
  3. Go to the file and paste the code from the first code block (see below)
  4. Next, create an HTML file and name it “index.html”
  5. Paste the code from the second code block (see below)
  6. Go to the Google Cloud Console.
  7. Int eh search bar, search for the “Places API” page.
  8. Enable the Google Places API and make a new API key.
  9. Input the API key you generated into the script of the file.
  10. Run and save the script.
  11. Make a web app deployment.
  12. Grant access to the app.
  13. Copy the link, open in a new tab.
  14. Input keywords, press “Scrape Info.”
  15. Ensure info is being returned to Google Sheet.

Code.js file

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index.html');

function scrapeBusinessInfoForMultipleKeywords(keywords) {
  keywords.forEach(function(keyword, index) {
    Utilities.sleep(1000 * index); // Delay to avoid hitting API limits

function scrapeBusinessInfo(keyword, nextPageToken = '') {
  const apiKey = 'API_KEY_HERE'; // Replace with your actual API key
  let apiUrl = `${encodeURIComponent(keyword)}&key=${apiKey}`;

  if (nextPageToken) {
    apiUrl += `&pagetoken=${nextPageToken}`;

  const response = UrlFetchApp.fetch(apiUrl, {muteHttpExceptions: true});
  const json = JSON.parse(response.getContentText());

  switch (json.status) {
    case 'OK':
      processBusinessResults(json.results, apiKey);
      if (json.next_page_token) {
        Utilities.sleep(2000); // Delay for API's next page token availability
        scrapeBusinessInfo(keyword, json.next_page_token);
    case 'OVER_QUERY_LIMIT':
      Logger.log('Query Limit Reached');
      Logger.log(`Error: ${json.status}`);

function processBusinessResults(results, apiKey) {
  results.forEach(function(business) {
    const details = getBusinessDetails(business.place_id, apiKey);
    const data = [,
      business.formatted_address, || 'N/A',
      details.formatted_phone_number || 'N/A',

function getBusinessDetails(placeId, apiKey) {
  const detailsUrl = `${placeId}&fields=formatted_phone_number,website&key=${apiKey}`;
  const response = UrlFetchApp.fetch(detailsUrl, {muteHttpExceptions: true});
  const json = JSON.parse(response.getContentText());
  return json.result || {};

function appendDataToSheet(data) {
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();


<!DOCTYPE html>
    <title>Google My Business Scraper</title>
        body {
            font-family: Arial, sans-serif;
            padding: 20px;
            background-color: #f0f0f0;
        #container, .videos-container {
            background-color: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
            transition: all 0.3s ease-in-out;
            margin-bottom: 20px;
        .center {
            display: flex;
            justify-content: center;
            flex-direction: column;
            align-items: center;
            text-align: center;
        textarea, button {
            width: calc(100% - 22px);
            padding: 10px;
            margin-bottom: 10px;
            border-radius: 5px;
            border: 1px solid #ccc;
            box-sizing: border-box;
        textarea:focus, button:focus {
            border-color: #feb650;
            outline: none;
        button {
            background-color: #feb650;
            color: white;
            cursor: pointer;
            transition: background-color 0.3s ease-in-out, transform 0.2s ease-in-out;
        button:hover {
            background-color: darken(#feb650, 5%);
            transform: translateY(-2px);
        #message {
            opacity: 0;
            transition: opacity 2s ease-in-out;
        .videos-row {
            display: flex;
            justify-content: center;
            gap: 20px; /* Adjust the space between videos */
            flex-wrap: wrap;
        .video-wrapper {
            padding-top: 177.77%; /* 9:16 Aspect Ratio */
            position: relative;
            flex-basis: 20%; /* Adjust based on desired video size, keeping the aspect ratio */
            min-width: 120px; /* Minimum width to maintain usability */
        .video-wrapper iframe {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            border: 0;
        footer {
            text-align: center;
            margin-top: 20px;
        a {
            color: #feb650;
            text-decoration: none;
            transition: color 0.3s ease-in-out;
        a:hover {
            color: darken(#feb650, 10%);
    <div id="container" class="center">
        <h2>Scrape Google My Business Info</h2>
        <textarea id="keywords" placeholder="Enter keywords, separated by commas"></textarea>
        <button onclick="scrapeInfo()">Scrape Info</button>
        <div id="message"></div>

        Built by <a href="" target="_blank">@makowskialex_</a> from <a href="" target="_blank"></a>

      function scrapeInfo() {
        var keywords = document.getElementById('keywords').value.split(',');
        if(keywords[0] === "") {
            showMessage("Please enter at least one keyword.", "error");
            showMessage("Scraping initiated. Check the sheet for updates.", "success");
            showMessage("Error: " + err, "error");

      function showMessage(message, type) {
        var messageDiv = document.getElementById('message');
        messageDiv.innerText = message; = type === "error" ? "red" : "green"; = 1; // Trigger fade-in

And that’s really it – hopefully you were able to navigate through the process without any problems.

Here are some more videos on lead generation that might be helpful for you:

Alex Makowski
Alex Makowski
End-To-End Lead Generation Specialist and Serial Entreprenuer. I help marketing agencies remove the burden of not having consistent leads and bring stability to their sales call calendar Quality booked aales calls on autopilot using cold email.