TikTok Shop Scraper - $1.10 per 1,000 Products avatar

TikTok Shop Scraper - $1.10 per 1,000 Products

Pricing

from $1.10 / 1,000 product scrapeds

Go to Apify Store
TikTok Shop Scraper - $1.10 per 1,000 Products

TikTok Shop Scraper - $1.10 per 1,000 Products

Units sold sits on the card of every TikTok Shop product, and it is what TikTok's own best-seller pages rank on. Search terms, collection URLs and store URLs mix in one run. Title, price, original price, discount, rating, reviews, shop, link, images. US storefront. $1.10/1,000.

Pricing

from $1.10 / 1,000 product scrapeds

Rating

0.0

(0)

Developer

Dami's Studio

Dami's Studio

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

5 days ago

Last modified

Share

TikTok Shop Scraper

Units sold sits on the card of every TikTok Shop product, and it is the number TikTok's own best-seller pages rank on. Search terms, TikTok Shop collection URLs and store URLs all go into the same run. Products come back as a table: title, price and currency, the crossed-out original price and the discount, star rating, review count, units sold, which shop sells it, the product link and the images.

No account, no API key, no browser.

  • Three ways in: a plain search term, a collection page URL, or a store page URL. Mix them in one run.
  • Units sold and star rating land on the same row as the price, so sorting a category by soldCount doesn't need a second lookup.
  • Sort a search by best sellers or by newest, not just the site default.
  • Rows are deduplicated on product id across the whole run, so a product that matches two of your terms is charged once.
  • Empty input returns one labelled sample row, free.

Price

$1.10 per 1,000 products. Plus $0.001 per run as the platform start fee, billed per gigabyte of run memory, so exactly that on the default 1 GB.

No volume tiers, no minimum spend, no subscription.

ProductsTotal
100$0.111
1,000$1.101
10,000$11.001
100,000$110.001

What gets charged

  • One product-scraped event per product row written to the dataset. Nothing else is metered per row.
  • Free: the sample row an empty run returns, and every diagnostic row. They all carry "charged": false.
  • Duplicate products that already appeared earlier in the same run are dropped before they're charged.
  • Products the page rendered but that carry no title are skipped rather than delivered as an empty row.
  • A run that finds nothing costs the start fee and nothing else.
  • Rows never leave the dataset without a charge and are never charged without a row. The billed event is a named one, so nothing is quietly attached to apify-default-dataset-item.

Input

{
"searchTerms": [
"makeup brush",
"wireless earbuds"
],
"startUrls": [
"https://shop.tiktok.com/us/k/probiotic-sodas",
"https://www.tiktok.com/shop/store/bk-beauty-brushes/7495182121564604692"
],
"sortBy": "best_sellers",
"maxItems": 200
}
FieldWhat it does
searchTermsKeywords to search TikTok Shop for. Each term is searched in turn until the run reaches maxItems.
startUrlsTikTok Shop pages to read instead of, or as well as, search terms. Collection pages (/shop/k/...), store pages (/shop/store/<name>/<seller id>) and search pages (/shop/s/...) are all understood. A bare seller id works too. Anything else gets a free diagnostic row telling you why it was skipped.
sortByHow products come back for a search term or a collection URL: relevance (site default), best_sellers (most units sold first) or newest. Store pages always come back in the shop's own order.
maxItemsTotal rows across every term and URL. The budget is split evenly between targets, so four targets and 100 rows gives you 25 of each. Default 50, hard ceiling 5,000. Keep it low while testing, since you pay per product.
proxyUrlsLeave empty. Only for callers who want traffic to leave through proxy servers they already pay for, as http://user:pass@host:port. They have to exit in a country where TikTok Shop operates.
sessionCookiesLeave empty. Runs work with nothing filled in. See Shop pages behind a bot check.

Run it with empty input and you get one labelled sample row, free.

Output

One row per product. This is a real row from a real run:

