πŸŽ‰ Premium Proxies Β· 24-Hour Free TrialClaim Now
Guides

How to Scrape Yandex Search Results in 2026

Scrape Yandex search results reliably: lr region codes, Cyrillic queries, SmartCaptcha detection, the official XML API, and working SparkProxy code.

S SparkProxy 1 21 min read
Share
How to Scrape Yandex Search Results in 2026

To scrape Yandex search results, request https://yandex.ru/search/?text=QUERY&lr=213&p=0&noreask=1 through a residential exit in the target country, parse the BEM serp-item markup, treat any response containing /showcaptcha as a block, and move organic-only workloads to the official Yandex Search API.

Most guides that claim to teach this are Google tutorials with the domain swapped, which earns you a captcha page in about forty requests. Yandex runs a different index per country, ranks by a region ID you pass in the URL, rewrites Cyrillic queries before searching, defends itself with its own SmartCaptcha, and publishes a first-party API with a quota model unlike anything Google offers. This guide is the delta, with runnable SparkProxy code and honest notes on where each approach breaks.

Why Yandex is not Google

Five differences change how you write the scraper.

One engine, several indexes. yandex.ru and yandex.com do not return the same ten results for one query: different ranking models, different language priors, different SERP furniture. Turkey has its own front door, yandex.com.tr.

Region is an explicit URL parameter. Google infers locality from your IP plus a gl hint. Yandex takes an integer, lr, from its own geo tree: two requests from one IP with lr=213 and lr=2 return visibly different SERPs.

Pagination is zero-indexed. p=0 is the first page where Google uses start=0. Everyone arriving from Google gets this wrong once, silently scraping page two as page one.

Query rewriting is aggressive. Yandex lemmatises Russian, repairs keyboard-layout mistakes, and transliterates Latin-typed Russian into Cyrillic, so your scraper can record a query you never sent.

The captcha is Yandex's own. SmartCaptcha is a Yandex Cloud product unrelated to reCAPTCHA or hCaptcha, and its challenge page returns HTTP 200, so a naive status check reports success.

If you have read our guide to scraping Google search results, treat this as the companion, not a reskin: the pacing instincts transfer, almost nothing else does.

Pick the domain and index

Decide the index first: it sets the language of the SERP furniture you parse, and how hard the target defends itself.

HostIndex and audienceInterfaceNotes
`yandex.ru`Russian index, Russia and CISRussianDeepest index, strictest anti-bot
`yandex.com`International indexEnglishOwn ranking model, thin Russian
`yandex.com.tr`Turkish indexTurkishOwn ad inventory
`yandex.kz`, `yandex.by`, `yandex.uz`Kazakhstan, Belarus, UzbekistanLocalCore index, preset region

The path is identical everywhere, so a minimum viable SERP URL is https://yandex.com/search/?text=proxy%20server&lr=225&p=0. Country front doors are not separate indexes; they are the core index with a regional default, which is why setting lr explicitly beats picking a hostname and hoping.

Free trial

Scraping at scale? Skip the blocks.

Fast, unblockable datacentre proxies with unlimited bandwidth.

The lr region code

lr is a numeric ID from Yandex's geo tree. It changes organic ranking, which local businesses appear, which ads are eligible, and the currency inside wizard blocks. No other parameter moves the results as much.

`lr`Region
213Moscow
2Saint Petersburg
225Russia (country level)
143Kyiv
187Ukraine
157Minsk
149Belarus
162Almaty
159Kazakhstan
11508Istanbul
983Turkey

Treat that table as a starting point, not gospel: codes for smaller cities move when Yandex reorganises the tree. Resolve them with the Direct API method Dictionaries.get and DictionaryNames: ["GeoRegions"], which returns GeoRegionId, GeoRegionName, GeoRegionType and ParentId for the same tree lr comes from. Cache it, refresh quarterly, key job configs on region names rather than raw integers.

Two behaviours catch people out. Region autodetection still runs: pass lr=213 from a German exit and Yandex sees a Moscow hint from Frankfurt. Ranking usually honours lr, but the mismatch is a bot signal and some local blocks fall back to the IP-derived region, so match exit country to region. And rstr is the negative form: &rstr=-213 returns everything except Moscow.

Geo accuracy is the whole game for rank tracking, and our explainer on geo-targeting in proxies covers the mechanics.

