Tutorial: Local SEO Audit for Multi-Location Businesses

This tutorial walks you through conducting a comprehensive local SEO audit for businesses with multiple locations.

Prerequisites

Step 1: Audit Google Business Profiles

import csv

def audit_gbp_profiles(locations_file):
    with open(locations_file, "r") as f:
        locations = csv.DictReader(f)
    audit_results = []
    for loc in locations:
        checks = {
            "name_consistent": bool(loc.get("gbp_name")),
            "category_set": bool(loc.get("primary_category")),
            "photos_count": int(loc.get("photo_count", 0)),
            "reviews_responded": float(loc.get("review_response_rate", 0)),
            "posts_last_30_days": int(loc.get("recent_posts", 0)),
            "hours_updated": bool(loc.get("hours_current")),
            "description_filled": bool(loc.get("description")),
        }
        completeness = sum(1 for v in checks.values() if v) / len(checks) * 100
        audit_results.append({
            "location": loc["name"],
            "city": loc["city"],
            "completeness": f"{completeness:.0f}%",
            "checks": checks
        })
    return sorted(audit_results, key=lambda x: float(x["completeness"].rstrip("%")))

Step 2: NAP Consistency Check

from difflib import SequenceMatcher

def check_nap_consistency(listings):
    issues = []
    canonical = listings[0]
    for i, listing in enumerate(listings[1:], 1):
        name_sim = SequenceMatcher(None, canonical["name"].lower(), listing["name"].lower()).ratio()
        address_sim = SequenceMatcher(None, canonical["address"].lower(), listing["address"].lower()).ratio()
        phone_match = canonical["phone"].replace("-", "") == listing["phone"].replace("-", "")
        if name_sim < 0.9:
            issues.append(f"Listing {i}: Name mismatch ({name_sim:.0%})")
        if address_sim < 0.9:
            issues.append(f"Listing {i}: Address mismatch ({address_sim:.0%})")
        if not phone_match:
            issues.append(f"Listing {i}: Phone mismatch")
    return issues

Step 3: Local Keyword Ranking Check

def check_local_rankings(keywords, locations):
    results = []
    for location in locations:
        for keyword in keywords:
            ranking = get_local_rank(keyword, location["city"])
            results.append({
                "keyword": keyword,
                "location": location["city"],
                "ranking": ranking,
                "in_local_pack": ranking <= 3,
                "in_top_10": ranking <= 10
            })
    return results

Step 4: Competitor Local SEO Analysis

def analyze_local_competitors(your_business, competitors, city):
    analysis = {
        "your_business": analyze_gbp_completeness(your_business),
        "competitors": [],
        "opportunities": []
    }
    for comp in competitors:
        comp_data = analyze_gbp_completeness(comp)
        analysis["competitors"].append(comp_data)
        if comp_data["review_count"] > analysis["your_business"]["review_count"]:
            analysis["opportunities"].append(
                f"Get more reviews (competitor has {comp_data[