{
"ok": true,
"charged": true,
"recordType": "product",
"productId": "1732304214281785959",
"title": "A set of 13 professional makeup brushes, including soft fur eyeshadow brushes, foundation brushes, and blush brushes, for creating a perfect makeup look",
"price": 1.72,
"currency": "USD",
"priceFormatted": "$1.72",
"originalPrice": 4.19,
"discountPercent": 59,
"rating": 5,
"reviewCount": 1,
"soldCount": 24,
"shopName": "Suitable beauty tools",
"sellerId": "8654790995160371815",
"shopUrl": "https://www.tiktok.com/shop/store/suitable-beauty-tools/8654790995160371815",
"brand": null,
"productUrl": "https://shop.tiktok.com/us/pdp/professional-makeup-brush-set-13-soft-fur-brushes-perfect-makeup/1732304214281785959",
"imageUrl": "https://p16-oec-general-useast5.ttcdn-us.com/tos-useast5-i-omjb5zjo8w-tx/1f4954883fb143619c8fa9928ea79b4e~tplv-fhlh96nyum-crop-webp:800:800.webp",
"images": [
"https://p16-oec-general-useast5.ttcdn-us.com/tos-useast5-i-omjb5zjo8w-tx/1f4954883fb143619c8fa9928ea79b4e~tplv-fhlh96nyum-crop-webp:800:800.webp",
"https://p19-oec-general-useast5.ttcdn-us.com/tos-useast5-i-omjb5zjo8w-tx/1f4954883fb143619c8fa9928ea79b4e~tplv-fhlh96nyum-crop-webp:800:800.webp"
],
"promotionLabels": ["Free shipping"],
"stockStatus": null,
"searchTerm": "makeup brush",
"sourceType": "search",
"sourceUrl": "https://www.tiktok.com/shop/s/makeup%20brush",
"region": "US",
"scrapedAt": "2026-08-16T18:14:43.486Z"
}

Field notes

  • price is what's actually being charged now, as a number. priceFormatted is the same figure with the symbol the site shows.
  • originalPrice is the crossed-out "was" price. Null when the product isn't discounted, which is most of them.
  • discountPercent is the percentage off as a number rather than the "59%" string the page renders. Null when there's no discount.
  • rating is stars out of five. A brand-new listing with no reviews comes back as null rather than 0.
  • reviewCount is how many reviews the product has. 0 is a real answer for a new listing.
  • soldCount is lifetime units sold as TikTok reports it on the card, as a number. Null on a card that doesn't carry one.
  • shopName is the seller's shop name. sellerId and shopUrl on the same row let you jump to the whole catalogue, and you can feed that shopUrl back in as a startUrls entry.
  • productUrl is the canonical product page, with the slug the site itself uses.
  • images holds every image URL the listing card carries. imageUrl is the first, which is the one the site shows.
  • promotionLabels holds the promotion chips on the card, like Free shipping or Flash sale. An empty array means the card carried none.
  • stockStatus is set only where the listing itself publishes a stock signal, otherwise null. See the limits.
  • searchTerm says which of your terms or URLs produced the row, so a multi-target run stays sortable.
  • region is the storefront the row came from, as a two-letter country code.

Real rows carry "charged": true. Sample rows carry "_sample": true, diagnostic rows carry "_diagnostic": true and an errorCode. Neither is billed.

How it works

It reads TikTok Shop's own product feed directly, the same data the product grid is filled with, instead of rendering the page. No headless browser and no login.

Each target opens its own page once to pick up the guest session the site hands any first-time visitor, then asks for products a hundred at a time. That keeps the round trips at roughly one per hundred rows. Whatever the page already rendered server-side is read out of the page and costs no extra request.

Requests leave through a rotating pool of addresses in the storefront's own country, because TikTok Shop decides which catalogue you see from where the request comes from. A thin or empty answer is treated as a throttled address rather than an empty catalogue: the run moves to a fresh address and asks again before it will accept "there is nothing here".

Shop pages behind a bot check

TikTok serves some shop pages behind a bot check to every logged-out visitor. Not to this Actor in particular, and not because of where the request comes from.

When that happens the run looks the shop up by name through search and keeps the products belonging to that seller, which usually recovers most of the shelf. Measured over 75 store reads: 72 came back with products, 3 did not. Search terms and collection URLs aren't affected. Those returned products on every one of 125 reads. If nothing at all comes back you get one uncharged BLOCKED row and pay nothing for the attempt.

If you'd rather the run retried such a page with a signed-in reader, put your own TikTok sessionid cookie into sessionCookies. It's optional and most people should leave it empty. A cookie is as good as a password for the account it belongs to, so use an account you're comfortable exposing, and expect it to stop working when you log out of that account.

