guide 20 min read

Local SEO with SERP API: Track Local Rankings & Google Maps Results

Complete guide to using SERP API for local SEO. Track local pack rankings, monitor Google Maps results, analyze local competitors, and optimize for location-based searches.

SERPpost Team
Local SEO with SERP API: Track Local Rankings & Google Maps Results

Local SEO with SERP API: Complete Guide to Local Rankings

Local SEO is crucial for businesses targeting specific geographic areas. This guide shows you how to use SERP API to track local rankings, monitor Google Maps results, and optimize your local search presence.

Why Use SERP API for Local SEO?

Local search results vary significantly by location. SERP API enables you to:

  • Track local pack rankings across multiple locations
  • Monitor Google Maps results for your business
  • Analyze local competitors in different areas
  • Optimize for “near me” searches with real data
  • Scale local monitoring across hundreds of locations
  • Get accurate location-specific results without VPNs

Understanding Local Search Results

Local search results typically include:

  1. Local Pack (Map Pack): Top 3 local businesses with map
  2. Organic Results: Regular search results
  3. Google Maps Results: Full map view results
  4. Local Finder: Extended local results
  5. Knowledge Panel: Business information sidebar

Basic Local Search with SERP API

const SERPpostClient = require('./serp-client');

const client = new SERPpostClient(process.env.SERPPOST_API_KEY);

async function searchLocal(query, location) {
  const results = await client.search('google', query, {
    location: location,
    gl: 'us',
    hl: 'en'
  });

  return {
    localPack: results.local_results || [],
    organicResults: results.organic_results || [],
    searchMetadata: results.search_metadata
  };
}

// Usage
const results = await searchLocal(
  'pizza restaurant',
  'New York, NY'
);

console.log('Local Pack Results:', results.localPack.length);

Location-Specific Parameters

async function searchWithPreciseLocation(query, locationParams) {
  const results = await client.search('google', query, {
    location: locationParams.city,
    gl: locationParams.country,
    hl: locationParams.language,
    uule: locationParams.uule  // Precise location encoding
  });

  return results;
}

// Example: Search from specific coordinates
const results = await searchWithPreciseLocation('coffee shop', {
  city: 'San Francisco, CA',
  country: 'us',
  language: 'en',
  uule: 'w+CAIQICINc2FuIGZyYW5jaXNjbw'  // Encoded location
});

Building a Local Rank Tracker

Track your business rankings across multiple locations:

class LocalRankTracker {
  constructor(apiKey) {
    this.client = new SERPpostClient(apiKey);
  }

  async trackLocalRanking(businessName, keyword, locations) {
    const rankings = [];

    for (const location of locations) {
      const results = await this.client.search('google', keyword, {
        location: location,
        gl: 'us',
        hl: 'en'
      });

      const ranking = this.findBusinessRanking(
        results,
        businessName
      );

      rankings.push({
        location,
        keyword,
        ...ranking,
        timestamp: new Date()
      });

      // Respect rate limits
      await this.sleep(1000);
    }

    return rankings;
  }

  findBusinessRanking(results, businessName) {
    // Check local pack
    const localPack = results.local_results || [];
    const localPackPosition = localPack.findIndex(result =>
      result.title.toLowerCase().includes(businessName.toLowerCase())
    );

    if (localPackPosition !== -1) {
      return {
        inLocalPack: true,
        localPackPosition: localPackPosition + 1,
        organicPosition: null,
        data: localPack[localPackPosition]
      };
    }

    // Check organic results
    const organicResults = results.organic_results || [];
    const organicPosition = organicResults.findIndex(result =>
      result.title.toLowerCase().includes(businessName.toLowerCase())
    );

    return {
      inLocalPack: false,
      localPackPosition: null,
      organicPosition: organicPosition !== -1 ? organicPosition + 1 : null,
      data: organicPosition !== -1 ? organicResults[organicPosition] : null
    };
  }

