Building lead lists from Google Maps is a core workflow for local agencies, franchise prospectors, and B2B sales teams. The manual process -- searching one category in one city, copying results, repeating -- does not scale past a handful of markets. The Scavio API accepts Google Maps queries and returns structured JSON with business name, address, phone, rating, review count, and website. This tutorial shows how to loop through a matrix of categories and cities, collect all results, and write them to a single CSV file ready for CRM import or outreach tooling.
Prerequisites
- Python 3.8 or higher installed
- requests and csv modules (csv is in the standard library)
- A Scavio API key from scavio.dev
- A list of target categories and cities
Walkthrough
Step 1: Define the category and city matrix
Create two lists representing the categories and cities you want to cross-query. The script will search every combination.
categories = ["plumber", "electrician", "hvac contractor"]
cities = ["Austin TX", "Denver CO", "Portland OR"]Step 2: Query Google Maps for each combination
POST to the Scavio search endpoint with platform set to google_maps and the query combining category and city. Collect all local_results into a flat list.
import os
import requests
API_KEY = os.environ.get("SCAVIO_API_KEY", "your_scavio_api_key")
ENDPOINT = "https://api.scavio.dev/api/v1/search"
all_results = []
for category in categories:
for city in cities:
query = f"{category} in {city}"
response = requests.post(
ENDPOINT,
headers={"x-api-key": API_KEY},
json={"query": query, "platform": "google_maps"}
)
data = response.json()
for biz in data.get("local_results", []):
biz["search_category"] = category
biz["search_city"] = city
all_results.append(biz)
print(f"{query}: {len(data.get('local_results', []))} results")Step 3: Write results to CSV
Use the csv module to write all collected business records to a file. Include columns for name, address, phone, website, rating, review count, and the search metadata.
import csv
fields = ["search_category", "search_city", "title", "address", "phone", "website", "rating", "reviews"]
with open("google_maps_export.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fields, extrasaction="ignore")
writer.writeheader()
writer.writerows(all_results)
print(f"Exported {len(all_results)} businesses to google_maps_export.csv")Step 4: Deduplicate by phone or address
Google Maps results across overlapping queries may include duplicates. Remove them by keying on phone number before writing the final CSV.
seen = set()
unique = []
for biz in all_results:
key = biz.get("phone") or biz.get("address", "")
if key and key not in seen:
seen.add(key)
unique.append(biz)
print(f"Deduplicated: {len(all_results)} -> {len(unique)} businesses")
all_results = uniquePython Example
import os
import csv
import requests
API_KEY = os.environ.get("SCAVIO_API_KEY", "your_scavio_api_key")
ENDPOINT = "https://api.scavio.dev/api/v1/search"
def fetch_maps(query: str) -> list[dict]:
r = requests.post(
ENDPOINT,
headers={"x-api-key": API_KEY},
json={"query": query, "platform": "google_maps"}
)
r.raise_for_status()
return r.json().get("local_results", [])
def batch_export(categories: list[str], cities: list[str], output: str) -> None:
all_biz = []
for cat in categories:
for city in cities:
results = fetch_maps(f"{cat} in {city}")
for biz in results:
biz["category"] = cat
biz["city"] = city
all_biz.extend(results)
print(f"{cat} in {city}: {len(results)} results")
seen, unique = set(), []
for biz in all_biz:
key = biz.get("phone") or biz.get("address", "")
if key and key not in seen:
seen.add(key)
unique.append(biz)
fields = ["category", "city", "title", "address", "phone", "website", "rating", "reviews"]
with open(output, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fields, extrasaction="ignore")
writer.writeheader()
writer.writerows(unique)
print(f"Exported {len(unique)} unique businesses to {output}")
if __name__ == "__main__":
batch_export(
["plumber", "electrician", "hvac contractor"],
["Austin TX", "Denver CO", "Portland OR"],
"google_maps_export.csv"
)JavaScript Example
const API_KEY = process.env.SCAVIO_API_KEY || "your_scavio_api_key";
const ENDPOINT = "https://api.scavio.dev/api/v1/search";
const fs = require("fs");
async function fetchMaps(query) {
const res = await fetch(ENDPOINT, {
method: "POST",
headers: { "x-api-key": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({ query, platform: "google_maps" })
});
const data = await res.json();
return data.local_results || [];
}
async function batchExport(categories, cities, output) {
const all = [];
for (const cat of categories) {
for (const city of cities) {
const results = await fetchMaps(`${cat} in ${city}`);
results.forEach(b => { b.category = cat; b.city = city; });
all.push(...results);
console.log(`${cat} in ${city}: ${results.length} results`);
}
}
const seen = new Set();
const unique = all.filter(b => {
const key = b.phone || b.address || "";
if (key && !seen.has(key)) { seen.add(key); return true; }
return false;
});
const header = "category,city,title,address,phone,website,rating,reviews";
const rows = unique.map(b =>
[b.category, b.city, b.title, b.address, b.phone, b.website, b.rating, b.reviews]
.map(v => `"${(v || "").toString().replace(/"/g, '""')}"`).join(",")
);
fs.writeFileSync(output, [header, ...rows].join("\n"));
console.log(`Exported ${unique.length} unique businesses to ${output}`);
}
batchExport(
["plumber", "electrician", "hvac contractor"],
["Austin TX", "Denver CO", "Portland OR"],
"google_maps_export.csv"
).catch(console.error);Expected Output
{
"local_results": [
{
"position": 1,
"title": "Radiant Plumbing & Air Conditioning",
"address": "9521 US-290 W, Austin, TX 78736",
"phone": "(512) 555-0147",
"website": "https://radiantplumbing.com",
"rating": 4.9,
"reviews": 3842,
"hours": "Open 24 hours",
"type": "Plumber"
}
]
}