Monitoreo de precios geolocalizado: rastrea precios en diferentes mercados

Monitorea precios de productos en mercados globales usando proxies geolocalizados. Código en Python y Node.js para comparación de precios multi-mercado, normalización de divisas y detección de estrategias de precios de competidores.

Monitoreo de precios geolocalizado: rastrea precios en diferentes mercados

Why Prices Differ by Location

E-commerce companies routinely adjust prices based on the customer's geographic location. A product that costs $49.99 in the United States might be priced at 59.99 EUR in Germany, 5,499 JPY in Japan, or $39.99 in India. These differences go beyond currency conversion — they reflect regional purchasing power, competitive landscapes, taxes, shipping costs, and deliberate pricing strategies.

For competitive intelligence, understanding these regional price variations is critical. A competitor might be aggressively undercutting you in one market while charging premium prices in another. Without geo-targeted monitoring, you are blind to half the competitive picture. This guide covers how to build a multi-market price monitoring system using geo-targeted proxies. For foundational monitoring architecture, see our guide on monitoring competitor prices automatically.

How Geo-Pricing Works

Websites determine your location through several signals and adjust content accordingly.

SignalHow It WorksImpact on Pricing
IP GeolocationIP address mapped to country/city via databasesPrimary factor — determines which regional store/pricing you see
Currency/LanguageBrowser Accept-Language and previous selectionsMay trigger region-specific catalog and pricing
CookiesPrevious region selections stored in sessionOverrides IP-based detection on subsequent visits
URL StructureCountry-specific domains (amazon.de) or paths (/de/)Directly determines regional catalog
GPS/Device LocationMobile device location servicesUsed for hyperlocal pricing (delivery zones)

Common Geo-Pricing Patterns

  • Marketplace localization: Amazon, eBay, and similar platforms operate separate regional storefronts with independent pricing.
  • Dynamic geo-pricing: SaaS companies and travel sites adjust prices based on the visitor's country of origin.
  • Shipping-adjusted pricing: Products include different shipping costs based on location, changing the effective price.
  • Tax-inclusive pricing: European prices typically include VAT, while US prices show pre-tax amounts.
  • Purchasing power parity: Some companies offer lower prices in developing markets to maximize accessibility.

Proxy Configuration for Multi-Market Monitoring

The core requirement is residential proxies from each target country. ProxyHat's geo-targeting lets you specify the exact country for each request.

ProxyHat Setup

# US pricing
http://USERNAME-country-US:PASSWORD@gate.proxyhat.com:8080
# German pricing
http://USERNAME-country-DE:PASSWORD@gate.proxyhat.com:8080
# UK pricing
http://USERNAME-country-GB:PASSWORD@gate.proxyhat.com:8080
# Japanese pricing
http://USERNAME-country-JP:PASSWORD@gate.proxyhat.com:8080
# Brazilian pricing
http://USERNAME-country-BR:PASSWORD@gate.proxyhat.com:8080
# City-level targeting for hyperlocal pricing
http://USERNAME-country-US-city-newyork:PASSWORD@gate.proxyhat.com:8080
http://USERNAME-country-DE-city-berlin:PASSWORD@gate.proxyhat.com:8080

For price monitoring across regions, use per-request rotation within each country to avoid detection, but always maintain consistent geo-targeting per marketplace. Check ProxyHat's full location list for 195+ supported countries.

Python Implementation

Here is a multi-market price monitoring system using ProxyHat's Python SDK.

Geo-Targeted Price Scraper

import requests
from bs4 import BeautifulSoup
import json
import time
import random
from dataclasses import dataclass, asdict
from datetime import datetime
USER_AGENTS = [
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36",
]
PROXY_TEMPLATE = "http://USERNAME-country-{country}:PASSWORD@gate.proxyhat.com:8080"
@dataclass
class GeoPrice:
    product_id: str
    country: str
    price: float | None
    currency: str
    url: str
    in_stock: bool
    scraped_at: str
def get_geo_proxy(country_code: str) -> dict:
    """Get proxy configured for a specific country."""
    proxy = PROXY_TEMPLATE.format(country=country_code)
    return {"http": proxy, "https": proxy}
