Scrap (Google Maps)
Source module that extracts Google Maps listings for a set of search queries across one or more geographic zones, in any of ~199 countries.
Purpose
Source module: runs each query over every grid point covering the requested zones and returns a flat CSV of Google Maps establishments (name, contact, location, rating).
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
queries |
string[] (1–20) |
yes | Search terms run against Google Maps. Each query is trimmed and capped at 200 chars. |
country |
string (ISO3) |
no | Target country as an ISO-3166 alpha-3 code (e.g. FRA, USA, DEU). Defaults to "FRA". Determines both the GPS grid and how zones are resolved. |
zones |
string[] (1–50) |
yes | Geographic zones expressed within country: a state/region (adm1), a county/department (adm2), a city with an optional radius ("Austin 30km"), or the country name itself to scrape the whole country. For France, INSEE codes, department codes and postal codes remain valid. Each zone is resolved to a grid of points server-side. |
include_reviews |
bool |
no | Kept for backward compatibility. Does not chain a reviews job — use the reviews module instead. Defaults to false. |
max_per_phone |
int (0–10) |
no | Maximum listings kept per phone number. 0 (default) = no limit; 1 = a single contact per number. Listings with no number are never dropped. The same listing never appears twice regardless. See Duplicates. |
Request body:
{
"queries": ["plombier", "chauffagiste"],
"country": "FRA",
"zones": ["75", "92"],
"include_reviews": false
}
Targeting another country instead — zones are expressed in that country:
{
"queries": ["plumber", "hvac"],
"country": "USA",
"zones": ["Texas", "Austin 30km"]
}
Effective Google Maps requests = len(queries) × grid_points(zones). Rejected at submit if cost exceeds the per-job EF ceiling.
Outputs
Result file: UTF-8 CSV, semicolon delimiter, BOM (Excel-safe). Same dataset available in three formats via download endpoint.
| Column | Type | Description |
|---|---|---|
nom |
string | Establishment name as displayed on Google Maps. |
site_web |
string | Public website URL if listed. |
telephone |
string | Phone number as listed. |
adresse |
string | Street address (number + street) as shown in the Maps list. |
ville |
string | City, taken from Google's own structured place data (exact, including Paris/Lyon/Marseille arrondissements and multi-postcode cities). Empty for listings Google has no address for. |
code_postal |
string | Postal code, from the same Google source as ville. |
rating |
float | Average star rating (0.0–5.0). |
reviews_count |
int | Number of public reviews. |
category |
string | Primary Google Maps category. |
lien_google_maps |
string | Canonical Google Maps URL for the listing. |
aggregator_flag |
bool | True if the listing looks like a directory/aggregator rather than an end business. |
query |
string | Source query that produced the row. |
lat, lon |
float | Grid point at which the row was collected. |
villeandcode_postalcome from the structured place data Google ships with each result, not from reverse-geocoding — so they match Google exactly. The Maps list view only renders the street, which is whyadressealone never carried the city.
Optional columns
Three extra columns are off by default and enabled per job via extra_columns (a list). The default output is street + ville + code_postal.
Option in extra_columns |
Adds column(s) | Description |
|---|---|---|
gps |
lat, lon |
Exact latitude/longitude of the business (Google's own coordinates — not a grid approximation). |
departement |
departement |
Department/county name (adm2). In France, derived from code_postal; in any other country, derived from the business GPS via world reverse-geocoding. |
region |
region |
Region/state name (adm1). In France, derived from code_postal; in any other country, derived from the business GPS via world reverse-geocoding. |
Example request body: { "queries": ["plumber"], "zones": ["Paris 10km"], "extra_columns": ["gps", "departement", "region"] }.
Duplicates
One listing = one row. Always, with no setting to turn it off. A business can sit within reach of several GPS grid points and match several of your queries, and Google's listing URL is not a stable identity — it also encodes the search context, so two captures of the same place come back with different URLs. Rows are therefore deduplicated on the 0x…:0x… feature id Google carries inside that URL (falling back to normalised name + address when it is absent). This is a correctness guarantee, not a preference.
max_per_phone — the one thing you can tune. Different listings sometimes share a phone number: a shared switchboard, an owner running several outlets, or a network of near-identical local listings pointing at one call centre. For a chain those are genuinely distinct establishments; for prospecting they are one contact. That trade-off is yours to make:
| Value | Behaviour |
|---|---|
0 (default) |
No limit — every listing is kept, whatever number it shows. |
1 |
One listing per phone number; the first one found wins. |
2, 3, … (max 10) |
Up to N listings per number. |
Numbers are compared in E.164 form, so +33 1 87 58 88 84 and 01 87 58 88 84 are the same number; the job's country sets the region used to parse national formats. Listings with no usable number are never dropped by this setting.
The quota applies across the whole job, queries and zones included, and is preserved when a cancelled job is resumed. Deduplication never changes which grid points get scraped, so it has no effect on EF cost or runtime.
Formats: csv (original), json, xlsx. Selected via ?format= on the download endpoint.
Lifecycle
Standard job lifecycle: see Jobs & lifecycle. While running, the SSE status event carries a query_stats payload of shape { "<query>": { "tiles": int, "with_results": int } }, updated in real time to expose per-query hit ratio.
Pipeline
| Field | Value |
|---|---|
needs |
null (source module — no input CSV required) |
produces |
poi_list |
Typical downstream modules chained against a scrap output:
emails— find professional and personal emails fromsite_web.socials— extract social network handles fromsite_web.legal_ids— extract SIREN/SIRET from the establishment's website (legal-mentions page).reviews— collect full review threads fromlien_google_maps.techstack,dead_check,brand_assets,ads_intelligence— site-level enrichments keyed onsite_web.
Endpoints
Dedicated endpoint:
POST /api/jobs
Content-Type: application/json
{
"queries": ["plombier"],
"zones": ["75"],
"include_reviews": false
}
Generic job endpoint (equivalent — same payload, job_type inferred from shape):
POST /api/jobs
Content-Type: application/json
{
"job_type": "scrap",
"queries": ["plombier"],
"zones": ["75"]
}
Both responses return the created JobPublic object including id, status, grid_points_count, ef_cost and output_filename.
Download:
GET /api/jobs/{job_id}/download?format=csv|json|xlsx
Limits
Platform-wide quotas: see /docs/concepts/limits. Module-specific caps:
| Limit | Value |
|---|---|
| Maximum queries per job | 20 |
| Maximum zones per job | 50 |
| Maximum query length | 200 chars |
| Maximum cost per job | 5 EF by default (per user, adjustable up to 50) |
| Email verification | Required on the account before a scrap job can be created. |
Errors
| Scenario | HTTP | Resolution |
|---|---|---|
| Unrecognised zone string | 400 | Inspect the errors array in the response body. Check the zone is spelled as it exists in the selected country (a state/region, county/department, "City 30km", or the country name). For France, INSEE/department/postal codes and "France" also work. |
| No grid points resolved | 400 | The zone set is empty after resolution — broaden the zone selection. |
| EF quota exceeded | 400 | Reduce the number of queries or shrink the zones until estimated EF is within your per-job ceiling (5 by default, adjustable up to 50 in Settings). |
| Email not verified | 403 | Verify the account email before creating a scrap job. |
| No worker available | The job stays in pending until the shared multi-proxy pool is free. Only one multi-proxy job runs at a time platform-wide. |
|
| Job failed mid-run | A partial CSV is preserved. A POST /api/jobs/{id}/resume creates a follow-up job that skips already-processed grid points and is billed only for the remainder. |
|
| Download expired | 410 | Result files have a retention window — re-run the job or chain from a fresh source. |
Queries refused by Google Maps surface in dead_queries on the job object.
Data sources & attribution
Worldwide zone resolution and reverse-geocoding are built on open datasets:
Worldwide administrative boundaries © geoBoundaries (CC BY 4.0) · Cities © GeoNames (CC BY 4.0).