Skip to content

SOP: Local SEO Data Collection Fresh

Local SEO requires data from three overlapping sources: the local pack (map results), individual business data (reviews, attributes), and the local finder (expanded local results). This SOP covers all three.

Data Sources Overview

SourceAPI SectionWhat You Get
Local PackSERP API — Google Maps3-pack positions, ratings, reviews count
Business DataBusiness Data APIFull GMB data, reviews, photos, attributes
Local FinderSERP API — Local FinderExpanded local results beyond the 3-pack

Step 1: Pull Local Pack Data (3-Pack)

The local pack is the map block that appears for location-intent queries. This shows who ranks in the 3-pack and their metrics.

python
import os
import requests
from requests.auth import HTTPBasicAuth

auth = HTTPBasicAuth(
    os.environ["DATAFORSEO_LOGIN"],
    os.environ["DATAFORSEO_PASSWORD"]
)

target_keywords = [
    "roofing contractor miami",
    "roof repair near me",
    "emergency roofing miami fl"
]

# Use Google Maps SERP to get local pack data
local_pack_payload = [
    {
        "keyword": keyword,
        "location_code": 1015116,  # Miami, FL
        "language_code": "en",
        "device": "desktop",
        "depth": 10
    }
    for keyword in target_keywords
]

response = requests.post(
    "https://api.dataforseo.com/v3/serp/google/maps/live/advanced",
    auth=auth,
    json=local_pack_payload
)

data = response.json()

# Parse local pack results
for task in data["tasks"]:
    keyword = task["data"]["keyword"]
    items = task["result"][0].get("items", []) if task.get("result") else []
    
    print(f"\nLocal Pack: {keyword}")
    print(f"{'Pos':>4} {'Business Name':<40} {'Rating':>6} {'Reviews':>8}")
    print("-" * 65)
    
    for item in items[:10]:
        if item.get("type") != "maps_search":
            continue
        
        pos = item.get("rank_absolute", "-")
        name = (item.get("title") or "")[:39]
        rating = item.get("rating", {}).get("value", "-")
        reviews = item.get("rating", {}).get("votes_count", 0)
        
        print(f"{pos:>4} {name:<40} {str(rating):>6} {reviews:>8,}")

Step 2: Get Detailed Business Data

Once you have business names from the local pack, pull detailed data using the Business Data API.

python
def get_business_data(auth, business_name, location, limit=1):
    """Get full GMB data for a specific business."""
    
    payload = [
        {
            "keyword": business_name,
            "location_name": location,
            "language_name": "English"
        }
    ]
    
    response = requests.post(
        "https://api.dataforseo.com/v3/business_data/google/my_business_info/live",
        auth=auth,
        json=payload
    )
    
    data = response.json()
    task = data["tasks"][0]
    
    if not task.get("result"):
        return None
    
    return task["result"][0]

# Example: get data for a specific competitor
biz_data = get_business_data(auth, "Acme Roofing Miami", "Miami, Florida, United States")

if biz_data:
    print(f"\nBusiness: {biz_data.get('title')}")
    print(f"Address: {biz_data.get('address')}")
    print(f"Phone: {biz_data.get('phone')}")
    print(f"Website: {biz_data.get('url')}")
    print(f"Rating: {biz_data.get('rating', {}).get('value')} ({biz_data.get('rating', {}).get('votes_count')} reviews)")
    print(f"Category: {biz_data.get('category')}")
    hours = biz_data.get("work_hours", {})
    if hours:
        print(f"Hours: {hours}")

Step 3: Pull Business Reviews

Use the Business Data reviews endpoint to pull recent reviews for sentiment analysis.

python
def get_business_reviews(auth, business_name, location, limit=50):
    """Pull customer reviews for a GMB listing."""
    
    payload = [
        {
            "keyword": business_name,
            "location_name": location,
            "language_name": "English",
            "depth": limit,
            "sort_by": "newest"
        }
    ]
    
    response = requests.post(
        "https://api.dataforseo.com/v3/business_data/google/reviews/live",
        auth=auth,
        json=payload
    )
    
    data = response.json()
    task = data["tasks"][0]
    
    if not task.get("result"):
        return []
    
    reviews = []
    for item in task["result"][0].get("items", []):
        reviews.append({
            "rating": item.get("rating", {}).get("value"),
            "date": item.get("timestamp"),
            "text": item.get("review_text", ""),
            "author": item.get("author_title"),
            "owner_reply": item.get("owner_answer")
        })
    
    return reviews

reviews = get_business_reviews(auth, "Acme Roofing Miami", "Miami, Florida, United States")