Cyrillic queries and Yandex operators

The query goes in text, percent-encoded as UTF-8, so ΠΊΡƒΠΏΠΈΡ‚ΡŒ прокси becomes %D0%BA%D1%83%D0%BF%D0%B8%D1%82%D1%8C%20%D0%BF%D1%80%D0%BE%D0%BA%D1%81%D0%B8. Let the HTTP library encode it and never concatenate an already-encoded string, the usual source of double-encoded %25D0 garbage.

from urllib.parse import urlencode

def yandex_url(query, lr=213, page=0, host="yandex.ru", noreask=True):
    params = {
        "text": query,
        "lr": lr,
        "p": page,          # zero-indexed: p=0 is the first page
        "lang": "ru",
    }
    if noreask:
        params["noreask"] = 1   # disable "did you mean" rewriting
    return f"https://{host}/search/?{urlencode(params)}"

print(yandex_url("ΠΊΡƒΠΏΠΈΡ‚ΡŒ прокси", lr=213, page=0))
# https://yandex.ru/search/?text=%D0%BA%D1%83%D0%BF%D0%B8%D1%82%D1%8C+...&lr=213&p=0&lang=ru&noreask=1

noreask=1 is the parameter most tutorials omit and the one that most affects data quality. Without it Yandex substitutes what it thinks you meant, and three rewrites fire regularly:

  • Morphological expansion. Russian is heavily inflected and Yandex matches lemmas, so ΠΊΡƒΠΏΠΈΡ‚ΡŒ прокси and ΠΊΡƒΠΏΠ»ΡŽ прокси collapse toward one result set.
  • Keyboard-layout repair. A query typed on the wrong layout, ghjrcb, becomes прокси. Helpful for humans, poison for a keyword tracker.
  • Transliteration. Latin-typed Russian like kupit proksi maps back to Cyrillic, so a transliterated keyword list measures a query you never stored.

With noreask=1 the suggestion banner still appears, but the results belong to the query you sent. Where you cannot use it, read the misspell indicator and flag those rows.

Yandex also ships a richer operator language than Google, working in HTML search and the XML API alike.

OperatorMeaning
`"точная Ρ„Ρ€Π°Π·Π°"`Exact phrase
`!слово`Exact word form only, no morphology
`!!слово`All morphological forms of this lemma
`+слово`Word must be present
`-слово`Exclude the word
`site:sparkproxy.io`Restrict to a site including subdomains
`host:sparkproxy.io`Restrict to one exact host
`url:sparkproxy.io/blog/*`Restrict to a URL pattern
`mime:pdf`, `lang:ru`Restrict by file type or language
`date:20260101..20260818`Date range on the document
`прокси /3 сСрвСр`Words within three positions

The ! operator is the practical one: if a tracked brand name collides with a common Russian noun, !Π±Ρ€Π΅Π½Π΄ stops Yandex expanding into every declension and gives you a stable time series.

Fetch the SERP with SparkProxy

The SparkProxy Scraping API handles the proxy pool, browser, and fingerprint, so your code makes one HTTP call. Base URL is https://scrape.sparkproxy.io/api/v1 with an X-API-Key header, and every parameter is in the Scraping API docs. Start with a residential fetch of a Moscow SERP:

curl -G "https://scrape.sparkproxy.io/api/v1" \
  -H "X-API-Key: sk-your-key-here" \
  --data-urlencode "url=https://yandex.ru/search/?text=ΠΊΡƒΠΏΠΈΡ‚ΡŒ+прокси&lr=213&p=0&noreask=1" \
  --data-urlencode "render_js=true" \
  --data-urlencode "premium_proxy=true" \
  --data-urlencode "country_code=RU" \
  --data-urlencode "stealth=true"

--data-urlencode matters: it encodes the Cyrillic and the nested ? and & of the target URL so the API receives one intact parameter. Hand-building that string is the most common reason a first Yandex call returns the homepage instead of a SERP.

The production version is a POST with a wait condition, so you never capture the page before the organic list paints:

import requests

API = "https://scrape.sparkproxy.io/api/v1"
KEY = "sk-your-key-here"