  async trackMultipleKeywords(businessName, keywords, locations) {
    const allRankings = [];

    for (const keyword of keywords) {
      const rankings = await this.trackLocalRanking(
        businessName,
        keyword,
        locations
      );
      allRankings.push(...rankings);
    }

    return this.generateReport(allRankings);
  }

  generateReport(rankings) {
    const report = {
      totalSearches: rankings.length,
      inLocalPack: rankings.filter(r => r.inLocalPack).length,
      averageLocalPackPosition: this.calculateAverage(
        rankings.filter(r => r.inLocalPack).map(r => r.localPackPosition)
      ),
      averageOrganicPosition: this.calculateAverage(
        rankings.filter(r => r.organicPosition).map(r => r.organicPosition)
      ),
      byLocation: this.groupByLocation(rankings),
      byKeyword: this.groupByKeyword(rankings)
    };

    return report;
  }

  groupByLocation(rankings) {
    const grouped = {};
    rankings.forEach(ranking => {
      if (!grouped[ranking.location]) {
        grouped[ranking.location] = [];
      }
      grouped[ranking.location].push(ranking);
    });
    return grouped;
  }

  groupByKeyword(rankings) {
    const grouped = {};
    rankings.forEach(ranking => {
      if (!grouped[ranking.keyword]) {
        grouped[ranking.keyword] = [];
      }
      grouped[ranking.keyword].push(ranking);
    });
    return grouped;
  }