if reviews:
    ratings = [r["rating"] for r in reviews if r["rating"]]
    avg = sum(ratings) / len(ratings) if ratings else 0
    
    print(f"\nReviews pulled: {len(reviews)}")
    print(f"Average rating: {avg:.1f}")
    
    # Distribution
    from collections import Counter
    dist = Counter(r["rating"] for r in reviews if r["rating"])
    for star in [5, 4, 3, 2, 1]:
        count = dist.get(star, 0)
        bar = "█" * count
        print(f"  {star}★: {count:>3} {bar}")

Step 4: Pull Local Finder Results

The Local Finder shows the expanded list beyond the 3-pack. Useful for tracking positions 4–20.

python
def get_local_finder_results(auth, keyword, location_code, depth=20):
    """Get Local Finder results (expanded local list)."""
    
    payload = [
        {
            "keyword": keyword,
            "location_code": location_code,
            "language_code": "en",
            "depth": depth
        }
    ]
    
    response = requests.post(
        "https://api.dataforseo.com/v3/serp/google/local_finder/live/advanced",
        auth=auth,
        json=payload
    )
    
    data = response.json()
    task = data["tasks"][0]
    
    results = []
    if task.get("result"):
        for item in task["result"][0].get("items", []):
            if item.get("type") == "local_finder":
                results.append({
                    "position": item.get("rank_absolute"),
                    "name": item.get("title"),
                    "rating": item.get("rating", {}).get("value"),
                    "reviews": item.get("rating", {}).get("votes_count", 0),
                    "address": item.get("address"),
                    "category": item.get("category"),
                    "phone": item.get("phone"),
                    "url": item.get("url")
                })
    
    return results

finder_results = get_local_finder_results(auth, "roofing contractor miami", 1015116, depth=20)

print(f"\nLocal Finder — Top {len(finder_results)} results:")
print(f"{'#':>3} {'Business':<35} {'Rating':>6} {'Reviews':>8}")
print("-" * 57)
for r in finder_results[:20]:
    name = (r["name"] or "")[:34]
    rating = r["rating"] or "-"
    print(f"{r['position']:>3} {name:<35} {str(rating):>6} {r['reviews']:>8,}")

Step 5: Combine for Local SEO Audit

Merge all three data sources into a single competitive view.

python
def build_local_seo_report(target_domain, local_pack_items, finder_results, biz_data, reviews):
    """Combine data sources into a unified local SEO snapshot."""
    
    # Find target in local pack
    target_pack_position = None
    for item in local_pack_items:
        if target_domain.lower() in (item.get("url") or "").lower():
            target_pack_position = item.get("rank_absolute")
            break
    
    # Find target in finder
    target_finder_position = None
    for item in finder_results:
        if target_domain.lower() in (item.get("url") or "").lower():
            target_finder_position = item["position"]
            break
    
    report = {
        "domain": target_domain,
        "pack_position": target_pack_position or "Not in 3-pack",
        "finder_position": target_finder_position or "Not in top 20",
        "rating": biz_data.get("rating", {}).get("value") if biz_data else None,
        "review_count": biz_data.get("rating", {}).get("votes_count") if biz_data else None,
        "review_avg_from_api": sum(r["rating"] for r in reviews if r["rating"]) / len(reviews) if reviews else None,
        "recent_reviews": len(reviews),
        "has_owner_replies": sum(1 for r in reviews if r.get("owner_reply")) > 0
    }
    
    print("\nLOCAL SEO SNAPSHOT")
    print("=" * 40)
    for k, v in report.items():
        print(f"{k:<25}: {v}")
    
    return report

# Build the report
report = build_local_seo_report(
    target_domain="acmeroofingmiami.com",
    local_pack_items=local_pack_items if 'local_pack_items' in dir() else [],
    finder_results=finder_results,
    biz_data=biz_data,
    reviews=reviews
)

Location Code Reference for Local SEO

For local SEO work, use the city-level location codes — not country codes. City codes return localized results.

python
# Get location codes for a city
payload = [{"location_name": "Miami,Florida,United States"}]

response = requests.post(
    "https://api.dataforseo.com/v3/serp/google/locations",
    auth=auth,
    json=payload
)

# Or use known codes:
LOCAL_CODES = {
    "Miami, FL": 1015116,
    "New York, NY": 1023191,
    "Los Angeles, CA": 1013962,
    "Chicago, IL": 1016367,
    "Houston, TX": 1014927,
    "Phoenix, AZ": 1023080,
    "Philadelphia, PA": 1023273,
    "San Antonio, TX": 1026277,
    "San Diego, CA": 1014271,
    "Dallas, TX": 1014419,
}

Key Metrics for Local SEO Audits

MetricGreenYellowRed
Local pack position1–34–10Not ranked
Rating4.5+4.0–4.4< 4.0
Review count100+25–99< 25
Owner reply rate> 80%40–80%< 40%
Reviews in last 30 days5+1–40

Internal SOP reference — not affiliated with DataForSEO.