If you sell on Amazon or Walmart, you've probably noticed that prices — yours and your competitors' — aren't the same everywhere. Both platforms adjust what shoppers see based on region: shipping cost differences, regional promotions, inventory availability by fulfillment center, and dynamic pricing algorithms all mean that a single price check from your own office IP doesn't tell you what a buyer in Texas, Ohio, or California is actually seeing. If you're monitoring competitor pricing without accounting for this, you're working from an incomplete picture.
This guide covers why a US-based residential proxy matters for reliable price monitoring, how to think about IP quality and city-level targeting for this specific use case, and a working Python example to get a monitoring script running.
Why Location-Accurate US IPs Matter for Price Monitoring
Price monitoring at any real scale means sending a large number of automated requests from an address that looks like a genuine US shopper — not a datacenter IP, and not a shared IP that's already been flagged by the platform's bot-detection systems. Two things determine whether that works reliably:
IP cleanliness. Amazon and Walmart both run fraud and bot detection that scores IP reputation. If the address you're scraping from has a poor reputation — previously flagged, overused, or associated with known automation — you'll see CAPTCHAs, throttling, or outright blocks well before you get meaningful data. This is measurable: in an independent fraud-score comparison run against a pool of US IPs (10,000 checks per provider), a quality-filtered residential pool returned a fraud score of 27.32 out of 100, with 28.43% of checked IPs flagging as previously abused — noticeably cleaner than the other pools tested in the same comparison, which ranged from roughly 33 to 43 on the same scale, with 33–45% of their IPs showing prior abuse.
Geographic accuracy. If you need to see the price a shopper in a specific state or city actually sees, the request has to originate from an IP genuinely located there — not just "somewhere in the US." Quality residential proxy pools support targeting down to the country, state, city, and even ZIP-code level, which is what makes it possible to check "what does this listing show a buyer in Dallas" as a distinct, repeatable query from "what does it show a buyer in New York."
Choosing the Right Proxy Type for This Job
Not every proxy type is built the same way for this task:
For price monitoring specifically — high request volume, need for geographic diversity, no requirement for a single persistent identity — residential proxies are the right fit. ISP proxies become more relevant if part of your workflow also involves managing seller accounts continuously rather than just pulling public pricing data.
Available US Coverage
A quality US residential pool spans a wide range of states — commonly including California, Texas, New York, Florida, Georgia, Pennsylvania, Ohio, Washington, and a long list of others — with concentrated IP availability in specific cities. For price monitoring across multiple regions, having depth in more than one or two cities matters — you want enough IPs per location that your monitoring volume doesn't quickly exhaust or overuse the same handful of addresses in any single city.
Building a Price Monitor: Python Example
The example below is a minimal but realistic starting point: it rotates through a list of target cities, sends a request through a residential proxy geo-targeted to each one, and extracts a price from the response. It's written for clarity, not as a drop-in production scraper — you'll need to adapt the parsing logic to the actual page structure of whatever listing you're tracking, and confirm your proxy authentication string format directly from your provider's dashboard, since exact username/session syntax varies by provider and account setup.
import requests
import time
import csv
from datetime import datetime
# Replace with your actual proxy credentials and endpoint from your
# provider's dashboard. Many residential proxy providers let you target
# a specific city or ZIP code by modifying the username string — check
# your dashboard's documentation for the exact format.
PROXY_HOST = "your-proxy-host:port"
PROXY_USER_TEMPLATE = "your-username-city-{city}" # confirm exact syntax with your provider
PROXY_PASS = "your-password"
TARGET_CITIES = [
"new_york", "los_angeles", "chicago", "dallas", "charlotte"
]
TARGET_URL = "https://example-product-page.com/dp/PRODUCT_ID" # replace with real listing URL
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/124.0 Safari/537.36"
)
}
def build_proxy_dict(city: str) -> dict:
username = PROXY_USER_TEMPLATE.format(city=city)
proxy_url = f"http://{username}:{PROXY_PASS}@{PROXY_HOST}"
return {"http": proxy_url, "https": proxy_url}
def fetch_price(city: str) -> dict:
proxies = build_proxy_dict(city)
try:
response = requests.get(
TARGET_URL,
headers=HEADERS,
proxies=proxies,
timeout=15,
)
response.raise_for_status()
except requests.RequestException as exc:
return {
"city": city,
"status": "error",
"detail": str(exc),
"timestamp": datetime.utcnow().isoformat(),
}
# Placeholder extraction logic — replace with real parsing
# (e.g., BeautifulSoup selectors matched to the actual page structure).
price = extract_price_from_html(response.text)
return {
"city": city,
"status": "ok",
"price": price,
"timestamp": datetime.utcnow().isoformat(),
}
def extract_price_from_html(html: str):
# Replace this with real HTML parsing for your target site.
# This is intentionally left as a stub — page structure changes
# frequently and needs to be matched to the live page you're tracking.
raise NotImplementedError("Add real price-extraction logic here")
def run_monitor(cities, delay_seconds=5):
results = []
for city in cities:
result = fetch_price(city)
results.append(result)
print(result)
time.sleep(delay_seconds) # basic rate limiting between requests
return results
def save_results_csv(results, filename="price_monitor_results.csv"):
if not results:
return
keys = results[0].keys()
with open(filename, "a", newline="") as f:
writer = csv.DictWriter(f, fieldnames=keys)
if f.tell() == 0:
writer.writeheader()
writer.writerows(results)
if __name__ == "__main__":
data = run_monitor(TARGET_CITIES)
save_results_csv(data)
A few practical notes on this setup:
- Rate limiting matters more than raw speed. The time.sleep() call between requests is deliberate — sending requests as fast as possible from a rotating IP pool is exactly the pattern automated bot-detection systems are built to catch. Slower, more human-like pacing preserves IP reputation over time.
- Sticky sessions help when a single check requires multiple steps. If your monitoring flow needs to load a page and then follow through a secondary request (like checking cart-level pricing or applying a coupon), a sticky session — keeping the same IP for up to 24 hours — avoids the city or identity shifting mid-flow.
- Log failures, don't just retry blindly. The status: error branch above is there so you can distinguish a genuine block (worth investigating) from a transient network issue (worth a simple retry) rather than treating every failure identically.
Scaling to Concurrent City Checks
Running through five cities sequentially with a delay between each is fine for a small catalog, but if you're tracking dozens of SKUs across a dozen cities, sequential requests become slow fast. A simple way to parallelize this safely — without abandoning rate-limit discipline — is to run a bounded number of concurrent workers, each respecting its own pacing:
from concurrent.futures import ThreadPoolExecutor, as_completed
MAX_CONCURRENT_WORKERS = 4 # keep this modest to avoid tripping rate limits
def run_monitor_concurrent(cities, delay_seconds=5):
results = []
with ThreadPoolExecutor(max_workers=MAX_CONCURRENT_WORKERS) as executor:
futures = {
executor.submit(fetch_price, city): city for city in cities
}
for future in as_completed(futures):
result = future.result()
results.append(result)
print(result)
time.sleep(delay_seconds / MAX_CONCURRENT_WORKERS)
return results
Keeping MAX_CONCURRENT_WORKERS deliberately low (single digits, not dozens) matters here — the goal is to cut down wall-clock time for a monitoring run, not to maximize request volume per second. A sudden burst of parallel requests from the same account, even across different IPs, is still a detectable pattern.
Detecting Price Changes, Not Just Logging Prices
A price log by itself isn't that useful until you compare it against the previous run. A minimal price-change detector reads your last saved result per city/SKU and flags anything that moved:
import json
import os
STATE_FILE = "last_known_prices.json"
def load_last_prices() -> dict:
if not os.path.exists(STATE_FILE):
return {}
with open(STATE_FILE, "r") as f:
return json.load(f)
def save_last_prices(prices: dict):
with open(STATE_FILE, "w") as f:
json.dump(prices, f, indent=2)
def detect_changes(results: list, threshold_pct: float = 2.0) -> list:
last_prices = load_last_prices()
changes = []
updated_prices = dict(last_prices)
for result in results:
if result["status"] != "ok":
continue
city = result["city"]
new_price = result["price"]
old_price = last_prices.get(city)
if old_price is not None and old_price > 0:
pct_change = abs(new_price - old_price) / old_price * 100
if pct_change >= threshold_pct:
changes.append({
"city": city,
"old_price": old_price,
"new_price": new_price,
"pct_change": round(pct_change, 2),
})
updated_prices[city] = new_price
save_last_prices(updated_prices)
return changes
if __name__ == "__main__":
data = run_monitor_concurrent(TARGET_CITIES)
save_results_csv(data)
price_changes = detect_changes(data)
for change in price_changes:
print(
f"Price change detected in {change['city']}: "
f"{change['old_price']} -> {change['new_price']} "
f"({change['pct_change']}%)"
)
This gives you a persistent baseline (last_known_prices.json) to compare each run against, and only surfaces changes above whatever threshold you consider meaningful — useful for feeding into a Slack alert, an email digest, or a simple dashboard, rather than re-reading a full CSV after every run to spot what moved.
Interpreting Differences Across Cities
Once you have a few days of data across multiple cities, a few patterns are worth watching for specifically:
- Consistent, stable differences between two cities usually point to structural factors — different fulfillment center proximity affecting shipping costs baked into the displayed price, or a regional promotion that's been running for a while.
- Sudden, simultaneous changes across most cities usually indicate a base price change from the seller, not a regional pricing effect.
- A change isolated to one or two cities while others stay flat is the pattern most worth digging into — it's the clearest signal of an actual geo-targeted pricing strategy at play, rather than noise or a general repricing event.
None of this requires anything more sophisticated than the diffing logic above — the value is in checking the same set of cities consistently over time, not in the complexity of the analysis.
Residential proxies for this use case are typically billed per GB of bandwidth used, starting from $2.20/GB, with sticky sessions and ZIP-level targeting included on quality plans. If your workflow expands into managing seller accounts continuously — not just pulling prices — static ISP proxies (starting from $2.99/IP, unlimited traffic, fixed for 30 or 90 days) become worth evaluating alongside your monitoring setup.
A Note on Responsible Scraping
Before running any monitoring script at scale, check the target platform's terms of service and robots.txt, respect rate limits, and avoid extracting anything beyond publicly visible pricing data. Price monitoring for competitive intelligence is common practice in e-commerce, but how you implement it — request pacing, data scope, and respecting access rules — is what keeps it on the right side of both platform policy and general web-scraping norms.
Getting Started
The core requirement underneath all of this is the same: clean, US-based, city-targetable IPs that don't get flagged before your monitoring even produces useful data. If you're setting up a monitoring pipeline and need that foundation, NodeMaven's US proxies page has the current city coverage and plan details to start from.

Top comments (0)