Track your Google Maps ranking across a geographic grid by running location-scoped search queries at multiple grid points and recording your business position in the results for each point. Local SEO depends on rank in the Google Maps pack, which varies significantly by searcher location. A rank grid reveals blind spots where your business drops out of the top results and identifies the geographic radius of your visibility. This tutorial builds a grid scanner using the Scavio API.
Prerequisites
- Python 3.8+ installed
- requests library installed
- A Scavio API key from scavio.dev
- Your business name and target service area coordinates
Walkthrough
Step 1: Define the geographic grid
Set up a grid of latitude/longitude points centered on your business location.
import os, requests, json
API_KEY = os.environ['SCAVIO_API_KEY']
BUSINESS_NAME = 'Your Business Name'
CENTER_LAT = 30.2672 # Austin, TX example
CENTER_LNG = -97.7431
GRID_SIZE = 5 # 5x5 grid
GRID_STEP = 0.02 # ~1.4 miles per step
def generate_grid(center_lat: float, center_lng: float, size: int, step: float) -> list:
points = []
half = size // 2
for i in range(-half, half + 1):
for j in range(-half, half + 1):
lat = round(center_lat + i * step, 4)
lng = round(center_lng + j * step, 4)
points.append({'lat': lat, 'lng': lng})
return points
grid = generate_grid(CENTER_LAT, CENTER_LNG, GRID_SIZE, GRID_STEP)
print(f'Grid: {len(grid)} points ({GRID_SIZE}x{GRID_SIZE})')Step 2: Query each grid point
Run a local search query at each grid point and find your business position in the results.
def search_at_location(query: str, lat: float, lng: float) -> list:
resp = requests.post('https://api.scavio.dev/api/v1/search',
headers={'x-api-key': API_KEY},
json={'platform': 'google', 'query': f'{query} near {lat},{lng}'}, timeout=15)
return resp.json().get('organic_results', [])
def find_rank(results: list, business_name: str) -> int:
name_lower = business_name.lower()
for i, r in enumerate(results):
title = r.get('title', '').lower()
if name_lower in title:
return i + 1
return 0 # Not found
results = search_at_location('plumber', CENTER_LAT, CENTER_LNG)
rank = find_rank(results, BUSINESS_NAME)
print(f'Rank at center: {rank if rank else "not found"}')Step 3: Record positions across the grid
Scan all grid points and record the rank at each location.
def scan_grid(query: str, business: str, grid: list) -> list:
scan_results = []
for i, point in enumerate(grid):
results = search_at_location(query, point['lat'], point['lng'])
rank = find_rank(results, business)
scan_results.append({
'lat': point['lat'],
'lng': point['lng'],
'rank': rank,
'total_results': len(results),
})
if (i + 1) % 5 == 0:
print(f'Scanned {i + 1}/{len(grid)} points')
return scan_results
# For demo, scan a small subset
small_grid = grid[:4]
results = scan_grid('plumber', BUSINESS_NAME, small_grid)
for r in results:
status = f'Rank {r["rank"]}' if r['rank'] else 'Not found'
print(f'({r["lat"]}, {r["lng"]}): {status}')Step 4: Compute rank changes over time
Compare today's scan with a previous scan to identify locations where rank improved or dropped.
def compare_scans(current: list, previous: list) -> list:
prev_map = {(p['lat'], p['lng']): p['rank'] for p in previous}
changes = []
for c in current:
key = (c['lat'], c['lng'])
prev_rank = prev_map.get(key, 0)
curr_rank = c['rank']
if prev_rank and curr_rank:
change = prev_rank - curr_rank # positive = improved
elif curr_rank and not prev_rank:
change = 'NEW'
elif prev_rank and not curr_rank:
change = 'LOST'
else:
change = 0
changes.append({**c, 'prev_rank': prev_rank, 'change': change})
return changes
# Example:
prev = [{'lat': 30.27, 'lng': -97.74, 'rank': 5}]
curr = [{'lat': 30.27, 'lng': -97.74, 'rank': 3, 'total_results': 10}]
for c in compare_scans(curr, prev):
print(f'({c["lat"]}, {c["lng"]}): {c["prev_rank"]} -> {c["rank"]} (change: {c["change"]})')Step 5: Visualize the rank grid
Generate a text-based heatmap of your rank across the grid for quick analysis.
def print_heatmap(scan_results: list, grid_size: int):
print(f'\nRank Heatmap ({grid_size}x{grid_size}):')
print('1-3=top, 4-7=mid, 8+=low, .=not found\n')
for i in range(grid_size):
row = []
for j in range(grid_size):
idx = i * grid_size + j
if idx < len(scan_results):
rank = scan_results[idx]['rank']
if rank == 0:
row.append('.')
elif rank <= 3:
row.append(str(rank))
elif rank <= 7:
row.append('m')
else:
row.append('L')
else:
row.append(' ')
print(' '.join(row))
# Demo with mock data
mock_scan = [{'rank': r} for r in [1,2,3,0,5, 2,1,4,3,6, 0,3,1,2,0, 5,4,3,2,7, 0,6,5,4,0]]
print_heatmap(mock_scan, 5)Python Example
import requests, os
H = {'x-api-key': os.environ['SCAVIO_API_KEY']}
def rank_at(query, lat, lng, business):
data = requests.post('https://api.scavio.dev/api/v1/search', headers=H,
json={'platform': 'google', 'query': f'{query} near {lat},{lng}'}).json()
for i, r in enumerate(data.get('organic_results', [])):
if business.lower() in r.get('title', '').lower():
return i + 1
return 0
print(f'Rank: {rank_at("plumber", 30.2672, -97.7431, "Your Business")}')JavaScript Example
const H = {'x-api-key': process.env.SCAVIO_API_KEY, 'Content-Type': 'application/json'};
async function rankAt(query, lat, lng, business) {
const r = await fetch('https://api.scavio.dev/api/v1/search', {
method: 'POST', headers: H,
body: JSON.stringify({platform: 'google', query: `${query} near ${lat},${lng}`})
});
const results = (await r.json()).organic_results || [];
const idx = results.findIndex(r => r.title?.toLowerCase().includes(business.toLowerCase()));
return idx >= 0 ? idx + 1 : 0;
}
rankAt('plumber', 30.2672, -97.7431, 'Your Business').then(console.log);Expected Output
A geographic grid scan that shows your Google Maps ranking at each location point, with change tracking over time and a text-based heatmap visualization.