def fetch_yandex(query, lr=213, page=0, host="yandex.ru", country="RU"):
    target = yandex_url(query, lr=lr, page=page, host=host)
    r = requests.post(
        API,
        headers={"X-API-Key": KEY, "Content-Type": "application/json"},
        json={
            "url": target,
            "render_js": True,
            "premium_proxy": True,     # residential exit, not a datacenter ASN
            "country_code": country,   # keep the exit country aligned with lr
            "stealth": True,           # extra anti-bot layers, requires render_js
            "wait_for": "#search-result, .serp-list",
            "device": "desktop",
            "format": "html",
            "tag": f"yandex:{lr}:{page}",
        },
        timeout=120,
    )
    r.raise_for_status()
    return r.text

Each parameter answers a specific Yandex defence, and each costs credits.

Yandex signalSparkProxy parameterWhy it helps
Cloud ASN reputation`premium_proxy: true`Residential exit, not a datacenter range
Region mismatch against `lr``country_code: "RU"` / `"TR"`Exit country matches the requested region
Wizard blocks injected by JavaScript`render_js: true`Some blocks are absent from the raw HTML
Antirobot fingerprint checks`stealth: true`TLS and headers consistent with real Chrome
Mobile layout differs`device: "mobile"`Mobile returns a different block order
Late-painting result list`wait_for: "#search-result"`Avoids capturing an empty shell

Credit costs stack: a residential request with JavaScript is 25 credits, with country_code and stealth adding 5 each. Where the SERP parses without a browser, drop render_js to false and pay 10. Test both on your own keywords; the answer differs by region and query type.

The Turkish index, from Node:

const axios = require('axios');

const target = 'https://yandex.com.tr/search/?text=' +
  encodeURIComponent('vekil sunucu') + '&lr=11508&p=0';

axios.get('https://scrape.sparkproxy.io/api/v1', {
  headers: { 'X-API-Key': 'sk-your-key-here' },
  params: {
    url: target,
    render_js: 'true',
    premium_proxy: 'true',
    country_code: 'TR',
    stealth: 'true'
  }
})
.then(res => console.log(res.data.length, 'bytes'))
.catch(err => console.error(err.response?.status, err.response?.data));

Pagination is a loop over p, and this is where the zero-index bites:

import random, time

def collect(query, lr=213, pages=3, host="yandex.ru"):
    rows = []
    for p in range(pages):              # p = 0, 1, 2 -> pages 1, 2, 3
        html = fetch_yandex(query, lr=lr, page=p, host=host)
        batch = parse_organic(html)
        if not batch:
            break                       # empty page means blocked or exhausted
        for r in batch:
            r["position"] += p * 10     # absolute position across pages
        rows.extend(batch)
        time.sleep(random.uniform(4, 12))   # jitter, never a constant sleep
    return rows

Parse organic results and wizard blocks

Here is a genuine advantage over Google. Yandex invented BEM and its SERP markup still follows it, so class names read like serp-item, organic__url-text, OrganicTitle-LinkText. Semantic, and they rotate far less often than Google's obfuscated hashes. They do rotate, so anchor on structure and attributes first, classes second.

from bs4 import BeautifulSoup

def parse_organic(html):
    soup = BeautifulSoup(html, "lxml")
    rows = []
    for i, item in enumerate(soup.select("li.serp-item"), start=1):
        # Direct ads carry a "Π Π΅ΠΊΠ»Π°ΠΌΠ°" label; skip them for organic-only tracking
        if "Π Π΅ΠΊΠ»Π°ΠΌΠ°" in item.get_text():
            continue
        link = item.select_one("a.OrganicTitle-Link, a.organic__url, a.Link")
        title = item.select_one(".OrganicTitle-LinkText, .organic__url-text")
        if not link or not link.get("href"):
            continue
        rows.append({
            "position": i,
            "url": link["href"],
            "title": title.get_text(strip=True) if title else "",
            "wizard": item.get("data-fast-name"),   # present on wizard blocks
            "snippet": (item.select_one(".OrganicTextContentSpan,"
                                        " .organic__content-wrapper")
                        or item).get_text(" ", strip=True)[:300],
        })
    return rows

The data-fast-name attribute is the useful hook: set on a serp-item, it names the wizard type, classifying a block without pattern-matching visible text in three languages.

Yandex calls its feature blocks "ΠΊΠΎΠ»Π΄ΡƒΠ½Ρ‰ΠΈΠΊΠΈ", wizards. They are not the set Google ships, so a Google parser mislabels most of them.

