Stop Getting US Results: How to Scrape Localized Google Shopping Data

A comprehensive guide to handling geolocation (gl) and language (hl) parameters correctly using the modern serpapi library.

The Problem

If you have ever tried to scrape Google Shopping results for a specific country—say, looking for coffee machine prices in Jakarta, Indonesia—you might have encountered a frustrating problem.

Even though your script runs perfectly, the results often come back in USD, showing products from Walmart or Target instead of local marketplaces like Tokopedia or Shopee.

Why does this happen?

By default, Google serves results based on the IP address of the requester. If your server (or your proxy's exit node) is located in the US, Google will show you American results.

Common Pitfall

Many developers assume the location parameter is enough. It's not.

The Common Mistake: "It works on my machine"

A common mistake developers make is relying solely on the location string.

// The Wrong Way
const params = {
  q: "Coffee Maker",
  location: "Jakarta", // Often insufficient!
};

While the location parameter is important, it is not always enough to force Google to switch the currency and language context entirely. You might get Jakarta location data, but still see prices in English or mixed currencies.

The Solution: The Holy Trinity of Parameters

To get accurate data, you need to be explicit. You must tell the API exactly where you are and what language you speak.

You need three key parameters:

  1. location: The physical location (e.g., "Jakarta, Indonesia")
  2. gl: The Country Code (e.g., id for Indonesia) - dictates the region for search interest
  3. hl: The Language Code (e.g., id for Bahasa Indonesia) - ensures the UI text and currency formatting align with the local user

Robust Implementation

Here is a robust script that fetches prices specifically for the Indonesian market:

import * as Dotenv from "dotenv";
import { getJson } from "serpapi";

Dotenv.config();

// Pro Tip: Use the 'google_shopping' engine for structured data
const params = {
  engine: "google_shopping",
  api_key: process.env.SERPAPI_KEY,
  q: "Mesin Kopi",
  location: "Jakarta, Indonesia",
  google_domain: "google.co.id", // Use the local Google domain
  gl: "id", // Country: Indonesia
  hl: "id", // Language: Indonesian
};

console.log("Fetching localized prices...");

try {
  const json = await getJson(params);

  // Defensive check: Ensure results actually exist
  if (json.shopping_results && json.shopping_results.length > 0) {
    json.shopping_results.forEach((item) => {
      console.log(`${item.title} - ${item.price}`);
      // Result: "Mesin Kopi Espresso - Rp 1.500.000"
    });
  } else {
    console.log("No shopping results found.");
  }
} catch (error) {
  console.error("API Error:", error.message);
}

Expected Output:

Fetching localized prices... Mesin Kopi Espresso Manual - Rp 1.500.000 Mesin Kopi Otomatis Premium - Rp 3.200.000 Prices in Indonesian Rupiah!

Why Use google_shopping instead of google?

You might notice I set the engine parameter to "google_shopping".

Pro Tip

The Google Shopping API returns clean, structured JSON specifically designed for product data.

Comparison:

Feature google engine google_shopping engine
Data Structure Mixed HTML parsing Clean JSON
Price Formatting Inconsistent Structured
Seller Info Limited Detailed
Condition (New/Used) Not available Available
Future-proof Breaks on HTML changes Stable API

If you use the standard "google" engine, you get the main search page. While this page contains shopping ads, parsing them is harder.

Country & Language Code Reference

Country gl Code hl Code Currency
Indonesia id id IDR (Rp)
Singapore sg en SGD ($)
Malaysia my ms MYR (RM)
Thailand th th THB (฿)
United States us en USD ($)
United Kingdom uk en GBP (£)
Full Reference

Find the complete list of country and language codes in the SerpApi documentation.

Advanced: Multi-Region Comparison

Want to compare prices across multiple countries? Here's a pattern:

const markets = [
  { name: "Indonesia", gl: "id", hl: "id", location: "Jakarta" },
  { name: "Singapore", gl: "sg", hl: "en", location: "Singapore" },
  { name: "Malaysia", gl: "my", hl: "ms", location: "Kuala Lumpur" },
];

for (const market of markets) {
  console.log(`\nFetching prices for ${market.name}...`);

  const json = await getJson({
    engine: "google_shopping",
    api_key: process.env.SERPAPI_KEY,
    q: "iPhone 15",
    location: market.location,
    gl: market.gl,
    hl: market.hl,
  });

  if (json.shopping_results?.[0]) {
    const topResult = json.shopping_results[0];
    console.log(`${market.name}: ${topResult.price}`);
  }
}

Output:

Fetching prices for Indonesia... Indonesia: Rp 15.999.000 Fetching prices for Singapore... Singapore: S$ 1,299 Fetching prices for Malaysia... Malaysia: RM 5,499

Common Pitfalls to Avoid

1. Forgetting to set both gl AND hl

// Incomplete - might still show wrong currency
{ location: "Jakarta", gl: "id" } // Missing hl

2. Using wrong language code for the country

// Wrong - Indonesia doesn't use English primarily
{ gl: "id", hl: "en" } // Should be hl: "id"

3. Not handling empty results

// Risky - what if there are no results?
console.log(json.shopping_results[0].price); // Can crash!

// Safe
if (json.shopping_results?.length > 0) {
  console.log(json.shopping_results[0].price); // Correct
}

Conclusion

Scraping is all about precision. By combining the correct gl and hl parameters with the google_shopping engine, you ensure that your application receives data that is actually relevant to your users' location.

Key Takeaways:

  • Always use the Holy Trinity: location, gl, and hl
  • Use google_shopping engine for structured product data
  • Include defensive checks for empty results
  • Test with different markets to verify localization

Want More Troubleshooting Patterns?

I built a complete handbook for handling Pagination, Error Handling, and Localization issues using modern Node.js practices.

Check out my full portfolio:


Author: Roki Miftah Kamaludin
Role: Aspiring Customer Success Engineer
LinkedIn: linkedin.com/in/rokimiftah