# Yandex SERP Scraper: Rank Tracking, Ads and Regions

> Yandex SERP Scraper is an Apify actor (API) by CheapAPI Labs. Yandex search results for rank tracking with ads and AI answers in about 640 Russian and 80 Turkish regions. It costs $6.00 per 1,000 result pages on the Apify Free plan and $4.00 on Gold, billed through your Apify account with no API key. Compared with the most used Yandex actor on Apify Store (about 2,200 users): 1,000 search terms with 10 results each costs $6 on Free and $4 on Gold here and $130 and $74 there (Apify Store listing prices, checked October 2026).

- Page: https://cheapapilabs.com/actors/yandex-serp-scraper/
- Run on Apify: https://apify.com/cheapapi/yandex-serp-scraper
- Publisher: CheapAPI Labs (Apify username cheapapi)
- Main price: $6.00 per 1,000 result pages on Free, $4.00 on Gold
- Our prices updated: 2026-10-30

## Prices per event (USD)

| Event | Free | Bronze | Silver | Gold |
|---|---:|---:|---:|---:|
| Results page (20 organic results) | $0.006 | $0.0055 | $0.005 | $0.004 |
| Full results page (10 organic results with ads and result blocks) | $0.008 | $0.007 | $0.006 | $0.005 |
| Search without results | $0.0012 | $0.0012 | $0.0012 | $0.0012 |
| AI answer | $0.06 | $0.06 | $0.06 | $0.06 |

Platinum and Diamond pay the Gold price. Apify platform usage: billed separately by Apify.

## Call it from code

```bash
curl -X POST "https://api.apify.com/v2/acts/cheapapi~yandex-serp-scraper/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" \
  -d '{"queries":["купить ноутбук","погода москва","купить диван | Kazan"],"searchDomain":"yandex.ru","region":"Moscow","maxResults":20}'
```

## Example output row

```json
{
  "id": "da771f1da55fe13c",
  "runId": "Hs4ujkV031SOo3a9H",
  "searchQuery": "python",
  "searchDomain": "yandex.ru",
  "region": "Russia",
  "regionId": 225,
  "language": "auto",
  "device": "desktop",
  "scrapedAt": "2026-10-01T19:36:02.717Z",
  "type": "organic",
  "position": 1,
  "title": "The official home of the Python Programming Language",
  "url": "https://www.python.org/",
  "domain": "python.org",
  "snippet": "The core of extensible programming is defining functions. Python allows mandatory and optional arguments, keyword arguments, and even arbitrary argument lists. More about defining functions in Python 3.",
  "headline": null,
  "passages": [
    "The core of extensible programming is defining functions. Python allows mandatory and optional arguments, keyword arguments, and even arbitrary argument lists. More about defining functions in Python 3."
  ],
  "modifiedDate": "2006-10-29",
  "documentLanguage": "en",
  "mimeType": "text/html",
  "savedCopyUrl": "https://yandexwebcache.net/yandbtm?fmode=inject&",
  "isTracked": false,
  "totalResults": 244710,
  "correctedQuery": null
}
```

## Fields

**Result type "Organic results"** (default), one row per organic result:

