kestrel actors docs

kestrel actorsGuides

How to track Amazon Best Sellers Rank daily

Most Amazon chart scrapers return ranks 1-30 and 51-80 and call it the top 100. The store renders 30 of the 50 entries on a chart page as HTML and lazy-loads the rest, so the gaps are silent, contiguous-looking and wrong.

The short version

  1. Name the category node, exactly as the store writes it. Slugs differ between marketplaces. Open the chart on the marketplace you want and copy the segment as printed.
  2. Ask for the ranks the page hides. The page carries the complete ranked list for its own lazy-loader; the same load-more call the browser makes returns the remaining entries with title, price, rating and rating count.
  3. Keep the whole row, not just the rank. Price, rating and rating count on the same row are what turn a rank into a decision.
  4. Diff between runs. Rank change tells you who moved. The change in rating count is the closest public proxy for how fast a product is selling.

The 30-of-50 problem

An Amazon Best Sellers chart page covers 50 ranks. The store renders 30 of them into the HTML and lazy-loads the other 20 when a browser scrolls.

So a scraper that parses the page as served returns ranks 1-30 from page one and 51-80 from page two. Nothing errors. The rows are real, the ranks are real, and the output looks like a top 80 with a strange gap that most people never check for, because rank is usually read as an ordering rather than as a set of integers that should be contiguous.

The complete list is available without a browser: the page itself carries the full ranked set of product identifiers for its own lazy-loader, and the same load-more call the browser makes returns the remaining entries rendered exactly like the first 30 — title, price, rating, rating count, image. Two pages then really are ranks 1-100, in order, with nothing missing.

When that call is refused, the missing entries can be completed from their product pages instead; and if a product page is unavailable too, the honest output is still a row with its rank, identifier and URL, counted as partial in the status row, so you can see exactly how complete the chart is rather than inferring it.

Working code

from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("kestrel/amazon-best-sellers-scraper").call(run_input={
    "categories": ["electronics/headphones"],
    "domain": "com",
    "pages": 2,            # ranks 1-100, contiguous
    "chart": "bestsellers",
})
rows = [r for r in client.dataset(run["defaultDatasetId"]).iterate_items() if r["type"] == "product"]
rows.sort(key=lambda r: r["rank"])
missing = [i for i in range(1, len(rows) + 1) if i not in {r["rank"] for r in rows}]
print(len(rows), "ranks;", "gaps:", missing or "none")
for r in rows[:10]:
    print(r["rank"], r["asin"], r["price"], r["currency"], r["rating"], r["ratings_count"], r["title"][:50])

That gap check is worth keeping in your pipeline permanently. It is two lines, and it is the difference between a chart dataset you can trust and one you assume.

What to diff between runs

Rank alone is a weak signal because it is relative: a product can climb because it sold more, or because the products above it sold less. The row carries the columns that disambiguate it.

ColumnWhat its change means
rankSomething moved. Not how much
ratings_countThe closest public proxy for sales velocity — new ratings arrive roughly in proportion to units sold
priceWhether a competitor bought their way up the chart
ratingWhether the climb is holding up with buyers

The questions a weekly diff actually answers: who moved above me and did they cut price to do it; is the product beating me winning on rating or on review volume; which entries are new to the top 100 this week and which fell out; how is the category's median price drifting.

None of those exist without kept history, which is the argument for a schedule rather than an ad-hoc run. Every row is timestamped, so rank over time is a group-by.

Best Sellers plus New Releases

Best Sellers tells you what is established. New Releases tells you what is arriving. Pull both for the same category and join on the product identifier: entries that appear in New Releases and then climb into Best Sellers within a few weeks are the genuinely trending products, and detecting that crossing is a join rather than a hunch.

Feeding the rest of the catalogue work

Chart rows are the cheapest way to build a target list. The identifiers you get from a chart go straight into the other Amazon actors:

asins = [r["asin"] for r in rows[:50]]
client.actor("kestrel/amazon-reviews-scraper").call(run_input={"asins": asins, "maxRating": 3})
client.actor("kestrel/amazon-product-scraper").call(run_input={"asins": asins, "domain": "com"})

Remember the ceiling on the review side: the public product page shows 8-13 reviews per product, so a 50-product pull is a few hundred rows of recent, shopper-visible feedback rather than a full archive. How to scrape Amazon reviews has the detail.

And be careful with prices from the product page: the price there is the buybox offer only, and it is frequently absent for unauthenticated clients — on one 1.8 MB product page in testing there was no price element at all. Chart and search pages render prices far more reliably, which is why price tracking belongs on Amazon Search and the chart actor rather than the product actor.

The honest limitations

Run it

Amazon Best Sellers Scraper — $0.004 per delivered product row, with status rows, empty categories and filtered rows free. Related: tracking competitor prices daily with n8n for the diffing workflow, and how to scrape Amazon reviews for what the review side can and cannot give you.

FAQ

Why does my chart scraper skip ranks 31-50?

Because a chart page holds 50 ranks and the store renders only 30 of them as HTML — the last 20 are lazy-loaded in the browser. A scraper that reads the page as served returns 1-30, then 51-80 from page two, and presents that as the top 100. The output looks contiguous unless you check the rank column for gaps.

Is Best Sellers Rank the same as units sold?

No. The marketplace does not publish sales volume anywhere. Rank is position within a category node, and it moves on relative sales velocity, so a rank change tells you something moved without telling you how much. The change in a product's rating count between two runs is the closest public proxy for volume, and it only exists if you keep your runs.

Can I get Movers and Shakers?

Not honestly. That chart is rendered entirely in the browser — the page as served carries no entries at all — so there is nothing to read without running a browser. It is left out rather than shipped as a feature that returns empty.

Why do some rows have no price?

Because the tile printed a range, or no price at all. Prices are read from the price element itself, including its currency, never assumed from the marketplace — a run on one marketplace can return rows priced in another currency when the store localises to the caller's location.

Run Amazon Best Sellers Scraper on Apify →

Other actors used on this page: apify.com/kestrel/amazon-search-scraper, apify.com/kestrel/amazon-product-scraper, apify.com/kestrel/amazon-reviews-scraper.

More guides