The same product can have different prices on Amazon and Walmart, but comparing those prices takes more than extracting two numbers. You also need to account for product variations, different sellers, and listings that bundle additional items.
In this tutorial, you'll build a Python price comparison tool that retrieves product data from Amazon and Walmart using Spidra, checks whether the listings describe the same product, calculates price differences, and exports the results to a CSV file.
We'll use real listings for the Sony WH-1000XM5 wireless headphones and extend the script to compare multiple products.
What we're comparing
We'll start with the Sony WH-1000XM5 noise-canceling headphones in black.
Here are the two product listings:
Both listings describe the same headphone model and color.
The product data retrieved on October 28, 2026, shows these prices:
| Amazon | Walmart | |
|---|---|---|
| Product | Sony WH-1000XM5 | Sony WH-1000XM5 |
| Color | Black | Black |
| Seller | Amazon.com | Focus Camera |
| Listed price | $228.00 | $298.00 |
| Currency | USD | USD |
Based on these prices, Amazon is $70 cheaper, a difference of approximately 23.49% relative to Walmart's price.
But there's more to the Walmart listing.
It also has an alternative new offer for $288, an open-box offer for $189, and a restored offer for $159.99.
If your script simply chooses the lowest number, it might compare Amazon's regular product listing against Walmart's restored headphones and report the wrong result.
We'll build the comparison around the featured listing prices, while also capturing Walmart's lowest available new offer separately.
The prices are snapshots of the advertised offers, not guaranteed checkout totals. They may change when you run the script.
Set up your Python environment
You'll need Python 3.9 or later, a Spidra account, and an API key.
Spidra provides structured product data from Amazon and Walmart through its scraping API. For these product pages, you can request JSON directly without writing CSS selectors or defining a custom extraction schema.
Create a free account at Spidra, generate an API key from the dashboard, and install the Python SDK:
pip install spidraSet your API key as an environment variable. On macOS or Linux:
export SPIDRA_API_KEY="spd_YOUR_API_KEY"On Windows PowerShell:
$env:SPIDRA_API_KEY="spd_YOUR_API_KEY"Now let's retrieve the product information.
Extract Amazon and Walmart product prices
Create a file named inspect_products.py:
# inspect_products.py
import json
import os
from spidra import Spidra
client = Spidra(api_key=os.environ["SPIDRA_API_KEY"])
products = {
"Amazon": "https://www.amazon.com/dp/B09XS7JWHH",
"Walmart": "https://www.walmart.com/ip/386006068",
}
for retailer, url in products.items():
result = client.scrape(url, output="json")
data = result.content
if isinstance(data, str):
data = json.loads(data)
print(f"{retailer}: {data['title']}")
print(f"Price: ${data['price']:.2f}")Run the script:
python inspect_products.pyUsing the captured product data, the output is:
Amazon: Sony WH-1000XM5 Premium Noise Cancelling Wireless Headphones, Black
Price: $228.00
Walmart: Sony WH-1000XM5 The Best Wireless Noise Canceling Headphones, Black
Price: $298.00The SDK submits the scraping requests and waits for their results. With output="json", Spidra returns structured product information for these pages.
You can see the complete fields available for each marketplace in the Amazon and Walmart documentation.
Understand the returned product data
The actual responses contain much more than the titles and prices.
The relevant Amazon fields look like this:
{
"asin": "B09XS7JWHH",
"title": "Sony WH-1000XM5 Premium Noise Cancelling Wireless Headphones, Black",
"brand": "Sony",
"price": 228,
"currency": "USD",
"listPrice": 399.99,
"buyBox": {
"seller": "Amazon.com"
},
"specifications": {
"Color": "Black",
"Model Name": "WH-1000XM5",
"Mfr Part Number": "WH1000XM5/B"
}
}The Walmart response contains these fields:
{
"usItemId": "386006068",
"title": "Sony WH-1000XM5 The Best Wireless Noise Canceling Headphones, Black",
"brand": "Sony",
"manufacturerPartNumber": "WH1000XM5/B",
"condition": "New",
"price": 298,
"currency": "USD",
"seller": {
"name": "Focus Camera"
},
"selectedOptions": [
{
"name": "Actual Color",
"value": "Black"
}
]
}These are shortened excerpts of the real responses.
Notice that the two marketplaces organize some information differently.
Amazon stores the manufacturer part number inside specifications and the featured seller inside buyBox. Walmart returns the part number as manufacturerPartNumber and the seller as a separate object.
That's why the next step is to normalize both responses before comparing them.
Match the same product across Amazon and Walmart
Product matching is the most important part of a reliable price comparison.
Two listings can share a brand and model name while representing different configurations, conditions, or bundles. Comparing those prices as if they were interchangeable can produce misleading results.
For our Sony example, both products have these identifying details:
| Attribute | Amazon | Walmart |
|---|---|---|
| Brand | Sony | Sony |
| Manufacturer part number | WH1000XM5/B | WH1000XM5/B |
| Color | Black | Black |
Those identifiers give us a reasonable basis for comparing the two selected listings.
Now consider another Walmart listing:
Sony WH-1000XM5 with headphone stand and USB adapter
This listing costs $398 and includes additional accessories. Its manufacturer part number is WH1000XM5/B_EDI_1.
Although the product title contains the same Sony WH-1000XM5 model, it isn't an equivalent standalone headphone listing.
Our script will compare the brand, manufacturer part number, and color. If any of those identifiers are missing or don't match, it will flag the comparison for review instead of declaring a cheaper retailer.
This is deliberately conservative. It may flag legitimate matches when retailers use different identifiers, but it avoids treating uncertain matches as confirmed ones.
For a larger catalog, you should also verify bundle contents, pack quantities, and product condition when selecting URL pairs.
Build the Amazon and Walmart price comparison script
Now let's turn the individual scraping requests into a reusable tool.
The script will:
- Read Amazon and Walmart product URL pairs from a CSV file.
- Retrieve structured product details using Spidra.
- Normalize prices and product identifiers.
- Check whether the listings match.
- Calculate the price difference and identify the cheaper retailer.
- Export the comparison results to CSV.
We'll also retain the lowest available new Walmart offer, giving us a second useful price without mixing in refurbished or open-box items.
Create a CSV file with product URLs
Create products.csv in your project directory:
product,amazon_url,walmart_url
Sony WH-1000XM5,https://www.amazon.com/dp/B09XS7JWHH,https://www.walmart.com/ip/386006068
Sony WH-1000XM5 bundle,https://www.amazon.com/dp/B09XS7JWHH,https://www.walmart.com/ip/20182712890The first row contains a matching product pair. The second deliberately includes the Walmart bundle so we can demonstrate how the script handles an incorrect match.
For your own catalog, add one row for each pair of listings you've identified.
Write the comparison script
Create compare_amazon_walmart.py:
# compare_amazon_walmart.py
import csv
import json
import os
import re
from decimal import Decimal, ROUND_HALF_UP
from pathlib import Path
from spidra import Spidra, SpidraError
INPUT_FILE = Path("products.csv")
OUTPUT_FILE = Path("price_comparison.csv")
CENT = Decimal("0.01")
def money(value):
if value is None:
return None
return Decimal(str(value)).quantize(CENT)
def token(value):
return re.sub(r"[^a-z0-9]", "", str(value or "").lower())
def amazon_product(data):
specs = data.get("specifications") or {}
return {
"brand": data.get("brand"),
"part": specs.get("Mfr Part Number"),
"color": specs.get("Color"),
"price": money(data.get("price")),
"seller": (data.get("buyBox") or {}).get("seller"),
"currency": data.get("currency"),
"availability": data.get("availability"),
}
def walmart_product(data):
options = data.get("selectedOptions") or []
color = next(
(
option.get("value")
for option in options
if "color" in (option.get("name") or "").lower()
),
None,
)
return {
"brand": data.get("brand"),
"part": data.get("manufacturerPartNumber") or data.get("model"),
"color": color,
"price": money(data.get("price")),
"seller": (data.get("seller") or {}).get("name"),
"currency": data.get("currency"),
"condition": data.get("condition"),
}
def same_product(amazon, walmart):
for field in ("brand", "part", "color"):
if not amazon[field] or not walmart[field]:
return False, f"Missing {field}: review manually"
if token(amazon[field]) != token(walmart[field]):
return False, f"{field.capitalize()} mismatch"
if walmart["condition"] != "New":
return False, "Walmart featured offer is not new"
return True, "Brand, part number and color agree"
def lowest_new_offer(data):
offers = data.get("offers") or []
prices = [
money(offer["price"])
for offer in offers
if offer.get("condition") == "New"
and offer.get("availability") == "IN_STOCK"
and offer.get("price") is not None
]
return min(prices) if prices else None
def fetch(client, url, cache):
if url not in cache:
result = client.scrape(url, output="json")
data = result.content
if isinstance(data, str):
data = json.loads(data)
if not isinstance(data, dict) or data.get("type") != "product":
raise ValueError(f"Expected product JSON for {url}")
cache[url] = data
return cache[url]
def compare(client, item, cache):
row = {
"product": item["product"],
"match_status": "",
"note": "",
"amazon_price": "",
"amazon_seller": "",
"walmart_price": "",
"walmart_seller": "",
"walmart_lowest_new_price": "",
"cheaper_retailer": "",
"difference_usd": "",
"pct_below_walmart": "",
}
try:
amazon_raw = fetch(client, item["amazon_url"], cache)
walmart_raw = fetch(client, item["walmart_url"], cache)
except (SpidraError, ValueError) as error:
row.update(match_status="error", note=str(error))
return row
amazon = amazon_product(amazon_raw)
walmart = walmart_product(walmart_raw)
row.update(
amazon_price=str(amazon["price"]) if amazon["price"] is not None else "",
amazon_seller=amazon["seller"] or "",
walmart_price=str(walmart["price"]) if walmart["price"] is not None else "",
walmart_seller=walmart["seller"] or "",
)
matched, reason = same_product(amazon, walmart)
if not matched:
row.update(match_status="review", note=reason)
return row
if amazon["currency"] != "USD" or walmart["currency"] != "USD":
row.update(
match_status="review",
note="Prices are not both in USD",
)
return row
a_price = amazon["price"]
w_price = walmart["price"]
if a_price is None or w_price is None or a_price <= 0 or w_price <= 0:
row.update(
match_status="review",
note="One or both prices are unavailable",
)
return row
lowest = lowest_new_offer(walmart_raw)
if lowest is not None:
row["walmart_lowest_new_price"] = str(lowest)
difference = abs(w_price - a_price)
if a_price < w_price:
winner = "Amazon"
elif w_price < a_price:
winner = "Walmart"
else:
winner = "Tie"
percentage = (
(difference / w_price) * 100
).quantize(CENT, rounding=ROUND_HALF_UP)
row.update(
match_status="matched",
note=reason,
cheaper_retailer=winner,
difference_usd=str(difference),
pct_below_walmart=(
str(percentage) if winner == "Amazon" else ""
),
)
return row
def main():
client = Spidra(api_key=os.environ["SPIDRA_API_KEY"])
cache = {}
with INPUT_FILE.open(newline="", encoding="utf-8") as source:
products = list(csv.DictReader(source))
rows = [
compare(client, product, cache)
for product in products
]
if not rows:
print("No products found in the CSV file.")
return
with OUTPUT_FILE.open("w", newline="", encoding="utf-8") as dest:
writer = csv.DictWriter(dest, fieldnames=list(rows[0]))
writer.writeheader()
writer.writerows(rows)
for row in rows:
if row["match_status"] == "matched":
result = (
f"{row['product']}: "
f"Amazon ${row['amazon_price']} | "
f"Walmart ${row['walmart_price']} | "
f"{row['cheaper_retailer']} lower by "
f"${row['difference_usd']}"
)
if row["cheaper_retailer"] == "Amazon":
result += (
f" ({row['pct_below_walmart']}% vs. Walmart)"
)
print(result)
else:
print(
f"{row['product']}: "
f"{row['match_status']} ({row['note']})"
)
print(f"Saved {len(rows)} comparisons to {OUTPUT_FILE}")
if __name__ == "__main__":
main()The script uses Python's Decimal type for monetary calculations and caches responses so the same URL isn't scraped again within a single execution.
The same_product() function checks the product identifiers before allowing a comparison. This means a missing or different manufacturer part number produces a review flag, not an unreliable result.
The script also checks currencies before calculating differences. For simplicity, it compares USD prices only rather than performing currency conversion.
Finally, lowest_new_offer() examines Walmart's additional offers but includes only available offers explicitly marked New.
Run the comparison
Execute the script:
python compare_amazon_walmart.pyWith the product data collected for this example, the comparison produces:
Sony WH-1000XM5: Amazon $228.00 | Walmart $298.00 | Amazon lower by $70.00 (23.49% vs. Walmart)
Sony WH-1000XM5 bundle: review (Part mismatch)
Saved 2 comparisons to price_comparison.csvThe first pair matches because the brand, manufacturer part number, and color agree.
The second pair is flagged because the Walmart bundle has a different manufacturer part number.
The script doesn't calculate a price difference for that pair, even though both listings contain the Sony WH-1000XM5 model name.
Export the comparison results to CSV
The script also creates price_comparison.csv, containing the product name, match status, retailer prices, sellers, lowest new Walmart offer, and calculated difference.
The main results look like this:
| Product | Amazon | Walmart | Lowest new Walmart offer | Difference | Status |
|---|---|---|---|---|---|
| Sony WH-1000XM5 | $228.00 | $298.00 | $288.00 | $70.00 | Matched |
| Sony WH-1000XM5 bundle | $228.00 | $398.00 | — | — | Review |
Because we retain the source prices alongside the comparison status, rows needing review aren't lost.
For the matching product, the CSV identifies Amazon as the cheaper retailer. It also records the alternative $288 new Walmart offer for further analysis.
This separation matters when building pricing applications. You may want to compare featured offers, the cheapest available new offer, or a particular seller's offer. Each answers a different business question.
Compare the lowest new offer instead of the featured price
So far, we've compared the featured prices returned at the top level of each product response.
On Walmart, a product can have additional offers from several sellers. The Sony headphones provide a useful example:
| Walmart seller | Condition | Price |
|---|---|---|
| Focus Camera | New, featured offer | $298.00 |
| Video & Audio Center | New | $288.00 |
| Beach Camera | Open Box | $189.00 |
| Secondipity | Restored: Like New | $159.99 |
If your objective is to compare the cheapest available new offer, the relevant Walmart price is $288 rather than $298.
Against Amazon's $228 featured price, that's a $60 difference.
The lowest_new_offer() function already extracts that amount. To make it the primary comparison, replace the featured Walmart price in the calculation with the lowest available new offer, falling back to the featured price when no alternative new offer exists.
For example, after retrieving walmart_raw and confirming the products match:
lowest = lowest_new_offer(walmart_raw)
w_price = lowest if lowest is not None else walmart["price"]Use that value in the price calculation and update the exported price field to reflect the chosen offer.
For a purchasing application, you might take the comparison further by considering shipping charges, expected delivery dates, and seller preferences. The available fulfillment details depend on the listing and the customer's location.
The main lesson is to define which price you want to compare before calculating the difference.
Compare more products in one run
The script isn't restricted to Sony headphones. You can add more matched product pairs to products.csv and run it again.
Each row represents one product comparison, and the script saves all results to the same output file.
For larger catalogs, you can improve throughput with Spidra's Batch scrape API, which processes up to 50 URLs per request and returns a separate result for each page.
Instead of making one scraping request for every URL, you can submit groups of product links and associate the returned data with their original CSV rows.
The batch response also includes individual item statuses, so an unavailable product page doesn't prevent you from processing the other products.
Our guide on scraping multiple URLs in Python and handling failures covers that workflow in detail.
For a recurring catalog comparison, it's also useful to save a timestamp with every price snapshot. That allows you to distinguish a genuine price change from an older result in your database.
Turn the price comparison into a price tracker
A one-time comparison tells you which retailer has the lower listed price at the moment you collect the data.
But pricing changes. A product that's cheaper on Amazon today may be cheaper on Walmart next week.
You can extend the script into a price tracker by storing each comparison with a timestamp and comparing new results against previous snapshots.
For example, your application could detect when a Walmart offer drops below its Amazon counterpart or when the difference between the two prices exceeds a chosen threshold.
Spidra also provides Monitors for scheduled checks and notifications when tracked data changes. You can use them to watch product information without building your own scheduling and notification system.
For e-commerce applications, this makes it possible to move from manually checking a few prices to maintaining an ongoing view of product pricing.
Build your own price comparison tool
You now have a Python script that retrieves structured Amazon and Walmart product data, validates matching listings, compares prices, and exports the results to CSV.
The same approach can support catalog enrichment, competitor price analysis, and marketplace research. You can extend it with more products, additional retailers, or historical price tracking.
When collecting data from Amazon, Walmart, or other websites, check the relevant website terms and applicable rules. Our guide to web scraping legality covers the considerations.
Create a free Spidra account and run the comparison script with your own product URLs.