[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-post-en-tiktok-shop-product-data-research":3},{"success":4,"payload":5,"related":81,"errors":157},true,{"id":6,"slug":7,"title":8,"excerpt":9,"body_html":10,"meta_title":11,"meta_description":12,"canonical_url":13,"canonical_override":14,"og_title":15,"og_description":12,"og_image_url":16,"faq_json":17,"key_takeaways":33,"allow_index":4,"noindex_google_only":39,"allow_follow":4,"published_at":40,"updated_at":41,"content_updated_at":42,"featured_image_url":43,"featured_image_alt":8,"card_image_url":44,"reading_time_minutes":45,"author":46,"categories":50,"tags":57,"alternates":73,"locale_paths":79,"views":80},215,"tiktok-shop-product-data-research","Collecting TikTok Shop Product Data for Market Research","Official routes first: what the TikTok Shop Partner API and Research API really expose, when licensed data wins, and how to read public product pages slowly and in the right market.","\u003Cp>If you need TikTok Shop product data for pricing or assortment research, the official routes come first. The TikTok Shop Partner API gives you full catalogue and order data, but only for shops that authorise your app. The TikTok Research API returns product, price and sold-count fields for EU shops, but only to approved non-commercial researchers. Most commercial teams studying competitors end up with licensed data from analytics vendors, or a slow, polite collector that reads the structured data public product pages already publish, routed through IPs in the market being studied.\u003C\u002Fp>\n\n\u003Cp>This guide is written for market research. It is not affiliated with or endorsed by TikTok, and it does not cover TikTok's private, signed internal APIs.\u003C\u002Fp>\n\n\u003Ch2>What TikTok Shop product data teams actually need\u003C\u002Fh2>\n\n\u003Cp>Most research questions come down to about eight fields: product title, current price, list price (and so the discount), sold count, shop, rating and review count, category, and which creators are promoting the item.\u003C\u002Fp>\n\n\u003Cul>\n\u003Cli>Pricing research. Current price, list price, currency and active promotions, compared across sellers of the same or equivalent product. This needs consistent timestamps and a fixed market. A price from the UK storefront and one from Germany are different observations, not one noisy one.\u003C\u002Fli>\n\u003Cli>Assortment analysis. Which categories a shop covers, how many SKUs and variants it lists, and where its prices sit in the category. Weekly snapshots are usually enough.\u003C\u002Fli>\n\u003Cli>Trend detection. Changes in sold count and review count over time, by category. The absolute numbers matter less than the slope, which is lucky, because displayed sales numbers are imprecise (more on that below).\u003C\u002Fli>\n\u003Cli>Creator and affiliate mapping. Which creators link to a product. This is the field with the most legal weight, because it is about people, not products.\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Cp>If you have built price trackers for other marketplaces, the shape is familiar. Our guides on \u003Ca href=\"\u002Fblog\u002Fmonitoring-competitor-prices\">automated competitor price monitoring\u003C\u002Fa> and \u003Ca href=\"\u002Fblog\u002Fgeo-targeted-pricing-monitoring\">tracking prices across markets\u003C\u002Fa> cover the pipeline side. What is different on TikTok Shop is how much of the catalogue is gated by market and by login.\u003C\u002Fp>\n\n\u003Ch2>Official routes to TikTok Shop data\u003C\u002Fh2>\n\n\u003Cp>There are three legitimate sources of structured TikTok Shop data, and none of them simply hands a commercial team its competitors' catalogues. Check them first anyway: the first two are free if you qualify, and the third often costs less than building.\u003C\u002Fp>\n\n\u003Ch3>TikTok Shop Partner Center and the TTS API\u003C\u002Fh3>\n\n\u003Cp>The \u003Ca href=\"https:\u002F\u002Fpartner.tiktokshop.com\u002Fdocv2\u002Fpage\u002Ftts-api-concepts-overview\" rel=\"noopener noreferrer\">TikTok Shop Partner Center\u003C\u002Fa> is where developers register apps for the TikTok Shop Open Platform. TikTok's own overview describes the TTS API as giving access to \"authorized TikTok Shop data, including catalogs, orders, shipments, payments, and more\". Its categories include Product, Order, Fulfillment, Return &amp; Refund, Logistics, Promotion, Finance, Seller, Authorization, Events, Data Reconciliation and Supply Chain APIs.\u003C\u002Fp>\n\n\u003Cp>The word that matters is \u003Cem>authorized\u003C\u002Fem>. The docs list three authorization types: seller (a shop grants your app access), creator, and partner. Every one of them depends on the data owner opting in. If you are a seller, or a tool vendor whose customers are sellers, this is the best data you can get: exact prices, real inventory and real order counts for those shops. It is not a way to look at shops that have not connected your app. The docs also \u003Ca href=\"https:\u002F\u002Fpartner.tiktokshop.com\u002Fdocv2\u002Fpage\u002Fregions-and-languages\" rel=\"noopener noreferrer\">list supported regions and locales\u003C\u002Fa>, with a note that availability changes as markets open.\u003C\u002Fp>\n\n\u003Ch3>TikTok Research API, which does include EU shop data\u003C\u002Fh3>\n\n\u003Cp>The \u003Ca href=\"https:\u002F\u002Fdevelopers.tiktok.com\u002Fproducts\u002Fresearch-api\" rel=\"noopener noreferrer\">TikTok Research API\u003C\u002Fa> is mostly known for public videos, comments and accounts. It also has three TikTok Shop endpoints that many people miss. The \u003Ca href=\"https:\u002F\u002Fdevelopers.tiktok.com\u002Fdocs\u002Fen\u002Fresearch-api-specs-query-tiktok-shop-info\" rel=\"noopener noreferrer\">shop info\u003C\u002Fa> endpoint returns shop rating, review count, items sold and a performance value. The \u003Ca href=\"https:\u002F\u002Fdevelopers.tiktok.com\u002Fdocs\u002Fen\u002Fresearch-api-specs-query-tiktok-shop-products\" rel=\"noopener noreferrer\">shop products\u003C\u002Fa> endpoint returns product name, price, description, sold count, review count and a 1–5 star breakdown. The \u003Ca href=\"https:\u002F\u002Fdevelopers.tiktok.com\u002Fdocs\u002Fen\u002Fresearch-api-specs-query-tiktok-shop-reviews\" rel=\"noopener noreferrer\">product reviews\u003C\u002Fa> endpoint returns review text, rating and timestamps. All three are scoped to shops and products available in the EU, and they return at most 10 results per call.\u003C\u002Fp>\n\n\u003Cp>The catch is eligibility. Access goes to academic institutions in the US, EEA, UK, Canada and Switzerland, to not-for-profit research organisations in the EU, and to some Brazilian institutions studying youth safety. Applicants must be \"independent of commercial interests\". The \u003Ca href=\"https:\u002F\u002Fdevelopers.tiktok.com\u002Fdocs\u002Fen\u002Fresearch-api-faq\" rel=\"noopener noreferrer\">Research API FAQ\u003C\u002Fa> gives a standard quota of 1,000 requests per day. A university group studying EU social commerce should apply; a brand's pricing team is not eligible and should not borrow a research partner's access.\u003C\u002Fp>\n\n\u003Ch3>Licensed data from analytics vendors\u003C\u002Fh3>\n\n\u003Cp>There is a whole category of TikTok Shop analytics products that sell product rankings, estimated sales, creator-to-product links and category trends through a dashboard or an API. Before you buy, ask three things. Which markets are covered? Are sales figures observed or modelled? Can you redistribute the data internally? A vendor that cannot say where its numbers come from is giving you estimates, and you should treat them that way.\u003C\u002Fp>\n\n\u003Ctable>\n\u003Cthead>\u003Ctr>\u003Cth>Route\u003C\u002Fth>\u003Cth>Who can get it\u003C\u002Fth>\u003Cth>What it covers\u003C\u002Fth>\u003Cth>Competitor data?\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\u003Ctd>TTS API (Partner Center)\u003C\u002Ftd>\u003Ctd>Sellers, and app developers authorised by sellers, creators or partners\u003C\u002Ftd>\u003Ctd>Full catalogue, prices, inventory, orders, promotions, finance for authorised shops\u003C\u002Ftd>\u003Ctd>No, only shops that connected your app\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Research API, TikTok Shop endpoints\u003C\u002Ftd>\u003Ctd>Approved non-commercial researchers\u003C\u002Ftd>\u003Ctd>EU shops: shop rating and sales, product price and sold count, reviews\u003C\u002Ftd>\u003Ctd>Yes, but non-commercial research only\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Analytics vendors\u003C\u002Ftd>\u003Ctd>Anyone who pays\u003C\u002Ftd>\u003Ctd>Rankings, estimated sales, creator links, history\u003C\u002Ftd>\u003Ctd>Yes, within the licence; accuracy varies\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Public product pages\u003C\u002Ftd>\u003Ctd>Anyone in a served market\u003C\u002Ftd>\u003Ctd>Whatever the page shows a logged-out visitor\u003C\u002Ftd>\u003Ctd>Yes, subject to ToS and robots.txt\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\n\u003Ch2>Public TikTok Shop pages: what exists on the open web\u003C\u002Fh2>\n\n\u003Cp>TikTok Shop is mostly an in-app experience, but there is a browser storefront at \u003Ccode>shop.tiktok.com\u003C\u002Fcode>, organised by market, with product detail pages under a \u003Ccode>\u002Fpdp\u002F\u003C\u002Fcode> path. The market you see depends on where you connect from. When we requested a \u003Ccode>www.tiktok.com\u002Fshop\u002Fpdp\u002F…\u003C\u002Fcode> URL from a server in Germany, it returned a 301 redirect to \u003Ccode>shop.tiktok.com\u002Fde\u002Fpdp\u002F…\u003C\u002Fcode>. The bare \u003Ccode>www.tiktok.com\u002Fshop\u003C\u002Fcode> path, requested from the same machine, redirected to \u003Ccode>shop.tiktok.com\u002Fus\u003C\u002Fcode>.\u003C\u002Fp>\n\n\u003Cp>On market coverage: TikTok's newsroom announced that \u003Ca href=\"https:\u002F\u002Fnewsroom.tiktok.com\u002Ftiktok-shop-expands-across-europe?lang=en-150\" rel=\"noopener noreferrer\">Austria, Belgium, the Netherlands and Poland would launch on 15 June 2026\u003C\u002Fa>, joining France, Germany, Ireland, Italy, Spain and the UK. The Partner Center region list (last updated 6 July 2026) also includes the US, Mexico, Brazil, Japan, Indonesia, Malaysia, the Philippines, Singapore, Thailand and Vietnam. That gives 20 markets with an official source. Third-party lists name more, but we could not confirm those against TikTok's own pages, so they are left out here.\u003C\u002Fp>\n\n\u003Cp>Three things follow for collection:\u003C\u002Fp>\n\n\u003Cul>\n\u003Cli>Geography decides what you see. The same product can have a different price, currency, availability or seller in each market, or not exist at all. To collect the UK storefront, your traffic has to come from the UK. This is the main reason residential IPs with country targeting matter here, such as \u003Ccode>USERNAME-country-GB\u003C\u002Fcode> on a \u003Ca href=\"\u002Fproxy\u002Fresidential\">residential proxy\u003C\u002Fa>. It has nothing to do with disguise.\u003C\u002Fli>\n\u003Cli>The pages rely heavily on JavaScript. The storefront is a client-rendered app. Response headers from \u003Ccode>shop.tiktok.com\u003C\u002Fcode> include \u003Ccode>x-processed-by: Modern.js\u003C\u002Fcode>, which is ByteDance's open-source web framework. Expect a real browser to be needed.\u003C\u002Fli>\n\u003Cli>There is an interstitial. From our datacenter test box, both \u003Ccode>shop.tiktok.com\u002Fde\u003C\u002Fcode> and \u003Ccode>shop.tiktok.com\u002Frobots.txt\u003C\u002Fcode> returned an HTML page titled \"Security Check\" that loads a CAPTCHA script. We stopped there. Solving or bypassing that challenge is out of scope, and if your collector hits it repeatedly, the right response is to slow down or stop.\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Ch2>Where the data lives on a product page\u003C\u002Fh2>\n\n\u003Cp>On modern storefronts, the fields you want rarely sit in tidy HTML. They usually live in one of three places, in descending order of stability: a JSON-LD block that follows \u003Ca href=\"https:\u002F\u002Fschema.org\u002FProduct\" rel=\"noopener noreferrer\">schema.org Product\u002FOffer\u003C\u002Fa> (published for search engines, see \u003Ca href=\"https:\u002F\u002Fdevelopers.google.com\u002Fsearch\u002Fdocs\u002Fappearance\u002Fstructured-data\u002Fproduct\" rel=\"noopener noreferrer\">Google's product markup docs\u003C\u002Fa>), Open Graph and product meta tags, and the framework's embedded hydration state, a large JSON blob inside a \u003Ccode>&lt;script&gt;\u003C\u002Fcode> tag that the app uses to render without another round trip.\u003C\u002Fp>\n\n\u003Cp>The technique is to read what the page already sent you. Parse any JSON-LD and meta tags as documented formats. For hydration state, inventory the JSON blobs (their IDs and sizes), look at one in a browser yourself, and map only the fields you need. Two things are explicitly out of scope: calling the private XHR endpoints the app uses behind the scenes, and reproducing their signature or token parameters. Forging their signatures is circumvention, not collection. We could not load a product page from our test machine (see the interstitial above), so we will not claim TikTok Shop pages carry JSON-LD. The code below works the same whether they do or not.\u003C\u002Fp>\n\n\u003Ctable>\n\u003Cthead>\u003Ctr>\u003Cth>Data point\u003C\u002Fth>\u003Cth>Where it usually lives\u003C\u002Fth>\u003Cth>Reliability\u003C\u002Fth>\u003Cth>Notes\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\n\u003Ctbody>\n\u003Ctr>\u003Ctd>Product title\u003C\u002Ftd>\u003Ctd>JSON-LD \u003Ccode>name\u003C\u002Fcode>, \u003Ccode>og:title\u003C\u002Fcode>, hydration state\u003C\u002Ftd>\u003Ctd>High\u003C\u002Ftd>\u003Ctd>May be localised per market; key on product ID, not title\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Current price\u003C\u002Ftd>\u003Ctd>JSON-LD \u003Ccode>offers.price\u003C\u002Fcode>, hydration state\u003C\u002Ftd>\u003Ctd>Medium\u003C\u002Ftd>\u003Ctd>Varies by market, variant and promotion; always store the currency\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>List price \u002F discount\u003C\u002Ftd>\u003Ctd>Hydration state; rarely in JSON-LD\u003C\u002Ftd>\u003Ctd>Medium-low\u003C\u002Ftd>\u003Ctd>Struck-through prices are seller-set; work out discounts yourself\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Sold count\u003C\u002Ftd>\u003Ctd>Hydration state, visible label; Research API (EU)\u003C\u002Ftd>\u003Ctd>Low-medium\u003C\u002Ftd>\u003Ctd>Labels are often abbreviated; use as a trend, not as audited sales\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Rating, review count\u003C\u002Ftd>\u003Ctd>JSON-LD \u003Ccode>aggregateRating\u003C\u002Fcode>, hydration state\u003C\u002Ftd>\u003Ctd>Medium\u003C\u002Ftd>\u003Ctd>May count reviews differently across markets; Research API has a star breakdown for the EU\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Shop name \u002F ID\u003C\u002Ftd>\u003Ctd>Hydration state, visible seller block\u003C\u002Ftd>\u003Ctd>High\u003C\u002Ftd>\u003Ctd>Store the ID; shop names change\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Category\u003C\u002Ftd>\u003Ctd>Breadcrumb JSON-LD, hydration state\u003C\u002Ftd>\u003Ctd>Medium\u003C\u002Ftd>\u003Ctd>Category trees differ by market; map them to your own taxonomy\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Variants \u002F SKUs\u003C\u002Ftd>\u003Ctd>Hydration state\u003C\u002Ftd>\u003Ctd>Medium\u003C\u002Ftd>\u003Ctd>Price ranges hide the price of the variant shoppers actually choose\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>Creator \u002F affiliate links\u003C\u002Ftd>\u003Ctd>Mostly in-app; partly in hydration state\u003C\u002Ftd>\u003Ctd>Low on the web\u003C\u002Ftd>\u003Ctd>Personal data; vendor or partner data is usually better here\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\n\u003Ch2>A polite collection design\u003C\u002Fh2>\n\n\u003Cp>A research collector should behave like a slow, honest visitor. That means one page at a time, a fixed set of URLs, aggressive caching, and stopping on challenges. TikTok's terms and robots rules make the limits clearer than most sites do, so start there.\u003C\u002Fp>\n\n\u003Ch3>What robots.txt and the terms actually say\u003C\u002Fh3>\n\n\u003Cp>\u003Ccode>www.tiktok.com\u002Frobots.txt\u003C\u002Fcode> (fetched 3 October 2026) disallows \u003Ccode>\u002Fshop\u002Fview\u002Fproduct\u002F\u003C\u002Fcode> for all user agents, and blocks the whole site for a named list of crawlers that includes GPTBot, ClaudeBot, CCBot, PerplexityBot and Bytespider. It does not mention \u003Ccode>\u002Fshop\u002Fpdp\u002F\u003C\u002Fcode>, and it has no Sitemap line. We could not read \u003Ccode>shop.tiktok.com\u002Frobots.txt\u003C\u002Fcode> from our test machine because it returned the HTML challenge page instead. That is a trap: Python's \u003Ccode>urllib.robotparser\u003C\u002Fcode> parses an HTML page as \"no rules\", which means \"allow everything\". The code below only trusts a plain-text 200 response.\u003C\u002Fp>\n\n\u003Cp>The terms are stricter than robots.txt. The \u003Ca href=\"https:\u002F\u002Fwww.tiktok.com\u002Flegal\u002Fpage\u002Fus\u002Fterms-of-service\u002Fen\" rel=\"noopener noreferrer\">US Terms of Service\u003C\u002Fa> (agreed with TikTok USDS Joint Venture LLC, last updated 15 July 2026) prohibit scraping or extracting data \"for any purpose, from the Platform using any automated system or software… except as approved in writing\". The \u003Ca href=\"https:\u002F\u002Fwww.tiktok.com\u002Flegal\u002Fpage\u002Feea\u002Fterms-of-service\u002Fen\" rel=\"noopener noreferrer\">EEA terms\u003C\u002Fa> say much the same. Whether they bind a logged-out visitor depends on jurisdiction, but they are a strong reason to prefer the official and licensed routes.\u003C\u002Fp>\n\n\u003Ch3>Design rules, with the reasons\u003C\u002Fh3>\n\n\u003Col>\n\u003Cli>\u003Cstrong>Start from a known URL list, not a crawl.\u003C\u002Fstrong> Research questions are about specific products or a sampled set. Crawling search and category pages is where load and ToS risk pile up, and TikTok's robots.txt disallows \u003Ccode>\u002Fsearch?\u003C\u002Fcode> paths anyway.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Use one market per run, and pin it.\u003C\u002Fstrong> Country-target the exit IP, and set the browser locale and timezone to match, so every observation in a batch is the same market.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Keep the rate low.\u003C\u002Fstrong> One page every 20–40 seconds per market is plenty for daily price snapshots of a few hundred products.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Cache by (product ID, market, day).\u003C\u002Fstrong> If you already have today's UK observation, don't fetch it again.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Treat a challenge as a stop signal.\u003C\u002Fstrong> Log it, wait, and lower your rate. Don't rotate IPs to get around the same block.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Don't log in.\u003C\u002Fstrong> Collect only what a logged-out visitor sees. Logged-in collection ties automation to an account and its terms, and touches personal data.\u003C\u002Fli>\n\u003C\u002Fol>\n\n\u003Ch2>A careful Python example with Playwright\u003C\u002Fh2>\n\n\u003Cp>The script below opens each product URL through a country-targeted proxy in Chromium. It checks a real robots.txt first, stops on non-200 responses or a \"Security Check\" title, and extracts JSON-LD Product data, a few meta tags, and a list of embedded JSON blobs for you to inspect. It does not use any TikTok-specific selectors, because we could not verify them, and every field comes back as \u003Ccode>None\u003C\u002Fcode> if it is missing. Proxy settings follow \u003Ca href=\"https:\u002F\u002Fplaywright.dev\u002Fpython\u002Fdocs\u002Fnetwork#http-proxy\" rel=\"noopener noreferrer\">Playwright's documented proxy option\u003C\u002Fa>. We tested the extraction and robots logic against local fixtures with Playwright 1.55. We did not run it against live TikTok Shop pages.\u003C\u002Fp>\n\n\u003Cpre>\u003Ccode class=\"language-python\">import json\nimport random\nimport time\nimport urllib.robotparser\nfrom urllib.parse import urlsplit\n\nfrom playwright.sync_api import sync_playwright\n\n# Country is chosen in the proxy username; match locale\u002Ftimezone to it.\nPROXY = {\n    \"server\": \"http:\u002F\u002Fgate.proxyhat.com:8080\",\n    \"username\": \"USERNAME-country-GB\",\n    \"password\": \"PASSWORD\",\n}\nUA_NAME = \"acme-price-research\"  # the token you'd look for in robots.txt groups\n\n# Product URLs you already have (from your own catalogue matching, a partner\n# export, or manual research), not URLs discovered by crawling search pages.\nURLS = [\n    \"https:\u002F\u002Fshop.tiktok.com\u002F...\",  # paste real product URLs as your browser shows them\n]\n\n\ndef robots_verdict(context, url):\n    \"\"\"Return True\u002FFalse from a real robots.txt, or None if we couldn't get one.\"\"\"\n    parts = urlsplit(url)\n    resp = context.request.get(f\"{parts.scheme}:\u002F\u002F{parts.netloc}\u002Frobots.txt\")\n    ctype = resp.headers.get(\"content-type\", \"\")\n    # An HTML challenge page parses as \"no rules\" (= allow everything) in\n    # urllib.robotparser, so only trust a plain-text 200.\n    if resp.status != 200 or \"text\u002Fplain\" not in ctype:\n        return None\n    rp = urllib.robotparser.RobotFileParser()\n    rp.parse(resp.text().splitlines())\n    return rp.can_fetch(UA_NAME, url)\n\n\ndef _types(node):\n    t = node.get(\"@type\", [])\n    return t if isinstance(t, list) else [t]\n\n\ndef extract(page):\n    \"\"\"Read what the page itself publishes: JSON-LD, Open Graph\u002Fproduct meta,\n    and an inventory of embedded JSON blobs for a human to inspect.\"\"\"\n    jsonld = []\n    for raw in page.locator('script[type=\"application\u002Fld+json\"]').all_text_contents():\n        try:\n            data = json.loads(raw)\n        except json.JSONDecodeError:\n            continue\n        nodes = data if isinstance(data, list) else data.get(\"@graph\", [data])\n        jsonld.extend(n for n in nodes if isinstance(n, dict))\n\n    meta = {}\n    for prop in (\"og:title\", \"og:url\", \"product:price:amount\", \"product:price:currency\"):\n        loc = page.locator(f'meta[property=\"{prop}\"]')\n        if loc.count():\n            meta[prop] = loc.first.get_attribute(\"content\")\n\n    blobs = page.evaluate(\n        \"\"\"() =&gt; [...document.querySelectorAll('script[type=\"application\u002Fjson\"]')]\n                 .map(s =&gt; ({id: s.id || null, chars: s.textContent.length}))\"\"\"\n    )\n\n    product = next((n for n in jsonld if \"Product\" in _types(n)), None)\n    record = {\"name\": None, \"price\": None, \"currency\": None,\n              \"availability\": None, \"rating\": None, \"review_count\": None}\n    if product:\n        record[\"name\"] = product.get(\"name\")\n        offers = product.get(\"offers\") or {}\n        if isinstance(offers, list):\n            offers = offers[0] if offers else {}\n        record[\"price\"] = offers.get(\"price\") or offers.get(\"lowPrice\")\n        record[\"currency\"] = offers.get(\"priceCurrency\")\n        record[\"availability\"] = offers.get(\"availability\")\n        agg = product.get(\"aggregateRating\") or {}\n        record[\"rating\"] = agg.get(\"ratingValue\")\n        record[\"review_count\"] = agg.get(\"reviewCount\") or agg.get(\"ratingCount\")\n    return {\"record\": record, \"meta\": meta, \"json_blobs\": blobs,\n            \"has_jsonld_product\": product is not None}\n\n\ndef main():\n    results = []\n    with sync_playwright() as p:\n        browser = p.chromium.launch(proxy=PROXY)\n        context = browser.new_context(locale=\"en-GB\", timezone_id=\"Europe\u002FLondon\")\n        page = context.new_page()\n        for url in URLS:\n            verdict = robots_verdict(context, url)\n            if verdict is not True:\n                results.append({\"url\": url, \"status\": \"skipped\", \"robots\": verdict})\n                continue\n            resp = page.goto(url, wait_until=\"domcontentloaded\", timeout=45_000)\n            status = resp.status if resp else None\n            if status != 200 or page.title().strip().lower() == \"security check\":\n                # Don't try to solve it. Record, slow down, move on.\n                results.append({\"url\": url, \"status\": \"challenge_or_error\", \"http\": status})\n                time.sleep(120)\n                continue\n            data = extract(page)\n            data.update({\"url\": url, \"final_url\": page.url, \"status\": \"ok\",\n                         \"fetched_at\": time.strftime(\"%Y-%m-%dT%H:%M:%SZ\", time.gmtime()),\n                         \"proxy_country\": \"GB\"})\n            results.append(data)\n            time.sleep(random.uniform(20, 40))  # one page at a time, slowly\n        browser.close()\n    print(json.dumps(results, indent=2, ensure_ascii=False))\n\n\nif __name__ == \"__main__\":\n    main()\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Cp>When \u003Ccode>has_jsonld_product\u003C\u002Fcode> comes back false, look at \u003Ccode>json_blobs\u003C\u002Fcode>. Open one product in a normal browser, find the blob that holds the price, and write a small, explicit mapping for the two or three keys you need. Add a test that fails loudly when those keys move. For a broader look at the JavaScript side, see \u003Ca href=\"\u002Fblog\u002Fscrape-javascript-heavy-websites\">scraping JavaScript-heavy websites\u003C\u002Fa>.\u003C\u002Fp>\n\n\u003Ch2>Data quality pitfalls\u003C\u002Fh2>\n\n\u003Cp>Most TikTok Shop datasets go wrong in analysis, not collection. Prices get compared across markets without normalisation, promotional prices get read as list prices, and rounded sold counts get treated as sales figures.\u003C\u002Fp>\n\n\u003Cul>\n\u003Cli>Region and currency. Store market, currency and the exit country with every row. Convert currencies at the observation date's rate, and never average prices across markets.\u003C\u002Fli>\n\u003Cli>Flash sales and promotions. Short promotions can move the displayed price for hours. Snapshot at the same time each day, and record both the shown price and any list price, so a sale is not mistaken for a permanent cut.\u003C\u002Fli>\n\u003Cli>Sold-count rounding. Displayed counts are often abbreviated, and even an exact integer in page data is the platform's figure, not an audited number. Use differences over time, and expect steps rather than smooth curves.\u003C\u002Fli>\n\u003Cli>Market-specific catalogues. A product missing from one market isn't out of stock. It may simply not be sold there. Keep \"not available in market\" separate from \"out of stock\".\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Cp>The same problems come up on other marketplaces we have covered. The \u003Ca href=\"\u002Fblog\u002Fscraping-shein-at-scale-2026\">Shein price-intelligence guide\u003C\u002Fa> deals with regional storefronts, the \u003Ca href=\"\u002Fblog\u002Fscrape-temu-product-price-data-2026-1\">Temu product-data guide\u003C\u002Fa> with embedded JSON, and the \u003Ca href=\"\u002Fblog\u002Fproxies-for-shopify-scraping\">Shopify guide\u003C\u002Fa> with documented public product endpoints, which is a useful contrast to platforms that don't have them.\u003C\u002Fp>\n\n\u003Ch2>Legal and ethical boundaries\u003C\u002Fh2>\n\n\u003Cp>Public product listings are the lowest-risk part of this dataset. Creators are the highest. A product's price is commercial information, but a creator's handle, profile and affiliate activity are personal data under the \u003Ca href=\"https:\u002F\u002Feur-lex.europa.eu\u002Feli\u002Freg\u002F2016\u002F679\u002Foj\" rel=\"noopener noreferrer\">GDPR\u003C\u002Fa> when they relate to an identifiable person. Collecting them for EU or UK work needs a lawful basis, data minimisation and a retention limit, even when the profile is public. Aggregate where you can, for example \"products promoted by more than 50 creators\", instead of building profiles of individuals.\u003C\u002Fp>\n\n\u003Cp>On top of that sit TikTok's terms, quoted above, and the trademark question: describe TikTok Shop factually, and don't present your dataset or tool as affiliated with it. If the research is commercial and ongoing, an official or licensed route is nearly always the more durable choice. A public-page collector works best for narrow, low-volume checks the official routes don't cover, such as confirming what a UK shopper actually sees today.\u003C\u002Fp>\n\n\u003Ch2>What this approach does not solve\u003C\u002Fh2>\n\n\u003Cp>A polite public-page collector won't give you full category coverage, creator attribution, live-stream sales or in-app-only content. It won't get past interstitials, and it shouldn't try. Over months it will also break as the frontend changes. If you need any of those, the gap is a data-licensing question, not an engineering one. For the network side of what remains, our \u003Ca href=\"\u002Fuse-cases\u002Fweb-scraping\">web scraping use case page\u003C\u002Fa> and \u003Ca href=\"\u002Flocations\">locations list\u003C\u002Fa> show which countries you can route through.\u003C\u002Fp>","TikTok Shop Product Data for Market Research | ProxyHat","How to get TikTok Shop product data for price research: the Partner API, the Research API's EU shop endpoints, licensed vendors, and polite page collection.","\u002Fblog\u002Ftiktok-shop-product-data-research",null,"TikTok Shop Product Data for Market Research","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fen\u002Ftiktok-shop-product-data-research-og.webp",[18,21,24,27,30],{"question":19,"answer":20},"Is there an official API for TikTok Shop product data?","Yes. The TikTok Shop TTS API, available through Partner Center, exposes catalogue, price, inventory and order data, but only for shops, creators or partners that authorise your app. It is not a way to read competitors' shops.",{"question":22,"answer":23},"Does the TikTok Research API include TikTok Shop data?","It does for the EU. The Research API has endpoints for shop info, shop products (including price and sold count) and product reviews, limited to shops and products available in the EU. Access is only for approved non-commercial researchers, such as academic institutions and not-for-profit research bodies.",{"question":25,"answer":26},"Why do TikTok Shop prices look different from another country?","TikTok Shop runs separate storefronts per market, and requests are routed to a market by location. Price, currency, seller and availability can all differ, so collect each market from an IP in that country and store the market with every observation.",{"question":28,"answer":29},"Do TikTok's terms allow automated collection from TikTok Shop pages?","TikTok's US and EEA terms prohibit automated data extraction without written approval, and robots.txt on www.tiktok.com disallows \u002Fshop\u002Fview\u002Fproduct\u002F for all crawlers. Prefer official or licensed data for commercial use, and keep any public-page collection minimal, slow and logged out.",{"question":31,"answer":32},"How accurate are TikTok Shop sold counts?","They are best treated as directional. Displayed counts are often abbreviated, and even exact figures are platform-reported rather than audited. Use changes over time to spot trends instead of treating them as revenue data.",[34,35,36,37,38],"The TTS API only returns data for shops, creators or partners that authorise your app, not competitors.","The Research API exposes EU shop, product, price and sold-count data, but only to non-commercial researchers.","TikTok Shop storefronts are per market, so collect each country from an IP in that country.","Read JSON-LD and embedded page data; leave private signed endpoints and challenge pages alone.","TikTok's terms prohibit automated extraction without approval, so prefer official or licensed data.",false,"2026-10-29T09:00:00+00:00","2026-10-29T09:00:19+00:00","2026-10-03T09:31:26+00:00","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fen\u002Ftiktok-shop-product-data-research-master.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog-covers\u002Fen\u002Ftiktok-shop-product-data-research-card.webp",16,{"name":47,"slug":48,"bio":49,"avatar_url":14,"website_url":14,"social_links":14},"ProxyHat Team","proxyhat-team","The ProxyHat editorial team covers proxies, web scraping, and data extraction.",[51],{"id":52,"slug":53,"name":54,"description":14,"meta_title":55,"meta_description":56,"parent_id":14},5,"ecommerce","E-commerce & Competitive Intelligence","E-commerce Scraping Guides | ProxyHat Blog","Guides on scraping e-commerce data, monitoring prices, and competitive intelligence with proxies.",[58,62,65,69],{"id":59,"slug":60,"name":61},1,"web-scraping","Web Scraping",{"id":63,"slug":53,"name":64},14,"E-commerce",{"id":66,"slug":67,"name":68},15,"price-monitoring","Price Monitoring",{"id":70,"slug":71,"name":72},18,"geo-targeting","Geo-Targeting",{"zh":74,"ko":75,"tr":76,"ru":77,"en":13,"de":78,"x-default":13},"\u002Fcn\u002Fblog\u002Ftiktok-shop-shangpin-shuju-caiji","\u002Fko\u002Fblog\u002Ftiktok-shop-sangpum-data-sujip","\u002Ftr\u002Fblog\u002Ftiktok-shop-urun-verisi-pazar-arastirmasi","\u002Fru\u002Fblog\u002Fdannye-tovarov-tiktok-shop-issledovanie","\u002Fde\u002Fblog\u002Ftiktok-shop-produktdaten-marktforschung",{"zh":74,"ko":75,"tr":76,"ru":77,"en":13,"de":78},2,[82,102,123,140],{"id":83,"slug":84,"title":85,"excerpt":86,"featured_image_url":87,"featured_image_alt":14,"card_image_url":88,"og_image_url":89,"published_at":90,"updated_at":91,"content_updated_at":92,"reading_time_minutes":93,"is_featured":39,"indexable":4,"views":94,"author":95,"categories":96,"tags":98},52,"geo-targeted-pricing-monitoring","Geo-Targeted Pricing Monitoring: Track Prices Across Markets","Learn how to build a geo-targeted pricing monitoring pipeline with proxies. Compare residential, mobile, and datacenter options, see code examples, and avoid common blocks.","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog\u002Fgeo-targeted-pricing-monitoring-master.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog\u002Fgeo-targeted-pricing-monitoring-card.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog\u002Fgeo-targeted-pricing-monitoring-og.webp","2026-02-26T03:50:37+00:00","2026-07-19T03:13:07+00:00","2026-10-03T08:29:56+00:00",9,34,{"name":47,"slug":48,"bio":49,"avatar_url":14,"website_url":14,"social_links":14},[97],{"id":52,"slug":53,"name":54,"description":14,"meta_title":55,"meta_description":56,"parent_id":14},[99,100,101],{"id":63,"slug":53,"name":64},{"id":66,"slug":67,"name":68},{"id":70,"slug":71,"name":72},{"id":93,"slug":103,"title":104,"excerpt":105,"featured_image_url":106,"featured_image_alt":14,"card_image_url":107,"og_image_url":108,"published_at":109,"updated_at":91,"content_updated_at":110,"reading_time_minutes":111,"is_featured":4,"indexable":39,"views":112,"author":113,"categories":114,"tags":116},"proxies-for-ecommerce-data-scraping","Proxies for E-Commerce Data Scraping: The Complete Guide","Learn how to use residential proxies for e-commerce data scraping. Covers Amazon, Shopify, and Walmart strategies, Python and Node.js code examples, geo-targeted price monitoring, and scaling infrastructure.","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog\u002Fproxies-for-ecommerce-data-scraping-master.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog\u002Fproxies-for-ecommerce-data-scraping-card.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog\u002Fproxies-for-ecommerce-data-scraping-og.webp","2026-01-12T01:55:44+00:00","2026-02-25T22:49:27+00:00",17,197,{"name":47,"slug":48,"bio":49,"avatar_url":14,"website_url":14,"social_links":14},[115],{"id":52,"slug":53,"name":54,"description":14,"meta_title":55,"meta_description":56,"parent_id":14},[117,118,119,120],{"id":63,"slug":53,"name":64},{"id":66,"slug":67,"name":68},{"id":59,"slug":60,"name":61},{"id":80,"slug":121,"name":122},"residential-proxies","Residential Proxies",{"id":124,"slug":125,"title":126,"excerpt":127,"featured_image_url":128,"featured_image_alt":14,"card_image_url":129,"og_image_url":130,"published_at":131,"updated_at":91,"content_updated_at":132,"reading_time_minutes":93,"is_featured":39,"indexable":39,"views":133,"author":134,"categories":135,"tags":137},40,"realtime-price-monitoring-infrastructure","Building Real-Time Price Monitoring Infrastructure","A practical guide to building real-time price monitoring infrastructure with rotating residential proxies, sticky sessions, and scalable scheduling so your price tracking stays reliable as targets harden their anti-bot defenses.","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog\u002Frealtime-price-monitoring-infrastructure-master.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog\u002Frealtime-price-monitoring-infrastructure-card.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog\u002Frealtime-price-monitoring-infrastructure-og.webp","2026-02-13T13:33:22+00:00","2026-10-03T08:29:53+00:00",63,{"name":47,"slug":48,"bio":49,"avatar_url":14,"website_url":14,"social_links":14},[136],{"id":52,"slug":53,"name":54,"description":14,"meta_title":55,"meta_description":56,"parent_id":14},[138,139],{"id":63,"slug":53,"name":64},{"id":66,"slug":67,"name":68},{"id":141,"slug":142,"title":143,"excerpt":144,"featured_image_url":145,"featured_image_alt":14,"card_image_url":146,"og_image_url":147,"published_at":148,"updated_at":91,"content_updated_at":149,"reading_time_minutes":93,"is_featured":39,"indexable":4,"views":150,"author":151,"categories":152,"tags":154},26,"proxies-for-shopify-scraping","How to Scrape Shopify Stores with Proxies: Complete Guide","Learn how to scrape Shopify store data using JSON API endpoints and residential proxies. Complete Python and Node.js code for extracting products, prices, and inventory data.","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog\u002Fproxies-for-shopify-scraping-master.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog\u002Fproxies-for-shopify-scraping-card.webp","https:\u002F\u002Fstatic.proxyhat.com\u002Fstorage\u002Fblog\u002Fproxies-for-shopify-scraping-og.webp","2026-01-30T00:06:37+00:00","2026-10-03T08:21:31+00:00",762,{"name":47,"slug":48,"bio":49,"avatar_url":14,"website_url":14,"social_links":14},[153],{"id":52,"slug":53,"name":54,"description":14,"meta_title":55,"meta_description":56,"parent_id":14},[155,156],{"id":63,"slug":53,"name":64},{"id":59,"slug":60,"name":61},[]]