BlockRussian nameWhat it isParsing hook
Organic resultΠžΡ€Π³Π°Π½ΠΈΡ‡Π΅ΡΠΊΠ°Ρ Π²Ρ‹Π΄Π°Ρ‡Π°Standard blue link`li.serp-item` with `.organic`
Direct adsЯндСкс Π”ΠΈΡ€Π΅ΠΊΡ‚Paid, top and bottom"Π Π΅ΠΊΠ»Π°ΠΌΠ°" label inside the item
Quick answerБыстрый ΠΎΡ‚Π²Π΅Ρ‚One-line factual answerAbove the first `serp-item`
Organization cardЯндСкс БизнСсLocal pack from Yandex MapsLinks to `yandex.ru/maps`
Market carouselЯндСкс ΠœΠ°Ρ€ΠΊΠ΅Ρ‚Products with pricesLinks to `market.yandex.ru`
Turbo pageΠ’ΡƒΡ€Π±ΠΎ-страницаYandex-hosted copy, the AMP equivalentURL has `/turbo?text=`
Dzen articlesΠ”Π·Π΅Π½Promoted feed contentLinks to `dzen.ru`
Video, Images, relatedΠ’ΠΈΠ΄Π΅ΠΎ, ΠšΠ°Ρ€Ρ‚ΠΈΠ½ΠΊΠΈCarousels and suggestions`data-fast-name` on the block

Turbo pages deserve a warning. A Turbo result points at a Yandex-hosted copy rather than the publisher's URL, so a naive tracker records yandex.ru/turbo?text=... as the ranking domain and the client's real domain looks like it vanished. The destination sits in that URL's query string, so unwrap it before storing the row. Nobody porting a Google parser catches this first time.

To skip parsing, extract_rules returns structured JSON instead of HTML:

payload = {
    "url": yandex_url("прокси для парсинга", lr=213),
    "render_js": True,
    "premium_proxy": True,
    "country_code": "RU",
    "stealth": True,
    "extract_rules": {
        "titles": {"selector": "li.serp-item .OrganicTitle-LinkText", "type": "list"},
        "links": {"selector": "li.serp-item a.OrganicTitle-Link", "type": "list",
                  "output": "@href"},
    },
}

Keep selectors in config, not code. Yandex ships markup changes without notice, and editing a JSON file beats cutting a release.

SmartCaptcha: detect, do not fight

SmartCaptcha launched as a Yandex Cloud service in 2022 and also guards Yandex's own properties. It is a checkbox-first challenge that escalates to image and slider tasks on a behavioural score. Not reCAPTCHA, not hCaptcha, and the token flow differs, so solvers built for Google's challenge typically fail against it.

The detection rule that matters: the challenge returns HTTP 200, so check the body and the final URL, never the status code.

class BlockedError(Exception):
    pass

CAPTCHA_MARKERS = (
    "/showcaptcha",          # the challenge path Yandex redirects to
    "SmartCaptcha",
    "captcha__image",
    "ΠŸΠΎΠ΄Ρ‚Π²Π΅Ρ€Π΄ΠΈΡ‚Π΅, Ρ‡Ρ‚ΠΎ запросы отправляли Π²Ρ‹",   # "confirm you sent these requests"
)

def is_blocked(html: str) -> bool:
    return any(m in html for m in CAPTCHA_MARKERS)

def fetch_checked(query, **kw):
    html = fetch_yandex(query, **kw)
    if is_blocked(html):
        raise BlockedError("SmartCaptcha served")
    if "serp-item" not in html:
        raise BlockedError("no results in response")   # soft block or empty shell
    return html

The /showcaptcha path is the strongest single signal. Yandex redirects to https://yandex.ru/showcaptcha?cc=1&retpath=..., and retpath holds your original query, so log which keyword tripped it.

When it fires:

  1. Stop that worker. Retrying the same query on the same session returns the challenge again and burns credits.
  2. Rotate the exit, then back off with jitter. Take a fresh residential IP and requeue the keyword at roughly 60 seconds, then 300, then 900, each delay multiplied by a random 0.5 to 1.5 so parallel workers do not retry in lockstep. Park it after the third failure instead of grinding. The general shape is in our post on retry and backoff strategies.
  3. Treat a captcha rate above roughly 5 percent as a configuration problem, not a traffic problem. Fingerprint, pacing, or exit geography is wrong, and concurrency makes it worse.
  4. Fall back to the XML API for keywords that keep failing. It is sanctioned and never serves a captcha.

