Skip to content

Latest commit

 

History

History
188 lines (138 loc) · 6.88 KB

File metadata and controls

188 lines (138 loc) · 6.88 KB

amazon-price-scraper-python

Track Amazon prices from Python, without the two bugs that silently corrupt almost every price dataset.

pip install git+https://github.com/thunderbit-operations/amazon-price-scraper-python
amzn-price track asins.txt --marketplace de --out prices.csv
asin,marketplace,price_amount,price_currency,list_price_amount,captured_at
B0CR7N9MD9,de,24.99,EUR,29.99,2026-07-28T13:19:54Z

This is a focused wrapper around amazon-product-scraper-python, which does the actual fetching and parsing. If you need the full product record — variants, images, sellers, ranks — use that directly. This package exists for the narrower job of collecting prices over time, and for the price-specific traps below.


The two bugs in almost every price scraper

1. .a-price-whole does not contain the whole price

This is the node every tutorial tells you to read. Here is what Amazon actually serves:

<span class="a-price">
  <span class="a-offscreen">₹150.00</span>
  <span aria-hidden="true">
    <span class="a-price-symbol"></span>
    <span class="a-price-whole">150<span class="a-price-decimal">.</span></span>
    <span class="a-price-fraction">00</span>
  </span>
</span>

.a-price-whole contains a nested .a-price-decimal, so its text content is "150." — not "150". Depending on your locale settings that either parses to the wrong number or throws. Neither outcome is loud.

The correct node is .a-offscreen: the fully formatted, locale-correct string that screen readers get. This package reads that one, and the underlying library rejects any selector mentioning .a-price-whole at config load time rather than documenting the hazard and hoping.

2. Currency is not determined by the domain

Fetch amazon.co.jp from a US IP address and the prices come back in USD, not yen. Amazon localises currency to the detected shopper locale — IP geography plus the delivery-location cookie — independently of the TLD.

Every price scraper surveyed maps TLD → currency. That produces a dataset where some rows are yen and some are dollars, all labelled JPY, with no way to tell them apart afterwards. This is not a recoverable error: once the rows are written, the true currency of any given row is gone.

Here, currency is always parsed from the page and carried alongside the amount:

price.amount      # Decimal("3.34")
price.currency    # "USD"  -- what the page said, not what the TLD implies
price.matches_expected("JPY")   # False -> surfaced, not swallowed

Every record is checked against what that storefront would normally quote, and mismatches are reported rather than silently normalised away. To pin a currency, route through an in-country proxy and assert on the detected location.


Usage

Track a list of ASINs

amzn-price track asins.txt --marketplace de --out prices.csv

Appends one row per ASIN. Re-running skips ASINs already captured today unless you pass --no-resume, so a killed run resumes instead of re-fetching.

Single lookup

amzn-price get B0CR7N9MD9 --marketplace in

As a library

import asyncio
from amazon_price_scraper import prices

async def main():
    async for row in prices(["B0CR7N9MD9", "B08N5WRWNW"], marketplace="in"):
        if row.price is None:
            print(f"{row.asin}: {row.status}")      # blocked / not_found / no_price
            continue
        print(f"{row.asin}: {row.price.amount} {row.price.currency}")

asyncio.run(main())

Rows are yielded as they complete and carry a status rather than raising, so one blocked page does not abort a run of ten thousand.


What you get per row

Field Notes
price_amount Decimal, never a float
price_currency Parsed from the page, ISO 4217
list_price_amount The struck-through price, when shown
currency_matches_marketplace False means the storefront quoted a foreign currency
availability / in_stock An out-of-stock product often has no price at all
provenance Which extraction strategy produced the price — see below
status ok, not_found, blocked_*, no_price

Why provenance matters for price tracking

Each price records how it was extracted. When Amazon moves the price container, extraction usually keeps working via a weaker fallback — the numbers still arrive and still look fine. The provenance shift is the only early signal you get, and for a time series it matters more than for a one-off scrape: a silent change in which container you are reading can change which price you are collecting (current vs list vs subscribe-and-save).


Blocked pages are not prices

Amazon runs three anti-bot stacks and all three answer with a 2xx status code. A scraper that checks resp.status_code records the challenge page as a product and writes a null price — which in a price history is indistinguishable from a genuine out-of-stock.

This package inherits the underlying library's classifier, which identifies each stack by header and signature and asserts positively that the product marker is present before accepting the page. A blocked row is labelled blocked_*, never written as a missing price.

It also knows a real 404 from a block, so a delisted ASIN does not get retried against fresh proxies.


Legal and responsible use

Rate limiting is on by default (~1 request/second per IP, jittered) and robots.txt is respected by default. This package ships no CAPTCHA solving, no proof-of-work computation and no rate-limit evasion, and pull requests adding them will be declined.

Amazon's Conditions of Use specifically name "any collection and use of any product listings, descriptions, or prices". Whether such terms bind a client that never logs in is genuinely contested — read the full legal section in the parent project before deploying this. Nothing here is legal advice, and you are responsible for your own use.

If an official API meets your need, prefer it: the Creators API for affiliate data (Product Advertising API 5.0 is deprecated), or SP-API for your own seller account.

This project is not affiliated with, authorised or endorsed by Amazon.

Who maintains this

Built and maintained by the team behind Thunderbit, an AI web scraper, alongside the core library this package is a thin layer over.

No account, API key or quota is involved — nothing here calls a Thunderbit service. If you would rather not keep a price tracker working as Amazon changes its markup, Thunderbit is the hosted answer to the same problem.

Licence

Apache-2.0 — free for commercial use. See LICENSE.