If you've been paying attention the last few years, you know prices for nearly everything have increased significantly. This is well reflected in macroeconomic data like the U.S. Bureau of Labor Statistics' overall inflation rate.
However, the reality is everyone has a unique inflation rate depending on their own shopping habits. The above aggregate data is based on a basket of goods estimated by economists. It doesn't necessarily reflect what individuals actually buy, and the economists periodically change the composition of the basket, which further confuses things.
Additionally, aggregated inflation data doesn't tell you anything about inflation within particular categories of goods. Inflation might be 3% overall, but it doesn't help you understand if one category is 7% and another is -2%.
To best understand real-world inflation, it can be worth it to track and calculate it yourself. With SerpApi's Amazon Search API, you can easily start tracking price changes for any custom basket of goods sold by Amazon.
In this tutorial, we'll walk through the steps of creating a basic Amazon price tracker in Python that will scrape Amazon price data and calculate the inflation rate of any custom basket of goods.
For more specifics on scraping Amazon product data with or without our API, check out this blog post:
Be sure to sign up for a free SerpApi account to get 250 free searches and your API key. You'll need this to follow along. Register here: https://serpapi.com/users/sign_up
Once registered, your API key will be here: https://serpapi.com/manage-api-key
Organizing your products list
We'll begin by creating a source list of products to query in Amazon. This can include any product listing on Amazon, but I'll be focusing on grocery products in this tutorial. The process would be the same for anything else you want to track on Amazon.
As a starting point, I've created a list of 10 grocery items that I buy regularly. You can copy my Google Sheet or raw text CSV below to help you get started. Even if you delete all 10 items I've listed, you'll still want to use this format, as the Python script we'll create later will expect the same structure.
title,asin,zip,monthly_purchases,date_checked,base_price,monthly_cost
"Blueberries, 1 Pint",B003AYKYIG,94404,4,2025-09-24,$4.49,$17.96
"Cage Free Large Brown Eggs, Grade AA, 1 Dozen",B0BXRKRF93,94404,5,2025-09-24,$2.99,$14.95
"Amazon Grocery, Whole Milk, 1 Gallon, 128 Fl Oz",B075K1MQZ8,94404,5,2025-09-24,$3.26,$16.30
"Amazon Grocery, Ground Beef, 80% Lean/20% Fat, 1 lb",B08LJVQSL2,94404,10,2025-09-24,$6.93,$69.30
"Beef Ribeye Steak, Grass-Fed, Pasture-Raised | 0.625 lbs.",B01H0AI64Y,94404,1,2025-09-24,$13.12,$13.12
"Quaker, Quick 1 Minute Whole Grain Oats, 42 Oz",B000PWK3KK,94404,3,2025-09-24,$4.99,$14.97
"Zucchini Squash, 1 Each""",B000P6G1AC,94404,10,2025-09-24,$1.02,$10.20
"Laura Scudder's All Natural Nutty Peanut Butter, 16 oz. Jar",B00CJ8K18W,94404,2,2025-09-24,$4.88,$9.76
"FAGE Total Greek Yogurt, 5% Whole Milk, Plain, 32 oz",B00WTR0CDM,94404,5,2025-09-24,$6.99,$34.95
"Taylor Farms Spinach, 9 oz Bag",B00KMM8I6Y,94404,5,2025-09-24,$2.06,$10.30
The sheet has the following columns:
-
title: This is the Amazon product title. You can technically put whatever you want here. It's only for your benefit. -
asin: This stands for Amazon Standard Identification Number. It's a unique code for every product on Amazon. You can get this from the URL of any product listing. For example,B003AYKYIGin:https://www.amazon.com/Fresh-Produce-Brands-Vary-100174/dp/B003AYKYIG -
zip: This is your delivery zip/post code. Grocery products, in particular, can have different prices and availability in different parts of the country. Keep this consistent. -
monthly_purchases: This is how many times you purchase this item each month. Put your best estimate. -
date_checked: This is the original date you input the product in the list. This will be crucial for the script to calculate an annualized inflation rate. For example, October 1, 2025 would be:2025-10-01. -
base_price: This is the base starting price of the product when you first looked it up on Amazon.
Use the screenshot below as a reference for where to find each data point sourced from Amazon. Ensure you use the same zip when manually searching and in the spreadsheet. Amazon prices, particularly groceries, can vary by region.
Scraping Amazon Prices
Before we get into connecting to SerpApi's Amazon Search API, you'll need to ensure you have our Python library installed. Run the following to do that:
pip install serpapi
More instructions here: https://serpapi.com/integrations
The script reads your API key from an environment variable, so you don't have to paste it into your code:
# macOS / Linux
export SERPAPI_API_KEY="your_api_key"
# Windows (PowerShell)
$env:SERPAPI_API_KEY="your_api_key"
Set up the starter code
Next, I'll share this starter code as our jumping-off point. Copy and paste this into your local environment.
import csv
import os
import sys
from datetime import date, datetime, timedelta
from decimal import Decimal
import serpapi
client = serpapi.Client(api_key=os.environ["SERPAPI_API_KEY"])
def read_csv(file):
with open(file, newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
return list(reader)
def to_decimal(value):
return Decimal(str(value).replace("$", "").replace(",", ""))
if __name__ == "__main__":
products = read_csv("products.csv")
The to_decimal helper removes the $ sign from prices in the CSV, so Python can do exact math with them.
Search Amazon by ASIN
We'll add a search function and pretty much copy the sample code we have in our Amazon API docs. We'll only make small changes like including a delivery_zip. This and the k query parameter will be settable with inputs to our search function.
def search(query, zip):
results = client.search({
"engine": "amazon",
"k": query,
"delivery_zip": zip,
})
return results
Let's quickly test our search function with the first product in our products.csv file.
if __name__ == "__main__":
products = read_csv("products.csv")
print(search(products[0]["asin"],products[0]["zip"]))
If you run the script now, you should see a large JSON dump in your terminal. This is a good sign the search worked, but we'll obviously need to extract just the product we want.
The Amazon Search API returns an array of organic results with the following structure. The example below has only 1 result, but many queries will return multiple results. We'll need to identify the matching asin and then select the extracted_price field for that result.
"organic_results":
[
{
"position": 1,
"asin": "B01H0AI64Y",
"tags":
[
"Ribeye Steak"
]
,
"title": "Pre, Beef Ribeye Steak, Grass-Fed, Pasture-Raised | 0.625 lbs.",
"link": "https://www.amazon.com/Pre-Ribeye-Steak-Grass-Finished-Pasture-Raised/dp/B01H0AI64Y/ref=sr_1_1?dib=eyJ2IjoiMSJ9.KGRC7U9W7UNHi6Lb8ubxbQ.vpBa-Hj3Wv8a3AQi2P5kTvmYit303_veEpuAHeYjttw&dib_tag=se&keywords=B01H0AI64Y&qid=1761648220&sr=8-1",
"link_clean": "https://www.amazon.com/Pre-Ribeye-Steak-Grass-Finished-Pasture-Raised/dp/B01H0AI64Y/",
"thumbnail": "https://m.media-amazon.com/images/I/81U1xlobC2L._AC_UL320_.jpg",
"rating": 4.4,
"reviews": 4000,
"bought_last_month": "10K+ bought in past month",
"price": "$13.12",
"extracted_price": 13.12,
"price_unit": "$20.99/lb",
"extracted_price_unit": 20.99,
"offers":
[
"SNAP EBT eligible"
]
,
"snap_ebt_eligible": true,
"delivery":
[
"$12.99 delivery Today 10 AM - 3 PM"
]
}
]
Get the product price
Let's add a new function to handle this logic and return the price.
def fetch_product_price(product):
results = search(product["asin"], product["zip"])
organic_results = results.get("organic_results", [])
target = next((result for result in organic_results if result.get("asin") == product["asin"]), None)
if not target:
return None
return to_decimal(target.get("extracted_price"))
If for some reason the product doesn't appear in search results, the function returns None. We'll use that later to skip the product and move on to the next one instead of stopping the whole script.
We can now test this function and print its output.
if __name__ == "__main__":
products = read_csv("products.csv")
print(fetch_product_price(products[0]))
If all goes well, we should see the price of our first product printed to the console. E.g. 4.49 for blueberries.
Calculating your basket cost
Calculate the base basket
Next we need a way to calculate the total cost of our base shopping basket. We'll add a calculate_base_basket function to do this. It will loop through our products.csv file while summing up the total monthly spend for each product.
def calculate_base_basket(products):
base_basket_sum = Decimal("0")
for product in products:
base_basket_sum += int(product["monthly_purchases"]) * to_decimal(product["base_price"])
return base_basket_sum
Once again, we'll test this.
if __name__ == "__main__":
products = read_csv("products.csv")
print(calculate_base_basket(products))
You should see the total cost of your monthly basket printed to the console. It's worth double-checking the output the first time to ensure nothing was misconfigured.
Calculate the latest basket
If all the math checks out, we can move to fetching the latest prices. We'll add a calculate_latest_basket function to do this.
This function will run a new price check for each product in our CSV, print the new price, and add it to our latest monthly basket cost. If a product can't be found, it prints a message, adds the product to a not_found list, and moves on to the next product. The function returns both the basket total and that list.
def calculate_latest_basket(products):
latest_basket_sum = Decimal("0")
not_found = []
for product in products:
latest_price = fetch_product_price(product)
if latest_price is None:
print(f"Skipped '{product['title']}' (ASIN: {product['asin']}): not found in search results")
not_found.append(product)
continue
print(f"{product['title']} base: {product['base_price']} | latest price: ${latest_price}")
latest_basket_sum += int(product["monthly_purchases"]) * latest_price
return latest_basket_sum, not_found
If you're running this soon after populating your products list, it's unlikely any prices have changed. As a start, we can test that everything works by ensuring base_basket_sum and new_basket_sum are equal.
if __name__ == "__main__":
products = read_csv("products.csv")
print(calculate_base_basket(products))
print(calculate_latest_basket(products))
Here's what I get. Looks like a match.
211.81
211.81
Calculating inflation
Add a test price change
We can next work on the logic to calculate your custom basket's inflation rate. However, to test this properly, it would be better to have a price difference to play with.
Let's manipulate the data for testing purposes by adding $0.25 to each product's monthly cost. This will add $2.50 to the total basket cost since there are 10 products listed.
def calculate_latest_basket(products):
latest_basket_sum = Decimal("0")
not_found = []
for product in products:
latest_price = fetch_product_price(product)
if latest_price is None:
print(f"Skipped '{product['title']}' (ASIN: {product['asin']}): not found in search results")
not_found.append(product)
continue
print(f"{product['title']} base: {product['base_price']} | latest price: ${latest_price}")
latest_basket_sum += int(product["monthly_purchases"]) * latest_price + Decimal("0.25") # artificially inflates prices
return latest_basket_sum, not_found
Now I get:
211.81
214.31
Calculate the price change
We'll then use the following simple formula to calculate the price change percentage.
In code:
inflation = ((latest_basket_sum - base_basket_sum) / base_basket_sum) * 100
We'll drop this formula into a check_inflation function.
def check_inflation(products):
latest_basket_sum, not_found = calculate_latest_basket(products)
found_products = [product for product in products if product not in not_found]
base_basket_sum = calculate_base_basket(found_products)
inflation = ((latest_basket_sum - base_basket_sum) / base_basket_sum) * 100
print(f"Your basket has increased by {inflation:.2f}%")
print(f"Base basket: ${base_basket_sum:.2f}")
print(f"Latest basket: ${latest_basket_sum:.2f}")
Then we'll call it from main.
if __name__ == "__main__":
products = read_csv("products.csv")
check_inflation(products)
Expected output:
Your basket has increased by 1.18%
Base basket: $211.81
Latest basket: $214.31
Annualize the inflation rate
This is helpful, of course, but a 1.18% change isn't useful without a standard time reference. A 1.18% change over a year would be negligible, but a 1.18% change over a week would be significant.
To get a more helpful number, we'll annualize the change and calculate this in a new function that will take a start date, end date, and the inflation rate we just calculated above.
def calculate_annual_rate(start_date, end_date, inflation):
inflation = inflation / 100
days = (end_date - start_date).days
if days < 1:
return None
periods_per_year = Decimal("365") / days
annual_rate = ((1 + inflation) ** periods_per_year - 1) * 100
return annual_rate
If you run the script on the same day as your date_checked, there's no time period to annualize yet, so the function returns None instead of dividing by zero.
We'll call the above function from check_inflation. I've made 5 additional changes to our previous version.
- Stop early if none of the products could be found
- Set a
start_dateusing thedate_checkedvalue of our first product - Set
end_dateequal to today - Update the prints to include dates and annual inflation
- List the products that cannot be found at the end
def check_inflation(products):
latest_basket_sum, not_found = calculate_latest_basket(products)
found_products = [product for product in products if product not in not_found]
if not found_products:
print("None of the products could be found. Check your ASINs and zip codes.")
return
base_basket_sum = calculate_base_basket(found_products)
inflation = ((latest_basket_sum - base_basket_sum) / base_basket_sum) * 100
start_date = datetime.strptime(products[0]["date_checked"], "%Y-%m-%d").date()
end_date = date.today()
annual_inflation = calculate_annual_rate(start_date, end_date, inflation)
print(f"{start_date} Base basket: ${base_basket_sum:.2f}")
print(f"{end_date} Latest basket: ${latest_basket_sum:.2f}")
print("===============================================================")
print(f"Between {start_date} and {end_date}")
print(f"Your basket has changed by {inflation:.2f}%")
if annual_inflation is None:
print("Run the script again on a later date to see the annual rate.")
else:
print(f"In annual terms, it's changed by {annual_inflation:.2f}%")
if not_found:
print("===============================================================")
print("The products that cannot be found:")
for product in not_found:
print(f"- {product['title']} (ASIN: {product['asin']})")
Test the full script
Before testing this, let's do one last manipulation to run this as if a month has passed since we checked prices. Let's force start_date to be 30 days ago.
def check_inflation(products):
...
start_date = date.today() - timedelta(days=30) # manipulated to be 30 days ago
end_date = date.today()
annual_inflation = calculate_annual_rate(start_date, end_date, inflation)
...
Finally, we can run the whole thing.
2025-09-24 Base basket: $211.81
2025-10-24 Latest basket: $214.31
===============================================================
Between 2025-09-24 and 2025-10-24
Your basket has changed by 1.18%
In annual terms, it's changed by 15.35%
That looks just right.
When a product cannot be found
If a product can't be found, the script skips it, leaves it out of both baskets, and lists it at the end. Here's the same test run with the spinach missing from the search results:
Blueberries, 1 Pint base: $4.49 | latest price: $4.49
Cage Free Large Brown Eggs, Grade AA, 1 Dozen base: $2.99 | latest price: $2.79
Amazon Grocery, Whole Milk, 1 Gallon, 128 Fl Oz base: $3.26 | latest price: $3.32
Amazon Grocery, Ground Beef, 80% Lean/20% Fat, 1 lb base: $6.93 | latest price: $6.99
Skipped 'Beef Ribeye Steak, Grass-Fed, Pasture-Raised | 0.625 lbs.' (ASIN: B01H0AI64Y): not found in search results
Quaker, Quick 1 Minute Whole Grain Oats, 42 Oz base: $4.99 | latest price: $4.45
Skipped 'Zucchini Squash, 1 Each"' (ASIN: B000P6G1AC): not found in search results
Skipped 'Laura Scudder's All Natural Nutty Peanut Butter, 16 oz. Jar' (ASIN: B00CJ8K18W): not found in search results
FAGE Total Greek Yogurt, 5% Whole Milk, Plain, 32 oz base: $6.99 | latest price: $6.96
Taylor Farms Spinach, 9 oz Bag base: $2.06 | latest price: $2.46
2025-09-24 Base basket: $178.73
2026-09-30 Latest basket: $178.86
===============================================================
Between 2025-09-24 and 2026-09-30
Your basket has changed by 0.07%
In annual terms, it's changed by 0.07%
===============================================================
The products that cannot be found:
- Beef Ribeye Steak, Grass-Fed, Pasture-Raised | 0.625 lbs. (ASIN: B01H0AI64Y)
- Zucchini Squash, 1 Each" (ASIN: B000P6G1AC)
- Laura Scudder's All Natural Nutty Peanut Butter, 16 oz. Jar (ASIN: B00CJ8K18W)
When this happens, check that the ASIN is correct and the product is still sold at your zip code.
Automating your Amazon Price Tracker
To see your inflation rate change over time, run the script on a schedule rather than manually. On macOS or Linux, run crontab -e and add this line to run the tracker every Monday at 9 AM and save each run to a log file:
0 9 * * 1 cd /path/to/tracker && SERPAPI_API_KEY="your_api_key" /usr/bin/python3 tracker.py >> tracker.log 2>&1
On Windows, use Task Scheduler to run the Python script on the same schedule. Replace tracker.py with the file name you saved the script as.
Each run uses one search per product. With 10 products, a weekly run uses about 40 searches a month, which fits in the free plan. A daily run uses about 300 searches a month, so reduce your list or upgrade your plan if you want daily checks.
We can wrap up here. The last thing I'll do is share the full working code below. The code below does not include the price and date manipulations for testing purposes. Add those yourself if you'd like.
Happy scraping!
Full Code
import csv
import os
from datetime import date, datetime, timedelta
from decimal import Decimal
import serpapi
client = serpapi.Client(api_key=os.environ["SERPAPI_API_KEY"])
def read_csv(file):
with open(file, newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
return list(reader)
def to_decimal(value):
return Decimal(str(value).replace("$", "").replace(",", ""))
def search(query, zip):
results = client.search({
"engine": "amazon",
"k": query,
"delivery_zip": zip,
})
return results
def fetch_product_price(product):
results = search(product["asin"], product["zip"])
organic_results = results.get("organic_results", [])
target = next((result for result in organic_results if result.get("asin") == product["asin"]), None)
if not target:
return None
return to_decimal(target.get("extracted_price"))
def calculate_base_basket(products):
base_basket_sum = Decimal("0")
for product in products:
base_basket_sum += int(product["monthly_purchases"]) * to_decimal(product["base_price"])
return base_basket_sum
def calculate_latest_basket(products):
latest_basket_sum = Decimal("0")
not_found = []
for product in products:
latest_price = fetch_product_price(product)
if latest_price is None:
print(f"Skipped '{product['title']}' (ASIN: {product['asin']}): not found in search results")
not_found.append(product)
continue
print(f"{product['title']} base: {product['base_price']} | latest price: ${latest_price}")
latest_basket_sum += int(product["monthly_purchases"]) * latest_price
return latest_basket_sum, not_found
def calculate_annual_rate(start_date, end_date, inflation):
inflation = inflation / 100
days = (end_date - start_date).days
if days < 1:
return None
periods_per_year = Decimal("365") / days
annual_rate = ((1 + inflation) ** periods_per_year - 1) * 100
return annual_rate
def check_inflation(products):
latest_basket_sum, not_found = calculate_latest_basket(products)
found_products = [product for product in products if product not in not_found]
if not found_products:
print("None of the products could be found. Check your ASINs and zip codes.")
return
base_basket_sum = calculate_base_basket(found_products)
inflation = ((latest_basket_sum - base_basket_sum) / base_basket_sum) * 100
start_date = datetime.strptime(products[0]["date_checked"], "%Y-%m-%d").date()
end_date = date.today()
annual_inflation = calculate_annual_rate(start_date, end_date, inflation)
print(f"{start_date} Base basket: ${base_basket_sum:.2f}")
print(f"{end_date} Latest basket: ${latest_basket_sum:.2f}")
print("===============================================================")
print(f"Between {start_date} and {end_date}")
print(f"Your basket has changed by {inflation:.2f}%")
if annual_inflation is None:
print("Run the script again on a later date to see the annual rate.")
else:
print(f"In annual terms, it's changed by {annual_inflation:.2f}%")
if not_found:
print("===============================================================")
print("The products that cannot be found:")
for product in not_found:
print(f"- {product['title']} (ASIN: {product['asin']})")
if __name__ == "__main__":
products = read_csv("products.csv")
check_inflation(products)
If you need help getting started, contact us. We're happy to help.



Top comments (0)