def scrape_price_for_region(product_url: str, country_code: str,
                             price_selector: str, currency: str) -> GeoPrice:
    """Scrape a product price from a specific geographic region."""
    headers = {
        "User-Agent": random.choice(USER_AGENTS),
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
        "Accept-Language": get_accept_language(country_code),
    }
    proxies = get_geo_proxy(country_code)
    try:
        response = requests.get(product_url, headers=headers,
                                proxies=proxies, timeout=30)
        response.raise_for_status()
        soup = BeautifulSoup(response.text, "html.parser")
        price_el = soup.select_one(price_selector)
        price = None
        if price_el:
            price_text = price_el.get_text(strip=True)
            cleaned = "".join(c for c in price_text if c.isdigit() or c in ".,")
            # Handle European comma as decimal separator
            if "," in cleaned and "." in cleaned:
                cleaned = cleaned.replace(".", "").replace(",", ".")
            elif "," in cleaned:
                cleaned = cleaned.replace(",", ".")
            price = float(cleaned) if cleaned else None
        return GeoPrice(
            product_id=product_url,
            country=country_code,
            price=price,
            currency=currency,
            url=product_url,
            in_stock=True,
            scraped_at=datetime.utcnow().isoformat(),
        )
    except Exception as e:
        return GeoPrice(
            product_id=product_url,
            country=country_code,
            price=None,
            currency=currency,
            url=product_url,
            in_stock=False,
            scraped_at=datetime.utcnow().isoformat(),
        )
def get_accept_language(country_code: str) -> str:
    """Return appropriate Accept-Language for a country."""
    lang_map = {
        "US": "en-US,en;q=0.9",
        "GB": "en-GB,en;q=0.9",
        "DE": "de-DE,de;q=0.9,en;q=0.5",
        "FR": "fr-FR,fr;q=0.9,en;q=0.5",
        "JP": "ja-JP,ja;q=0.9,en;q=0.5",
        "BR": "pt-BR,pt;q=0.9,en;q=0.5",
        "IN": "en-IN,hi;q=0.9,en;q=0.5",
        "IT": "it-IT,it;q=0.9,en;q=0.5",
        "ES": "es-ES,es;q=0.9,en;q=0.5",
    }
    return lang_map.get(country_code, "en-US,en;q=0.9")

Multi-Market Monitor

class MultiMarketMonitor:
    """Monitor prices across multiple geographic markets."""
    def __init__(self):
        self.markets = {}
        self.results = []
    def add_market(self, country_code: str, marketplace_url: str,
                   price_selector: str, currency: str):
        """Register a market for monitoring."""
        self.markets[country_code] = {
            "url": marketplace_url,
            "selector": price_selector,
            "currency": currency,
        }
    def monitor_product(self, product_urls: dict[str, str]) -> list[GeoPrice]:
        """
        Monitor a product across all configured markets.
        product_urls: {"US": "https://amazon.com/dp/...", "DE": "https://amazon.de/dp/..."}
        """
        prices = []
        for country, url in product_urls.items():
            market = self.markets.get(country)
            if not market:
                continue
            price = scrape_price_for_region(
                url, country,
                market["selector"],
                market["currency"]
            )
            prices.append(price)
            print(f"  {country}: {price.currency} {price.price}")
            time.sleep(random.uniform(2, 5))
        return prices
    def compare_prices(self, prices: list[GeoPrice], base_currency_rates: dict) -> dict:
        """Compare prices across markets normalized to USD."""
        normalized = {}
        for p in prices:
            if p.price:
                rate = base_currency_rates.get(p.currency, 1.0)
                normalized[p.country] = {
                    "local_price": p.price,
                    "currency": p.currency,
                    "usd_equivalent": round(p.price / rate, 2),
                }
        if not normalized:
            return {}
        usd_prices = [v["usd_equivalent"] for v in normalized.values()]
        cheapest = min(usd_prices)
        most_expensive = max(usd_prices)
        return {
            "prices": normalized,
            "cheapest_market": [k for k, v in normalized.items()
                                if v["usd_equivalent"] == cheapest][0],
            "most_expensive_market": [k for k, v in normalized.items()
                                      if v["usd_equivalent"] == most_expensive][0],
            "price_spread_pct": round(
                (most_expensive - cheapest) / cheapest * 100, 1
            ) if cheapest > 0 else 0,
        }