  calculateAverage(numbers) {
    if (numbers.length === 0) return 0;
    return numbers.reduce((a, b) => a + b, 0) / numbers.length;
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Usage
const tracker = new LocalRankTracker(process.env.SERPPOST_API_KEY);

const report = await tracker.trackMultipleKeywords(
  'Joe\'s Pizza',
  ['pizza delivery', 'best pizza', 'pizza near me'],
  ['New York, NY', 'Brooklyn, NY', 'Queens, NY']
);

console.log('Local SEO Report:', report);

Monitoring Google Maps Results

Track your Google Maps presence:

class GoogleMapsMonitor {
  constructor(apiKey) {
    this.client = new SERPpostClient(apiKey);
  }

  async monitorMapsListing(businessName, location) {
    const results = await this.client.search('google', businessName, {
      location: location,
      gl: 'us',
      hl: 'en'
    });

    const localResults = results.local_results || [];
    const knowledgeGraph = results.knowledge_graph;

    const listing = localResults.find(result =>
      result.title.toLowerCase() === businessName.toLowerCase()
    );

    if (listing) {
      return {
        found: true,
        position: localResults.indexOf(listing) + 1,
        title: listing.title,
        rating: listing.rating,
        reviews: listing.reviews,
        address: listing.address,
        phone: listing.phone,
        hours: listing.hours,
        type: listing.type,
        thumbnail: listing.thumbnail
      };
    }

    // Check knowledge graph
    if (knowledgeGraph && 
        knowledgeGraph.title.toLowerCase() === businessName.toLowerCase()) {
      return {
        found: true,
        inKnowledgeGraph: true,
        ...knowledgeGraph
      };
    }

    return {
      found: false,
      message: 'Business not found in local results'
    };
  }

  async compareLocations(businessName, locations) {
    const comparisons = [];

    for (const location of locations) {
      const listing = await this.monitorMapsListing(businessName, location);
      comparisons.push({
        location,
        ...listing
      });
      await this.sleep(1000);
    }

    return comparisons;
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Usage
const monitor = new GoogleMapsMonitor(process.env.SERPPOST_API_KEY);

const listings = await monitor.compareLocations(
  'Starbucks',
  ['Seattle, WA', 'Portland, OR', 'San Francisco, CA']
);

listings.forEach(listing => {
  console.log(`${listing.location}: ${listing.found ? 'Found' : 'Not found'}`);
  if (listing.found) {
    console.log(`  Rating: ${listing.rating} (${listing.reviews} reviews)`);
  }
});

Local Competitor Analysis

Analyze local competitors across multiple locations:

class LocalCompetitorAnalyzer {
  constructor(apiKey) {
    this.client = new SERPpostClient(apiKey);
  }

  async analyzeCompetitors(keywords, location, limit = 10) {
    const competitorData = new Map();

    for (const keyword of keywords) {
      const results = await this.client.search('google', keyword, {
        location: location,
        gl: 'us',
        hl: 'en'
      });

      // Analyze local pack
      const localResults = results.local_results || [];
      localResults.slice(0, limit).forEach((result, index) => {
        const name = result.title;
        if (!competitorData.has(name)) {
          competitorData.set(name, {
            name,
            appearances: 0,
            keywords: [],
            averagePosition: 0,
            totalPosition: 0,
            ratings: [],
            reviews: []
          });
        }

        const data = competitorData.get(name);
        data.appearances++;
        data.keywords.push(keyword);
        data.totalPosition += index + 1;
        data.averagePosition = data.totalPosition / data.appearances;
        
        if (result.rating) data.ratings.push(result.rating);
        if (result.reviews) data.reviews.push(result.reviews);
      });

      await this.sleep(1000);
    }

    // Convert to array and sort by appearances
    const competitors = Array.from(competitorData.values())
      .sort((a, b) => b.appearances - a.appearances);

    return {
      location,
      totalKeywords: keywords.length,
      competitors: competitors.map(c => ({
        ...c,
        averageRating: c.ratings.length > 0
          ? c.ratings.reduce((a, b) => a + b, 0) / c.ratings.length
          : null,
        totalReviews: c.reviews.length > 0
          ? c.reviews.reduce((a, b) => a + b, 0)
          : null
      }))
    };
  }

  async compareAcrossLocations(keywords, locations) {
    const analyses = [];

    for (const location of locations) {
      const analysis = await this.analyzeCompetitors(keywords, location);
      analyses.push(analysis);
    }

    return this.generateComparisonReport(analyses);
  }

  generateComparisonReport(analyses) {
    const allCompetitors = new Set();
    analyses.forEach(analysis => {
      analysis.competitors.forEach(c => allCompetitors.add(c.name));
    });

    const report = {
      locations: analyses.map(a => a.location),
      totalCompetitors: allCompetitors.size,
      byLocation: analyses,
      topCompetitors: this.findTopCompetitors(analyses)
    };

    return report;
  }

  findTopCompetitors(analyses) {
    const competitorScores = new Map();

    analyses.forEach(analysis => {
      analysis.competitors.forEach((competitor, index) => {
        const name = competitor.name;
        if (!competitorScores.has(name)) {
          competitorScores.set(name, {
            name,
            totalScore: 0,
            locations: []
          });
        }

        const data = competitorScores.get(name);
        // Score based on position (lower is better)
        data.totalScore += (10 - index);
        data.locations.push(analysis.location);
      });
    });

    return Array.from(competitorScores.values())
      .sort((a, b) => b.totalScore - a.totalScore)
      .slice(0, 10);
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Usage
const analyzer = new LocalCompetitorAnalyzer(process.env.SERPPOST_API_KEY);

const analysis = await analyzer.compareAcrossLocations(
  ['pizza', 'italian restaurant', 'pizza delivery'],
  ['New York, NY', 'Chicago, IL', 'Los Angeles, CA']
);

console.log('Top Competitors:', analysis.topCompetitors);

“Near Me” Search Optimization

Track and optimize for “near me” searches:

class NearMeOptimizer {
  constructor(apiKey) {
    this.client = new SERPpostClient(apiKey);
  }

  async analyzeNearMeSearch(baseKeyword, locations) {
    const nearMeVariations = [
      `${baseKeyword} near me`,
      `${baseKeyword} nearby`,
      `best ${baseKeyword} near me`,
      `${baseKeyword} close to me`,
      `${baseKeyword} around me`
    ];

    const results = [];

    for (const location of locations) {
      for (const keyword of nearMeVariations) {
        const searchResults = await this.client.search('google', keyword, {
          location: location,
          gl: 'us',
          hl: 'en'
        });

        results.push({
          keyword,
          location,
          localPackCount: (searchResults.local_results || []).length,
          hasLocalPack: (searchResults.local_results || []).length > 0,
          organicCount: (searchResults.organic_results || []).length,
          features: this.extractFeatures(searchResults)
        });

        await this.sleep(1000);
      }
    }

    return this.generateNearMeReport(results);
  }

  extractFeatures(results) {
    const features = [];
    
    if (results.local_results) features.push('local_pack');
    if (results.knowledge_graph) features.push('knowledge_graph');
    if (results.people_also_ask) features.push('people_also_ask');
    
    return features;
  }

  generateNearMeReport(results) {
    return {
      totalSearches: results.length,
      withLocalPack: results.filter(r => r.hasLocalPack).length,
      averageLocalPackSize: this.calculateAverage(
        results.map(r => r.localPackCount)
      ),
      byKeyword: this.groupByKeyword(results),
      byLocation: this.groupByLocation(results),
      recommendations: this.generateRecommendations(results)
    };
  }

  groupByKeyword(results) {
    const grouped = {};
    results.forEach(result => {
      if (!grouped[result.keyword]) {
        grouped[result.keyword] = [];
      }
      grouped[result.keyword].push(result);
    });
    return grouped;
  }

  groupByLocation(results) {
    const grouped = {};
    results.forEach(result => {
      if (!grouped[result.location]) {
        grouped[result.location] = [];
      }
      grouped[result.location].push(result);
    });
    return grouped;
  }

  calculateAverage(numbers) {
    if (numbers.length === 0) return 0;
    return numbers.reduce((a, b) => a + b, 0) / numbers.length;
  }

  generateRecommendations(results) {
    const recommendations = [];

    // Check local pack presence
    const withoutLocalPack = results.filter(r => !r.hasLocalPack);
    if (withoutLocalPack.length > 0) {
      recommendations.push({
        type: 'local_pack_opportunity',
        message: `${withoutLocalPack.length} searches don't show local pack`,
        keywords: withoutLocalPack.map(r => r.keyword)
      });
    }

    return recommendations;
  }

  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// Usage
const optimizer = new NearMeOptimizer(process.env.SERPPOST_API_KEY);

const report = await optimizer.analyzeNearMeSearch(
  'coffee shop',
  ['Seattle, WA', 'Portland, OR']
);

console.log('Near Me Analysis:', report);

Multi-Location Monitoring Dashboard

Build a comprehensive local SEO dashboard:

class LocalSEODashboard {
  constructor(apiKey) {
    this.rankTracker = new LocalRankTracker(apiKey);
    this.mapsMonitor = new GoogleMapsMonitor(apiKey);
    this.competitorAnalyzer = new LocalCompetitorAnalyzer(apiKey);
    this.nearMeOptimizer = new NearMeOptimizer(apiKey);
  }

  async generateFullReport(businessName, keywords, locations) {
    console.log('Generating comprehensive local SEO report...');

    const [
      rankings,
      mapsListings,
      competitors,
      nearMeAnalysis
    ] = await Promise.all([
      this.rankTracker.trackMultipleKeywords(businessName, keywords, locations),
      this.mapsMonitor.compareLocations(businessName, locations),
      this.competitorAnalyzer.compareAcrossLocations(keywords, locations),
      this.nearMeOptimizer.analyzeNearMeSearch(keywords[0], locations)
    ]);

    return {
      businessName,
      generatedAt: new Date(),
      summary: {
        totalLocations: locations.length,
        totalKeywords: keywords.length,
        inLocalPack: rankings.inLocalPack,
        averagePosition: rankings.averageLocalPackPosition,
        mapsListingsFound: mapsListings.filter(l => l.found).length
      },
      rankings,
      mapsListings,
      competitors,
      nearMeAnalysis,
      recommendations: this.generateRecommendations({
        rankings,
        mapsListings,
        competitors
      })
    };
  }

  generateRecommendations(data) {
    const recommendations = [];

    // Check for missing locations
    const missingListings = data.mapsListings.filter(l => !l.found);
    if (missingListings.length > 0) {
      recommendations.push({
        priority: 'high',
        type: 'missing_listings',
        message: `Claim or verify listings in ${missingListings.length} locations`,
        locations: missingListings.map(l => l.location)
      });
    }

    // Check for low rankings
    const lowRankings = data.rankings.byLocation;
    Object.entries(lowRankings).forEach(([location, ranks]) => {
      const notInPack = ranks.filter(r => !r.inLocalPack);
      if (notInPack.length > ranks.length / 2) {
        recommendations.push({
          priority: 'medium',
          type: 'improve_rankings',
          message: `Improve local pack presence in ${location}`,
          keywords: notInPack.map(r => r.keyword)
        });
      }
    });

    return recommendations;
  }
}

// Usage
const dashboard = new LocalSEODashboard(process.env.SERPPOST_API_KEY);

const report = await dashboard.generateFullReport(
  'Joe\'s Pizza',
  ['pizza', 'pizza delivery', 'best pizza'],
  ['New York, NY', 'Brooklyn, NY']
);

console.log(JSON.stringify(report, null, 2));

Best Practices for Local SEO Monitoring

  1. Track multiple locations: Monitor all service areas
  2. Use precise locations: Specify exact cities/neighborhoods
  3. Monitor competitors: Track top local competitors
  4. Check “near me” searches: Optimize for mobile searches
  5. Track reviews and ratings: Monitor reputation metrics
  6. Schedule regular checks: Daily or weekly monitoring
  7. Compare engines: Check both Google and Bing

Integration with Other Tools

Combine local SEO data with:

  • Analytics platforms: Track correlation with traffic
  • Review management: Monitor review impact on rankings
  • Social media: Correlate social signals with rankings
  • CRM systems: Track leads by location
  • Reporting tools: Automated client reports

Cost Optimization

Keep costs low while monitoring multiple locations:

  • Cache results for frequently checked locations
  • Batch similar location searches
  • Use appropriate check frequencies
  • Focus on high-priority locations first
  • Check our affordable pricing

Advanced Use Cases

Multi-Location Franchise Monitoring

async function monitorFranchise(franchiseName, locations) {
  const tracker = new LocalRankTracker(process.env.SERPPOST_API_KEY);
  
  const results = await tracker.trackMultipleKeywords(
    franchiseName,
    ['franchise name', 'service keywords'],
    locations
  );
  
  // Generate per-location reports
  return results.byLocation;
}

Service Area Business Tracking

async function trackServiceArea(businessName, centerLocation, radiusMiles) {
  // Generate locations within radius
  const locations = generateLocationGrid(centerLocation, radiusMiles);
  
  const tracker = new LocalRankTracker(process.env.SERPPOST_API_KEY);
  return await tracker.trackLocalRanking(businessName, 'service keyword', locations);
}

Next Steps

Conclusion

SERP API makes local SEO monitoring scalable and reliable. Track rankings across multiple locations, monitor Google Maps presence, and analyze local competitors without the complexity of traditional scraping methods.

Start monitoring local rankings today with 100 free credits and see how SERPpost can improve your local SEO strategy.


Ready to dominate local search? Get your API key and start tracking. Need help? Check our documentation or pricing.

Share:

Tags:

#SERP API #Local SEO #Google Maps #Local Rankings #Location-Based Search

Ready to try SERPpost?

Get started with 100 free credits. No credit card required.