What people use it for

  • Product research. Pull a category, sort by units sold, see what's actually moving before you commit to stocking it. Rating and review count sit on the same row, so you can filter out the one-review listings.
  • Shelf tracking. Feed in a store URL and get that seller's whole shelf with prices, discounts and sold counts. Re-run on a schedule and diff on productId.
  • Price monitoring. price, originalPrice and discountPercent are numbers, so a daily run turns straight into a price history without any parsing.
  • Affiliate sourcing. Find high-sold, well-rated products in a niche and jump to the shop behind each through shopUrl.
  • Promo research. promotionLabels shows which listings are running flash sales right now, which is a decent proxy for what a seller is pushing.

Reading the output

  • Real rows carry "charged": true and "recordType": "product". One billed event each.
  • The sample row carries "_sample": true and "charged": false. There's exactly one, only when the input had no terms and no URLs.
  • Diagnostic rows carry "_diagnostic": true, "charged": false and an errorCode: NO_RESULTS when a term or shop returned nothing, NOT_FOUND when the URL or seller id doesn't exist, BAD_INPUT when a URL isn't a TikTok Shop page this Actor understands, NETWORK when the target couldn't be reached, TIME_BUDGET when time ran out. Each carries plain-English text and the target it belongs to.

Filter on charged == true and you have exactly the rows you paid for.

Which URLs work

You pasteWhat you get
https://shop.tiktok.com/us/k/probiotic-sodasthe collection page's products, paged until maxItems
https://www.tiktok.com/shop/store/bk-beauty-brushes/7495182121564604692that shop's catalogue, in the shop's own order
https://www.tiktok.com/shop/s/makeup%20brushthe same as putting makeup brush in searchTerms
7495182121564604692a bare seller id, treated as a store

A single product page URL isn't a valid target. This returns lists of products, and a product page would be a list of one. Paste the shop URL from the product page instead, or search for the product name.

Limits

  • This reads the US storefront. TikTok decides which catalogue to serve from where the request comes from, and the addresses this Actor uses land in the US. For another storefront, supply your own proxy servers exiting in that country through proxyUrls. The region field on every row tells you which storefront actually answered.
  • The per-unit inventory count isn't published on the product listing surface, so stockStatus is null for most rows. It's filled in only where the listing itself says something definite.
  • A single product page URL isn't a supported target.
  • sortBy applies to search terms and collection URLs. A store page comes back in the order the shop arranges its own shelf, which the site doesn't let you re-sort.
  • Prices, ratings, review counts and sold counts are a snapshot at read time. They move, sometimes hourly on a flash sale.
  • Search depth isn't unlimited. TikTok stops handing out more pages for a term after a few hundred products, and when it does the run stops rather than repeating rows. Narrow the term, or add more terms, to go wider.
  • brand is only present when the seller filled it in, which is roughly four listings in ten.
  • Product descriptions, variant and SKU prices, shipping cost, delivery estimates and individual reviews aren't part of a listing row.
  • Hard ceilings: 5,000 rows and 20 targets per run.
  • Image URLs are TikTok CDN links carrying an expiry, so download anything you want to keep rather than hot-linking it.

Questions

What happens if a term matches nothing?

One uncharged diagnostic row for that target with errorCode: "NO_RESULTS", and the run carries on.

Do I need a TikTok account, a cookie or an API key?

No. There's no API key anywhere in this and you don't have to supply a cookie. Runs read the same public feed a logged-out visitor sees.

So what is sessionCookies for?

The minority of shop pages TikTok puts behind a bot check for logged-out visitors, plus a rate limit nobody else shares if you're running heavily. Optional, and most people should leave it empty.

Do I need a proxy?

No. The run brings its own egress and its cost is already inside the price. proxyUrls exists only for callers who want traffic to leave through servers they already own, for example to reach a storefront in another country.

Will the run fail if something goes wrong?

No. A blocked, empty or broken target produces an uncharged diagnostic row and the run still finishes as succeeded. A failed run would still bill the start fee, which would mean paying to be told something went wrong.

How do I get the best sellers in a category?

Put the category as a search term, or paste a collection URL, set sortBy to best_sellers, and read the soldCount column. That's the ordering TikTok itself uses for its ranking pages.

Can I run it on a schedule?

Yes. Nothing is held between runs. Use productId to work out which rows are new and to diff prices since last time.