| Field | Type | Example |
|---|---|---|
| `searchQuery` | string | `купить ноутбук` |
| `searchDomain` | string | `yandex.ru` |
| `type` | string | `organic` (`ai-answer` and `related-searches` for the extra rows) |
| `region`, `regionId` | string, number | `Moscow`, `213` |
| `position` | number | `1` (Yandex's exact position, 1 to 250) |
| `title` | string | `Ноутбуки для работы купить по низкой цене…` |
| `url` | string | `https://www.mvideo.ru/noutbuki-…` |
| `domain` | string | `mvideo.ru` |
| `snippet` | string | snippet passages joined (or the page description) |
| `passages` | array | the snippet's separate text passages |
| `headline` | string / null | page description shown by Yandex |
| `modifiedDate` | string / null | `2018-05-21`, page date reported by Yandex (not always the last edit) |
| `documentLanguage` | string / null | `ru` |
| `mimeType` | string / null | `text/html`, `application/pdf` … |
| `savedCopyUrl` | string / null | Yandex's cached copy of the page |
| `isTracked` | boolean | `true` when the domain is in **Track domains** |
| `totalResults` | number / null | `37812154` (Yandex's estimate) |
| `correctedQuery` | string / null | the spelling Yandex searched instead |
| `domainGroupPosition` | number | only with **Results per website** 2 to 3 |
| `previousPosition`, `positionChange`, `isNew`, `previousRunAt` | number / null, number / null, boolean, string | with **Compare with the previous run**: `7`, `3` (moved up 3 places), `false` |
| `id` | string | `e714ca1d5650c2f4`, same search, position and URL give the same id (not unique per run) |
| `runId`, `scrapedAt`, `language`, `device` | string | the run that collected the row, collection time, interface language setting (`auto` = domain default; the page language is `documentLanguage`) and device (`desktop` in Organic results mode; the Device option applies to the full results page only) |

**Result type "Full results page"**, one row per block of the results page, in order:

| Field | Type | Example |
|---|---|---|
| `type` | string | `organic`, `ad` or `block` |
| `position` | number | place on the page among all blocks (1, 2, 3 …) |
| `organicPosition` / `adPosition` | number / null | position among organic results / among ads |
| `title`, `url`, `domain`, `snippet` | | as above (`url` is `null` for ads) |
| `breadcrumbs` | array | `["Ноутбуки, планшеты, компьютеры", "Ноутбуки"]` |
| `rating` | number / null | `4.6`, shop rating shown next to the result |
| `sitelinks` | array | links under a result: `[{"title": "Менеджер", "url": "https://spb.hh.ru/vacancies/menedzher"}]` |
| `blockType`, `blockSubtype`, `blockText` | | `ai-overview` (Alice AI quick answer, full text), `knowledge-panel`, `images`, `videos`, `products`, `ads-block` … |
| `blockLinks` | array | links of a block, the AI overview's sources, a knowledge panel's links, image/video items: `[{"title": "ru.wikipedia.org", "url": "https://ru.wikipedia.org/wiki/…"}]` |
| `relatedSearches` | array | on a `related-searches` row: `["эйфелева башня в париже", "эйфелева башня высота", …]` |
| `page` | number | results page the block came from |
| `htmlUrl` | string | link to the saved page HTML (when **Save the page HTML** is on) |

`totalResults`, `correctedQuery`, `modifiedDate`, `passages` and `domainGroupPosition` exist only in **Organic results** mode (the full results page does not contain them).

**AI answer** (option), one extra row per search term with `type: "ai-answer"`:

| Field | Type | Example |
|---|---|---|
| `answerText` | string / null | `Выбор ноутбука для учёбы зависит от того, что именно предстоит делать. [1] …` (Markdown; `[1]` = first source) |
| `answerSources` | array | `[{"url": "https://hi-tech.mail.ru/…", "title": "25 лучших ноутбуков для учебы 2026…", "used": true}]` |
| `answerRejected` | boolean | `true` when Yandex declined to answer |

The row also carries the common fields (`id`, `runId`, `searchQuery`, `searchDomain`, `scrapedAt`). `region`, `regionId`, `device` and `position` are `null`, because the answer is not region specific and is requested once per search term even with **Several regions**.

Example AI answer row (real output, shortened):

```json
{
    "id": "5a9d3af82cdb697c",
    "runId": "Hs4ujkV031SOo3a9H",
    "searchQuery": "python",
    "searchDomain": "yandex.ru",
    "region": null,
    "regionId": null,
    "language": "auto",
    "device": null,
    "scrapedAt": "2026-10-01T19:36:02.717Z",
    "type": "ai-answer",
    "position": null,
    "answerText": "**Python** — **высокоуровневый язык программирования общего назначения** с динамической типизацией и автоматическим управлением памятью. [1][5] Создан нидерландским программистом Гвидо ван Россумом в 1991 году. [5] \n\n**Н …",
    "answerSources": [
        {
            "url": "https://ru.wikipedia.org/wiki/Python",
            "title": "Python — Википедия",
            "used": true
        },
        {
            "url": "https://skillbox.ru/media/code/dlya_chego_nuzhen_python/",
            "title": "Python: что это за язык, для чего нужен и где используется / Skillbox Media",
            "used": true
        }
    ],
    "answerRejected": false
}
```

With **Compare with the previous run**, organic results look like this (illustrative values; the fields are real):

```json
{ "searchQuery": "купить ноутбук", "region": "Moscow", "type": "organic", "position": 4, "url": "https://www.citilink.ru/catalog/noutbuki/", "domain": "citilink.ru", "previousPosition": 7, "positionChange": 3, "isNew": false, "previousRunAt": "2026-10-01T16:16:08.854Z" }
```

and a "One row per search term" row (shortened, illustrative values):

```json
{
    "searchQuery": "купить ноутбук", "region": "Moscow", "organicCount": 20, "previousRunAt": "2026-10-01T16:16:08.854Z",
    "trackedDomains": [
        { "domain": "citilink.ru", "position": 4, "url": "https://www.citilink.ru/catalog/noutbuki/", "previousPosition": 7, "positionChange": 3 },
        { "domain": "dns-shop.ru", "position": 6, "url": "https://www.dns-shop.ru/catalog/17a892f816404e77/noutbuki/", "previousPosition": 6, "positionChange": 0 }
    ]
}
```

With **Only results that moved at least** N places, a daily run stores only the results whose position changed by N or more, plus new ones.

With **Output format "One row per search term"**, one row holds all results of a search term: `organicResults`, `ads`, `blocks`, `aiAnswer`, `organicCount`, `adsCount`, `totalResults`, `totalResultsText`, `correctedQuery`, `trackedDomains` (each tracked domain with its `position` or `null`, plus `previousPosition` and `positionChange` when comparing with the previous run), `noNewResults` (true when “Only new results” found nothing new for the term; the row then has `organicCount` 0) and `htmlUrls` (links to the saved page HTML with “Save the page HTML”).

**Related searches** come as one extra row per search term with `type: "related-searches"` and `relatedSearches` (full results page; in "One row per search term" output they are the `relatedSearches` field).

**Run summary**, the `RUN_SUMMARY` record of the run's key value store (also linked in the Output tab):
- **Counts:** `searchTermsRequested`, `searchTermsCompleted`, `searchTermsWithoutResults`, `searchTermsWithoutNewResults`, `searchTermsFailed`, `searchTermsPartial` (a later results page failed: only the delivered part is charged), `searchTermsSkippedByLimit`, `searchTermsInvalid`, `searchTermsDuplicate`, `pagesCharged`, `rowsDelivered` (result rows stored, not counting AI answer and related searches rows; 1 per term with One row per search term), `organicResultsChecked`, `organicResultsDelivered` (stored, after filters), `adsDelivered`, `aiOverviewsFound`, `relatedSearchesFound`, `aiAnswersDelivered`, `trackedDomainHits`, `backupRequests`, `historyReadFailures`, `historyWriteFailures`.
- **State:** `searchDomain`, `region`, `resultType`, `stoppedAtSpendingLimit`, `runError` (only when the run stopped early), `finishedAt`, `problems` (the first 100 search terms that had a problem, with the reason in plain words) and `termsWithoutNewResults` (the first 100 terms with nothing new in **Only new results**, kept apart from real problems).

Example `RUN_SUMMARY` excerpt (shape of a real run with a no results term and a declined AI answer):

```json
{
    "searchTermsRequested": 3, "searchTermsCompleted": 2, "searchTermsWithoutResults": 1, "searchTermsFailed": 0,
    "pagesCharged": 2, "organicResultsDelivered": 20, "aiAnswersDelivered": 1, "stoppedAtSpendingLimit": false,
    "problems": [
        { "searchQuery": "zxqv wlkj", "reason": "Yandex returned no results for this search term (charged the no-results fee, $0.0012)." },
        { "searchQuery": "declined", "reason": "Yandex declined to write an AI answer for this search term (the AI answer request is still charged)." }
    ]
}
```

**Dataset views** (Output tab, or `…/datasets/<id>/items?view=<name>` in the API): `overview` (Results, every row type, including ads and blocks of the full results page), `rankTracking` (positions, changes and tracked flags), `searches` (one row per search term), `ads` (full page: ads and blocks), `aiAnswers` and `changes` (position changes and new results, with Compare with the previous run).

## Compared with alternatives

Typical run: **1,000 search terms × 10 organic results** (10,000 result rows; AI answers not included). Actor fees of public Apify Store listings (Free plan / Gold plan), checked October 2026.

| Option | 1,000 search terms × 10 results | Search terms per run | Notes |
|---|---|---|---|
| Most used Yandex Store Actor (~2,200 users) | $130 / $74 | 1 (setup fee $0.08 / $0.05 on every run) | $0.05 / $0.024 per results page |
| Second most used (~270 users) | $70 / $40 | 1 | $0.007 / $0.004 per result |
| Same developer's per result listing (~85 users) | $130 / $74 | 1 | same fees as the first row |
| Per item Actor (~60 users) | $50 / $10 | many | $0.005 / $0.001 per result row + $0.005 per run |
| Flat per result Actor (~40 users) | $34 / $34 | many | $0.0034 per result |
| Per result Actor (~23 users) | $40 / $32 | many | $0.004 / $0.0032 per result |
| Budget per result Actor (~13 users) | $20 / $14 | many | $0.002 / $0.0014 per result + the same per run |
| Per call Actor (~2 users) | **$2 / $1.60** | many | $0.002 / $0.0016 per results page ("standard call") |
| **This Actor** | **$6 / $4** | **many (up to 10,000)** | no start fee |

Plainly: against the two most used Yandex Actors we are **95% / 95% cheaper** (Free / Gold) than the first and **91% / 90%** than the second, and cheaper than every listed alternative except one very new per call listing (~2 users), which costs about a third (Free) to 40% (Gold) of our price for plain results pages; you get regions, exact positions up to 250, the full page with ads, rank changes and AI answers here. Small per row or per run fees of other Actors ($0.00001 per row up to $0.005 per run) are left out of the table, negligible when 1,000 terms run in one run. Apify platform usage is billed separately for this Actor (see Pricing); the table compares Actor fees only. Competitor prices are a snapshot from their public listings (October 2026) and may change. Our price stays the same with **20 results per term**, while per result Actors double (the second one: $140 / $80).

**Per plan, 1,000 search terms × 10 results (most used Actor, one run per search term, vs. this Actor).** Apify lets us set Free, Bronze, Silver and Gold prices; Platinum and Diamond pay our Gold price.

| Apify plan | Most used Actor | This Actor | Difference |
|---|---|---|---|
| Free | $130 | $6.00 | 95% cheaper |
| Bronze | $105 | $5.50 | 95% cheaper |
| Silver | $85 | $5.00 | 94% cheaper |
| Gold, Platinum, Diamond | $74 | $4.00 | 95% cheaper |

## Not included

- **Search volume / Wordstat keyword frequencies, keyword ideas**, not provided.
- **Yandex Maps places and reviews, Yandex Market products, Yandex Images and News verticals**, this Actor covers Yandex web search.
- **Ad landing URLs**, ads (and their sitelinks) link through Yandex's ad redirect, so ads come with the advertiser's displayed domain, title, text, rating and sitelink titles, but `url` is `null`.
- **Content of the ranking pages**, only what the results page shows.
- **Other Yandex domains and interface languages**, the six Yandex searches in this Actor (yandex.ru, .com.tr, .com, .kz, .by, .uz) and six interface languages (plus automatic) are what Yandex offers for automated search; some scrapers list more domains or languages.

## Limits

- Results are not personalized (no Yandex account or search history), so positions can differ slightly from a signed in browser.
- Positions above 100 come from separate result pages, which Yandex may reorder slightly between requests.
- AI answers are not region specific, are written in Russian, Kazakh or Uzbek, and Yandex sometimes declines to answer (the request is still charged).
- The full results page reflects Yandex's layout; if Yandex redesigns the page, ads and blocks may be classified less precisely until we update the Actor (organic results mode is not affected).
- The AI overview (Alice AI quick answer), knowledge panel and related searches are returned when Yandex includes them in the results page it serves; Yandex shows them only for some search terms, and some are added later by the browser and then not included.
- `modifiedDate` is the date Yandex reports for the page, which is often older than the last edit.
- Region selection works for yandex.ru and yandex.com.tr; for other domains use **Location by IP address**.
- Now and then Yandex answers a full results page request with a verification page instead of results. The Actor sends it once more; if that fails too, the search term is reported in `RUN_SUMMARY` and charged only the $0.0012 no results fee.
- Raising **Results per search term** later: the added positions get `isNew: null` (unknown) in the first comparison, because the previous run did not look that deep. To start the history from scratch, delete the history key value store in your account (Storage → Key value stores; default name `yandex-serp-rank-history`, or your **History store name**). It keeps one small record per search term and settings; for **Only new results** it remembers at most the latest 10,000 URLs per term, so older URLs beyond that could be delivered again as new.
- **Compare with the previous run** compares runs with the same search term and the same settings (domain, region, language, device, period, sort, safe search, typo fixing, results per website, location IP, result type).
- A region outside the domain's country (e.g. Moscow on yandex.com.tr) is allowed, it shows that domain as seen from the region, and the log says so.

## FAQ

**Is it legal, and does the output contain personal data?**
The Actor collects publicly visible search results: titles, URLs and snippets of web pages (which may mention people, e.g. on news or profile pages). No account, cookie or user data is collected, and nothing about you is sent to Yandex except your search terms and options. Make sure your use of the data complies with the laws of your country and the rights of the content owners.

**How fresh is the data, and is it the same as in my browser?**
Every search is made live when you run the Actor; nothing is served from an old cache. You get the not personalized results Yandex shows to a signed out user in the chosen region and device; your own browser can differ slightly because of your search history, account and exact location.

**How fast and reliable is it?**
Each search term usually takes 2 to 10 seconds, and many run in parallel: 1,000 search terms take about 6 minutes (measured: 136 terms in 51 seconds). In our live load tests 276 of 276 search terms were completed without a failure; in a separate timing test of 60 searches the median was 3 seconds and 2 searches waited about 95 seconds in Yandex's queue, the Actor now sends one backup request after 15 seconds for such cases (counted in `RUN_SUMMARY.backupRequests`).

**Why did I get fewer results than I asked for?**
Yandex shows at most 250 results per search term, and for rare terms far fewer. For deep results (above 100), Yandex can reorder results between its result pages; a URL that appears twice is delivered once, so a few positions may be missing. You are charged only for delivered results.

**Why was I charged for a search with no results, or for a declined AI answer?**
Yandex processed the search but found nothing (or it could not be completed after processing), which costs a small no results fee of $0.0012. Likewise, when Yandex declines to write an AI answer, it still bills that request, so the $0.06 AI answer fee applies (`answerRejected: true`). Search terms rejected before processing (e.g. longer than 400 characters) are free. If a later results page of a term fails, the delivered part is kept and only those pages are charged (`Only part of the results could be collected` in `problems`).

**Which region should I use, and can I use search operators?**
Rankings on yandex.ru depend strongly on the city: use the city your customers search from ("Moscow", "Saint Petersburg", "Novosibirsk"); without a region you get results for the whole country (Russia or Turkey). Russian names and region IDs work too, and an unknown name gets suggestions. Yandex operators such as `site:`, `"exact phrase"`, `-word` and `!word` work in search terms. `term | City` sets a region for one term only when the part after `|` is a known place; if you need Yandex's OR operator `|` followed by a place name, put the region in the Region field instead.

**Is there a limit on the Apify Free plan?**
Yes: up to $0.25 of results per Actor per month (about 40 searches of up to 20 results). The run then ends with a clear message; any paid Apify plan removes the limit. Apify platform usage is billed separately for all plans.

**How do I track rankings or get only new results?**
Schedule the Actor (e.g. daily) and turn on **Compare with the previous run**, every organic result gets `previousPosition`, `positionChange` (positive = moved up) and `isNew`, and each tracked domain its position change, or **Only new results**, which delivers only organic results whose URL is new for that search term (you pay only for those; if nothing is new, $0.0012 per processed request applies). Both options can be combined: you get only the new results, while tracked domain `position` and the rank history still cover every result found this run. The history stays in your account.

**Why is `url` null for ads?**
Ads link through Yandex's click tracking redirect, which is not the advertiser's page. You get the advertiser's displayed domain, title, text and rating instead.