Solving SmartCaptcha at scale is expensive and brittle. Better pacing plus the official API for the hard tail costs less.

The sanctioned route: Yandex XML API

Unlike Google, Yandex publishes a first-party interface returning real SERP documents, not a cut-down custom-search subset. If the quota fits your volume, reach for it before writing a selector.

Two generations exist. The classic XML endpoint is https://yandex.com/search/xml (or the .ru equivalent), authenticated with a Yandex Cloud folderid plus an apikey. Search API v2 lives at https://searchapi.api.cloud.yandex.net/v2/web/search, speaks REST and gRPC, and adds a deferred mode that buys a larger hourly allowance in exchange for waiting. Both share one documentation set.

curl -G "https://yandex.com/search/xml" \
  --data-urlencode "folderid=b1gxxxxxxxxxxxxxxxxx" \
  --data-urlencode "apikey=AQVNxxxxxxxxxxxxxxxxxxxx" \
  --data-urlencode "query=ΠΊΡƒΠΏΠΈΡ‚ΡŒ прокси" \
  --data-urlencode "lr=213" \
  --data-urlencode "l10n=ru" \
  --data-urlencode "sortby=rlv" \
  --data-urlencode "filter=none" \
  --data-urlencode "groupby=attr=d.mode=deep.groups-on-page=10.docs-in-group=1" \
  --data-urlencode "page=0"

In groupby, groups-on-page sets results per page and docs-in-group pages per domain. Parsing is plain XML, and the error element comes first.

import requests, xml.etree.ElementTree as ET

def yandex_xml(query, folder_id, api_key, lr=213, page=0):
    r = requests.get("https://yandex.com/search/xml", params={
        "folderid": folder_id, "apikey": api_key,
        "query": query, "lr": lr, "l10n": "ru",
        "sortby": "rlv", "filter": "none", "page": page,
        "groupby": "attr=d.mode=deep.groups-on-page=10.docs-in-group=1",
    }, timeout=60)
    root = ET.fromstring(r.content)

    err = root.find(".//error")
    if err is not None:
        raise RuntimeError(f"Yandex XML error {err.get('code')}: {err.text}")

    out = []
    for doc in root.findall(".//doc"):
        title_el = doc.find("title")
        passage = doc.find(".//passage")
        out.append({
            "url": doc.findtext("url"),
            "domain": doc.findtext("domain"),
            "title": "".join(title_el.itertext()) if title_el is not None else "",
            "snippet": "".join(passage.itertext()) if passage is not None else "",
        })
    return out

Note the itertext() calls. Yandex wraps matched terms in tags inside title and passage, so .text truncates at the first highlighted word. That is behind most "why are my Yandex titles cut off" bug reports.

The documented error codes you will meet:

CodeMeaningWhat to do
15Nothing found for this queryRecord an empty SERP, do not retry
32Request limit exceededBack off, you are over the hourly slice
33Requesting IP is not allow-listedFix the allow list, stop proxying this call
37Invalid API keyCredentials problem, fail loudly
55Daily request limit exceededStop until the quota window resets

Two quota behaviours decide the fit.

The quota unit is the hour. Published quotas and limits run to 10,000 synchronous requests per hour at 10 per second, or 35,000 per hour deferred, where a request takes at least five minutes and the result is held for 12 hours. The legacy XML console said the same thing differently: a daily allowance spread across 24 hourly slots. Either way a burst at 09:00 exhausts that hour, and you get a hard error rather than gentle throttling. Size workers to the hourly slice, not the daily total.

Depth is capped too. The same table caps results returned at 250 per request, and HTML pagination dries up around 1,000 documents, so deep-tail rank research is unavailable by either route.

Error 33 exists because the interface is IP-bound, the constraint that surprises proxy users most. You register the addresses allowed to call it, so XML traffic through a rotating residential pool is rejected by design. Run XML calls from a few fixed egress addresses, keep the proxy pool for HTML, and never mix both in one worker. That mix is the most common architecture mistake in Yandex pipelines.

HTML SERP scrapingYandex XML / Search API v2
Sanctioned by YandexNoYes
Ads and wizard blocksFull, as renderedOrganic documents only
Captcha riskRealNone
Cost driverCredits per requestQuota per 1,000 requests
Rate modelYours to controlFixed hourly allocation
IP handlingRotating pool requiredFixed allow-listed IPs
DepthLimited by block rateUp to 250 results per request