# Example: Monitor a product across Amazon marketplaces
monitor = MultiMarketMonitor()
# Configure markets
monitor.add_market("US", "amazon.com", "span.a-price-whole", "USD")
monitor.add_market("DE", "amazon.de", "span.a-price-whole", "EUR")
monitor.add_market("GB", "amazon.co.uk", "span.a-price-whole", "GBP")
monitor.add_market("JP", "amazon.co.jp", "span.a-price-whole", "JPY")
# Monitor a specific product
prices = monitor.monitor_product({
    "US": "https://www.amazon.com/dp/B0CHX3QBCH",
    "DE": "https://www.amazon.de/dp/B0CHX3QBCH",
    "GB": "https://www.amazon.co.uk/dp/B0CHX3QBCH",
    "JP": "https://www.amazon.co.jp/dp/B0CHX3QBCH",
})
# Compare prices in USD
comparison = monitor.compare_prices(prices, {
    "USD": 1.0, "EUR": 0.92, "GBP": 0.79, "JPY": 149.5,
})
print(json.dumps(comparison, indent=2))

Node.js Implementation

A Node.js multi-market monitor using ProxyHat's Node SDK.

const axios = require("axios");
const cheerio = require("cheerio");
const { HttpsProxyAgent } = require("https-proxy-agent");
function getGeoProxy(countryCode) {
  return `http://USERNAME-country-${countryCode}:PASSWORD@gate.proxyhat.com:8080`;
}
const LANG_MAP = {
  US: "en-US,en;q=0.9",
  GB: "en-GB,en;q=0.9",
  DE: "de-DE,de;q=0.9,en;q=0.5",
  FR: "fr-FR,fr;q=0.9,en;q=0.5",
  JP: "ja-JP,ja;q=0.9,en;q=0.5",
};
async function scrapeGeoPrice(url, countryCode, priceSelector, currency) {
  const agent = new HttpsProxyAgent(getGeoProxy(countryCode));
  try {
    const { data } = await axios.get(url, {
      httpsAgent: agent,
      headers: {
        "User-Agent":
          "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36",
        "Accept-Language": LANG_MAP[countryCode] || "en-US,en;q=0.9",
      },
      timeout: 30000,
    });
    const $ = cheerio.load(data);
    let priceText = $(priceSelector).first().text().trim();
    let price = parseFloat(priceText.replace(/[^0-9.,]/g, "").replace(",", ".")) || null;
    return {
      country: countryCode,
      price,
      currency,
      url,
      inStock: true,
      scrapedAt: new Date().toISOString(),
    };
  } catch (err) {
    return { country: countryCode, price: null, currency, url, inStock: false, scrapedAt: new Date().toISOString() };
  }
}
async function monitorMultiMarket(productUrls, markets) {
  const results = [];
  for (const [country, url] of Object.entries(productUrls)) {
    const market = markets[country];
    if (!market) continue;
    const result = await scrapeGeoPrice(url, country, market.selector, market.currency);
    results.push(result);
    console.log(`${country}: ${result.currency} ${result.price}`);
    await new Promise((r) => setTimeout(r, 2000 + Math.random() * 3000));
  }
  return results;
}
function comparePrices(results, rates) {
  const normalized = {};
  for (const r of results) {
    if (r.price) {
      const rate = rates[r.currency] || 1;
      normalized[r.country] = {
        localPrice: r.price,
        currency: r.currency,
        usdEquivalent: Math.round((r.price / rate) * 100) / 100,
      };
    }
  }
  const usdPrices = Object.values(normalized).map((v) => v.usdEquivalent);
  const cheapest = Math.min(...usdPrices);
  const mostExpensive = Math.max(...usdPrices);
  return {
    prices: normalized,
    cheapestMarket: Object.keys(normalized).find((k) => normalized[k].usdEquivalent === cheapest),
    mostExpensiveMarket: Object.keys(normalized).find(
      (k) => normalized[k].usdEquivalent === mostExpensive
    ),
    priceSpreadPct: cheapest > 0 ? Math.round(((mostExpensive - cheapest) / cheapest) * 1000) / 10 : 0,
  };
}
// Usage
const markets = {
  US: { selector: "span.a-price-whole", currency: "USD" },
  DE: { selector: "span.a-price-whole", currency: "EUR" },
  GB: { selector: "span.a-price-whole", currency: "GBP" },
  JP: { selector: "span.a-price-whole", currency: "JPY" },
};
monitorMultiMarket(
  {
    US: "https://www.amazon.com/dp/B0CHX3QBCH",
    DE: "https://www.amazon.de/dp/B0CHX3QBCH",
    GB: "https://www.amazon.co.uk/dp/B0CHX3QBCH",
    JP: "https://www.amazon.co.jp/dp/B0CHX3QBCH",
  },
  markets
).then((results) => {
  const comparison = comparePrices(results, { USD: 1.0, EUR: 0.92, GBP: 0.79, JPY: 149.5 });
  console.log(JSON.stringify(comparison, null, 2));
});

