A professional, high-speed Python command-line utility that extracts local business prospects directly from Google Maps without requiring API keys or quota fees.
The scraper targets businesses lacking website links (your ideal candidates for free website builds or modern design outreach), retrieves their public contact information, searches DuckDuckGo for their company contact email addresses, and persists results incrementally with automatic deduplication.
- System Features
- Prerequisites & System Requirements
- Step-by-Step Installation
- Comprehensive Usage Examples
- Understanding the Output Data Schema
- Data Sources & Attribution
- Legal & Responsible Use
- Troubleshooting & FAQs
- License
- Zero Maps API Fees: Leverages Playwright to scrape public Google Maps results in headless Chromium.
- Smart Filter: Automatically skips any local listing that already has an established website link.
- DuckDuckGo Email Locator: Searches public indexing dynamically for the company's contact email address.
- Incremental Saving & Deduplication: Saves lists progressively after every query to protect data and guarantees zero duplicates via URL and phone hashing.
- Self-Healing County Database: Ships with (and can re-download) a local list of 3,048 counties covering the 50 U.S. states, for seamless state-wide target queries. Note the database covers the 50 states only: Washington D.C. and the U.S. territories are not included, so target those with
--countiesor--queryinstead. - Highly Customizable CLI: Run campaigns easily across predefined state arrays, custom county list strings, or custom search queries.
- Operating System: macOS, Linux, or Windows (tested and optimized for macOS & Linux environments).
- Python: Python 3.11 or newer is required. pandas 3.x dropped support for anything below 3.11. Verified working on Python 3.14.
- macOS ships a system
python3that is still 3.9, which will not work. Install a modern interpreter first:brew install python@3.12(or newer) and build your virtual environment from that binary.
- macOS ships a system
- Node.js (Optional): Only required if connecting to the downstream SMS outreach client.
Follow these exact steps to set up your local environment and download the headless browser binaries.
Clone the repository and navigate into the project directory:
git clone https://github.com/Robj1925/google-maps-scraper.git
cd google-maps-scraperIsolate your Python dependencies using a local virtual environment.
# Create a virtual environment named "venv" using the system python3
python3 -m venv venv
# Activate the virtual environment
# On macOS and Linux:
source venv/bin/activate
# On Windows (Command Prompt):
# venv\Scripts\activate.bat
# On Windows (PowerShell):
# venv\Scripts\Activate.ps1Upgrade pip and install the mandatory Python packages (playwright and pandas):
pip install --upgrade pip
pip install -r requirements.txtDownload and configure the headless Chromium binary required for crawling Google Maps:
playwright install chromiumExecute campaigns dynamically by running commands inside your active virtual environment.
No API keys and no .env file are needed. The scraper reads no environment variables at all: everything is driven by CLI flags.
The fastest way to try the tool. A single search query runs directly against Google Maps and bypasses the state and county loops entirely:
# Search for hair salons specifically in Salem, NY
python scraper.py --query "hair salons in Salem, NY" --max-results 5Warning
Read this before running a full-state campaign.
A state-wide run is not a quick job. --state TX expands to 254 separate county queries. Each query loads a Google Maps search page, scrolls the results feed roughly 15 times, then opens every matching place page individually, and each lead that passes the no-website filter also triggers a DuckDuckGo lookup. That is on the order of 16+ page loads per county before per-lead traffic, so a full state means hours of continuous automated requests from your IP.
Sustained volume like that is the fastest route to a CAPTCHA wall or a temporary block, and it is a real load on someone else's servers. Start narrow with --query or --counties, keep --max-results low, and leave --delay at its default (or raise it). Run a whole state only when you actually need it.
Crawl an entire state's counties for a specific sector. The script resolves state abbreviations or full names automatically and uses the bundled counties_by_state.json offline:
# Scrape roofing contractors across all 254 counties in Texas
python scraper.py --industry "roofing" --state TX
# Scrape construction companies across all 62 counties in New York
python scraper.py --industry "construction" --state "New York"
# Scrape plumbing services across all 21 counties in New Jersey
python scraper.py --industry "plumbing" --state NJTo scrape only specific counties or municipal regions, pass a comma-separated list to the --counties flag:
# Scrape landscaping businesses in specific counties of New Jersey and New York
python scraper.py --industry "landscaping" --counties "Bergen County NJ, Orange County NY, Westchester County NY"Control your crawl speed and resource consumption by specifying the max results to fetch per location loop using --max-results (default is 10):
# Get at most 3 electrical contractors per county in New Jersey
python scraper.py --industry "electrician" --state NJ --max-results 3Specify a custom destination CSV file path using --csv (default is leads.csv):
# Save scraped roofing leads to a distinct target sheet
python scraper.py --industry "roofing" --state NJ --csv "new_jersey_roofers.csv"--delay sets the pause in seconds between individual place pages (default is 2.0). The default is deliberately conservative. Raising it is safe, lowering it is not:
# Go slower and gentler on a long campaign
python scraper.py --industry "roofing" --state NJ --delay 5All scraped leads are appended progressively to your output CSV file. The CSV contains the following columns:
| Column Name | Example Value | Description |
|---|---|---|
Name |
Example Roofing Co |
The public business name listed on Google Maps. |
Phone |
(555) 010-0000 |
The public phone number listed on Google Maps (not validated). |
Address |
123 Main St, Springfield, IL 62701 |
The public street address of the business. |
Email |
info@example.com |
Scraped email address from DuckDuckGo contact index (N/A if not found). |
URL |
https://www.google.com/maps/place/... |
The direct Google Maps page link (used for indexing/deduplication). |
The bundled counties_by_state.json county database is not original work. It comes from the open balsama/us_counties_data project, and the scraper re-downloads it from that repository if the local copy is missing (see the URL in scraper.py). Full credit to that project and its contributors.
If you redistribute this tool or the county data with it, check the upstream repository's own license and terms first. They govern that file, not the MIT license below.
This tool automates access to public web pages. That does not make every use of it lawful or permitted. Read this section before you run it.
- Google's Terms of Service. Scraping Google Maps may violate Google's Terms of Service, regardless of whether the data itself is public. Google also offers a paid Places API for programmatic access. You are solely responsible for deciding whether your use is acceptable, and for any consequence of that decision, including IP blocks or account action.
- The data you collect is regulated. Business names, phone numbers, email addresses, and street addresses are contact data, and using them for outreach is governed by law in most places. Depending on your jurisdiction and the recipient's, that can include CAN-SPAM (commercial email in the U.S.), the TCPA (calls and SMS in the U.S., including to numbers on the National Do Not Call Registry), and the GDPR or equivalent regimes (anywhere EU/UK/EEA data subjects are involved, where even business contact data can be personal data). Establish a lawful basis before you send anything cold, identify yourself honestly, and honour opt-out and unsubscribe requests promptly and permanently.
- Do not lower the delays. The default
--delaybetween place pages and the fixed pause between queries are deliberately conservative. They exist so this tool behaves like a considerate client rather than a denial-of-service source. Turning them down makes blocks more likely, and it pushes your cost onto someone else's infrastructure. - Do not resell harvested lists. This tool is for building your own prospect list for your own outreach. Selling, renting, or redistributing scraped contact data is a separate and considerably riskier activity, and it is not what this project is for.
- No warranty. This software is provided as-is, without warranty of any kind, express or implied. Selectors break when Google changes its markup, results are incomplete by nature, and email matches from DuckDuckGo are best-effort guesses that can be wrong. Verify anything you act on. See the LICENSE for the full disclaimer.
Nothing here is legal advice. If you are running outreach at any real scale, talk to a lawyer who knows your jurisdiction.
- A: DuckDuckGo has temporarily flagged your IP address for high-volume automated searching. The scraper is built to handle this gracefully: it logs the error, sets the
Emailcolumn toN/A, and continues crawling phone numbers and addresses without stopping.
- A: You missed installing the browser binaries. Run the following command inside your active virtual environment to download Chromium:
playwright install chromium
- A: Yes. Ensure your cron script activates the virtual environment first:
#!/bin/bash cd /path/to/google-maps-scraper source venv/bin/activate python scraper.py --industry "construction" --state NJ --max-results 5
Released under the MIT License. See the LICENSE file for the full text, including the no-warranty and no-liability disclaimer.
Copyright (c) 2026 Robby J (github.com/Robj1925).
The bundled county database is third-party data. See Data Sources & Attribution.