The pragmatic architecture uses both: XML for organic ranking series, HTML for the ad blocks, Market carousels, and local packs the API omits. Our SERP scraping proxy explainer covers that split in the general case.

Datacenter IPs, pacing, block rates

Be honest about this part: Yandex is harder on datacenter ranges than Google. It runs its own cloud business, classifies ASNs in detail, and its Antirobot system treats cloud egress as suspicious. In practical testing, plain datacenter IPs hitting yandex.ru reach /showcaptcha within a few dozen queries, sometimes on the first request from a subnet with history, while residential exits inside the target country sustain roughly 100 to 200 queries per IP per day at human-like pacing. Those are practitioner ranges rather than a published study, and they move with subnet reputation.

The rules that follow:

  • Residential exits in-country for yandex.ru. A Russian query answered from a Moscow residential IP is unremarkable. From a Virginia cloud range it is not. The trade-offs sit in residential vs datacenter proxies.
  • Jitter, never a constant sleep. Uniform 5-second gaps are a stronger bot tell than a higher raw rate. Randomise between 4 and 12 seconds.
  • Keep sessions short. Yandex sets a yandexuid identifier and a yp preferences cookie carrying region state, so long-lived cookie jars accumulate a behavioural profile. Rotate identity with the IP.
  • Do not parallelise into a captcha. When block rate climbs, concurrency makes it climb faster. Cut workers first, then investigate.
  • Cache hard. Most rank-tracking questions need daily granularity.

The .tr and .com properties are noticeably more tolerant than .ru, so if you only need the international index, target yandex.com and save residential budget for the queries that need a Russian exit. The wider detection surface is covered in how to avoid getting your proxy blocked.

Frequently asked questions

FAQ

Yes. Yandex XML and the newer Search API v2 return real SERP documents, authenticated with a Yandex Cloud folder ID and API key. It is the sanctioned route for organic results, but it excludes ads and wizard blocks, returns up to 250 results per request, and caps synchronous use at 10,000 requests per hour.

lr is a numeric region ID from Yandex's geo tree, such as 213 for Moscow or 11508 for Istanbul. It reorders organic results and selects which local blocks appear, so a scraping job that ignores lr collects an undefined region.

Usually not. SmartCaptcha is a separate Yandex Cloud product with its own challenge types and token flow, so services built around Google's reCAPTCHA generally cannot return a valid token. Rotating the exit IP, slowing down, or falling back to the XML API is cheaper than solving it.

Poorly on yandex.ru. Yandex classifies cloud ASNs aggressively and often serves /showcaptcha within a few dozen requests from a datacenter range. Residential exits in the target country hold up far better, and yandex.com is more tolerant than the Russian index.

Fewer than you would like. The Search API limits table caps one request at 250 results, and HTML pagination dries up near 1,000 documents. Pages are zero-indexed, so p=0 is the first page. Plan for shallow requests, not one deep pull.

No. yandex.com serves an international, English-first index, yandex.ru the Russian-language index, and yandex.com.tr Turkey. Rankings, ad inventory, and SERP blocks differ, so pick the host for the market you measure, then set lr on top.

Special Discount Β· 20% off

Get 20% off your first month

Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.

Save up to 15% more on quarterly, half-yearly and yearly plans

Claim Discount

About the Author

The SparkProxy Technical Team builds and operates the infrastructure behind SparkProxy's datacenter proxies, residential proxies, and Scraping API. We run large-scale collection against search engines, marketplaces, and anti-bot systems daily, and publish what the logs show, including where an approach stops working. Questions about a Yandex workload, region set, or quota model go to support@sparkproxy.io.

Keep reading

Related articles

How to Scrape Vinted Listings

How to Scrape Vinted Listings

Scrape Vinted listings across vinted.fr, .de and .co.uk: the internal JSON API, cookie bootstrapping, per-market catalogue IDs, and GDPR-safe resale analytics.

SparkProxyΒ·Guides
How to Scrape TikTok Public Data With Proxies

How to Scrape TikTok Public Data With Proxies

Scrape TikTok public data with proxies: read the hydration JSON blob, use the Research API and oEmbed, detect fake 200s, and cut credits per good page.

SparkProxyΒ·Guides