Multi-Market Monitoring Strategies

Currency Normalization

To compare prices meaningfully, normalize all prices to a base currency. Use a reliable exchange rate API (Open Exchange Rates, ECB rates) and update rates daily. Store both local and normalized prices for accurate historical analysis.

Tax Handling

Prices in different markets include different tax levels:

MarketTypical Tax TreatmentConsideration
United StatesPrices shown pre-taxActual cost varies by state (0-10.25%)
European UnionPrices include VAT (19-27%)Subtract VAT for like-for-like comparison
United KingdomPrices include 20% VATSubtract VAT for net comparison
JapanPrices include 10% consumption taxSubtract tax for net comparison

Monitoring Schedule

Not all markets need the same check frequency. Prioritize based on business impact:

  • Primary markets: Your main sales regions — check every 1-2 hours.
  • Expansion targets: Markets you are entering — check every 4-6 hours.
  • Reference markets: Markets for benchmarking only — check daily.

Detecting Geo-Pricing Strategies

With multi-market data, you can identify competitor pricing strategies:

  • Uniform global pricing: Same price (after currency conversion) everywhere. Common for digital products.
  • PPP-adjusted pricing: Lower prices in lower-income markets. Common for SaaS and software.
  • Competition-driven pricing: Prices vary by market based on local competitive pressure.
  • Cost-plus pricing: Different prices reflecting different shipping, warehousing, and tax costs.
Key takeaway: Multi-market monitoring reveals pricing strategies invisible to single-market analysis. A competitor offering 30% lower prices in one market signals either aggressive expansion or a different cost structure worth investigating.

Handling Common Challenges

Regional Redirects

Some sites redirect users to regional versions based on IP. With geo-targeted proxies, you want this redirect — it takes you to the correct regional pricing. Do not follow cross-region redirects, as they indicate the IP location does not match the target marketplace.

Content Differences

Product availability varies by region. A product sold on amazon.com might not exist on amazon.de. Handle 404 responses and unavailable products gracefully in your monitoring pipeline.

Scraping Etiquette

When monitoring multiple regions, you are effectively scraping multiple separate websites. Apply best practices for avoiding blocks independently per marketplace. What works for amazon.com may need different tuning for amazon.co.jp.

Data Storage for Multi-Market Data

CREATE TABLE geo_price_history (
    id SERIAL PRIMARY KEY,
    product_id VARCHAR(100) NOT NULL,
    country_code VARCHAR(2) NOT NULL,
    local_price DECIMAL(12, 2),
    currency VARCHAR(3),
    usd_equivalent DECIMAL(12, 2),
    exchange_rate DECIMAL(10, 6),
    in_stock BOOLEAN,
    scraped_at TIMESTAMPTZ NOT NULL,
    created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX idx_geo_price_product_country
    ON geo_price_history (product_id, country_code, scraped_at DESC);
-- Query: Price spread across markets for a product
SELECT
    country_code,
    AVG(usd_equivalent) AS avg_usd_price,
    MIN(usd_equivalent) AS min_usd_price,
    MAX(usd_equivalent) AS max_usd_price
FROM geo_price_history
WHERE product_id = 'B0CHX3QBCH'
  AND scraped_at >= now() - INTERVAL '7 days'
GROUP BY country_code
ORDER BY avg_usd_price;

Key Takeaways

  • E-commerce prices vary significantly by geography — monitoring one market gives an incomplete competitive picture.
  • Geo-targeted residential proxies are essential: use ProxyHat's country-level targeting to access authentic regional pricing.
  • Normalize prices to a base currency for meaningful cross-market comparisons.
  • Account for tax differences (VAT-inclusive vs pre-tax) when comparing prices.
  • Match Accept-Language headers to the target country for accurate results.
  • Prioritize monitoring frequency by market importance to optimize proxy usage.

Ready to monitor prices across markets? Start with ProxyHat's residential proxies with 195+ country options and read our e-commerce scraping guide for the full strategy. For real-time capabilities, see our guide on real-time price monitoring infrastructure.

¿Listo para empezar?

Accede a más de 50M de IPs residenciales en más de 148 países con filtrado impulsado por IA.

Ver preciosProxies residenciales
← Volver al Blog