# Yandex Search Scraper - Web, Images, Videos & SEO Rankings (`abotapi/yandex-serp-scraper`) Actor

Scrape Yandex web, image and video search results with URLs, snippets and organic rankings for SEO and competitor analysis. Парсер Яндекс Поиска, картинок и видео: ссылки, сниппеты и позиции для SEO, мониторинга выдачи и анализа конкурентов.

- **URL**: https://apify.com/abotapi/yandex-serp-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Features:** Limited permissions
- **Stats:** 4 total users, 2 monthly users, 91.4% runs succeeded, 2 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 serp results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Yandex Search SERP Scraper

Scrape organic Yandex Search results for any list of queries, across web, image, and video search. Every result comes back as clean, structured JSON with its title, URL, domain, and a continuous position across the fetched pages of the query walk, ready for rank tracking, SEO monitoring, and competitor research. Ads and vertical inserts (news, image, and video carousels shown on a web SERP) are left out of the organic rows by default, so every emitted position reflects the organic rank within the walk (the full result list when you start at page 1). Export the dataset as JSON, CSV, or Excel, or pull it straight into your app through the API.

### Why This Scraper?

- **Organic results by default.** Ads, the knowledge panel and inline image or video strips are left out of the organic rows, so positions are genuine organic ranks. Three opt-in switches (`includeAds`, `includeKnowledgeGraph`, `includeInlineMedia`, all off) return those blocks as separate rows with `kind` set, never numbered. Yandex serves those blocks only for some queries, regions and exits, so a run can return none of them; the knowledge panel is best effort.
- **Continuous position tracking.** Every result carries its query, 1-based SERP page, and a running 1-based position across the whole query walk, not just the current page. When you set `startPage` above 1, `page` stays the real SERP page but `position` counts from 1 at the first page you fetched, so it is the rank inside your window rather than the absolute rank.
- **Three verticals from one input.** Pick web, images, or videos with `searchType`. Media rows add their own thumbnail, duration, view count, and embed fields.
- **Pagination that knows when to stop.** The walk keeps requesting pages until `maxPages` is reached or a page returns no new organic results.
- **A hard run-wide cap.** `maxItems` bounds total emitted results across every query, so run size and cost stay predictable. It defaults to 100000, the maximum, which is effectively uncapped; set a lower value to cap a run. A run that stops at the cap skips EXPIRED detection in incremental mode.
- **Built for monitoring.** Incremental mode returns only new and changed results on repeated runs, and `resumeFromRunId` skips results an earlier run already collected (see the resume notes below for its limits).
- **Loud failures, not silent gaps.** A search that is fully refused fails the run with a clear message instead of quietly returning an empty dataset.

### Use Cases

- **SEO and rank tracking:** monitor organic positions for target keywords over time, using incremental mode to see only what moved.
- **Competitor and SERP research:** see which domains and pages currently rank for a query, with breadcrumbs and snippets included.
- **Content and media discovery:** pull the current top videos or images for a topic, with thumbnails, durations, and view counts.
- **Brand monitoring:** track how a brand or product name surfaces in Yandex search results.
- **Data science and NLP datasets:** build structured, position-tagged corpora of titles and snippets for downstream analysis.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live SERP.

| Field | Example |
| --- | --- |
| `query` | `"wireless noise cancelling headphones"` |
| `page` | `1` |
| `position` | `1` |
| `title` | `"Sample Audio Shop - Noise Cancelling Headphones"` |
| `url` | `"https://www.example-audio-shop.com/noise-cancelling-headphones"` |
| `domain` | `"example-audio-shop.com"` |
| `breadcrumb` | `"example-audio-shop.com › headphones › noise-cancelling"` (null when the SERP served none) |
| `snippet` | `"Compare top noise cancelling headphones in stock at Sample Audio Shop..."` (null when the SERP served none) |
| `changeType` | `"NEW"` (incremental mode only: `NEW`, `UPDATED`, `UNCHANGED`, or `EXPIRED`; absent when incremental mode is off) |
| `changedFields` | `["content"]` (incremental mode only, populated on `UPDATED` rows) |
| `firstSeenAt` | `"2026-01-01T00:00:00.000Z"` (incremental mode only) |
| `lastSeenAt` | `"2026-03-01T00:00:00.000Z"` (incremental mode only) |
| `kind` | absent for web rows, `"video"` or `"image"` for media rows; `"ad"`, `"knowledge_graph"`, `"inline_images"` or `"inline_videos"` for the opt-in side rows (web search) |
| `mediaUrls` | `["https://example-cdn.com/thumb1.jpg"]` (inline image and video rows) |
| `clickUrl` | Ad rows only: Yandex's own click-tracking link; the advertiser's site is in `domain` / `url` |
| `blockPosition` | `3` (ad, knowledge panel and inline media rows: block index on the page; these rows carry no `position`) |
| `thumbnailUrl` | `"https://example-cdn.com/thumb.jpg"` (media rows) |
| `imageUrl` | `"https://example-cdn.com/original.jpg"` (image rows) |
| `sourceHost` | `"example-video-host.com"` (video rows) |
| `embedUrl` | `"https://example-video-host.com/embed/sample123"` (video rows) |
| `durationSeconds` | `245` (video rows) |
| `durationText` | `"4:05"` (video rows, as displayed) |
| `views` | `"12K"` (video rows, as displayed) |
| `publishedDate` | `"3 months ago"` (video rows, as displayed) |

Image and video rows share the same shape as web rows (`query`, `page`, `position`, `title`, `url`, `domain`) plus their own media fields; the web-only `breadcrumb` and `snippet` fields are not populated for media rows. `EXPIRED` rows (incremental mode with `emitExpired`) are slim tombstones carrying only `recordId`, `url`, `changeType`, and `lastSeenAt`, where `lastSeenAt` is the time of the run that marked them expired.

### How to Use

1. Enter one or more search **queries**.
2. Pick a **search type**, web, images, or videos, and set **Max pages per query** if you need more than the first page.
3. Set **Max results** to cap run size and cost, and adjust **Proxy & connection** if you need a different proxy.
4. Start the run, then read the dataset as JSON, CSV, or Excel, or through the API. Views: `organic-results` (all rows), `web`, `images` and `videos` (one view per search type), and `changes` (incremental `changeType`, `changedFields` and timestamps).

**Search two queries on the web vertical, two pages each:**

```json
{
  "queries": ["wireless noise cancelling headphones", "portable espresso maker"],
  "maxPages": 2,
  "maxItems": 100
}
```

**Image search:**

```json
{
  "searchType": "images",
  "queries": ["minimalist desk setup"],
  "maxItems": 50
}
```

**Video search with a region code:**

```json
{
  "searchType": "videos",
  "queries": ["home workout routine"],
  "lr": 213,
  "maxPages": 2
}
```

**Recurring monitoring of one query:**

```json
{
  "queries": ["wireless noise cancelling headphones"],
  "incrementalMode": true,
  "emitExpired": true,
  "stateKey": "headphones-watch"
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/yandex-serp-scraper").call(run_input={"queries": ["wireless noise cancelling headphones"], "maxPages": 2})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["position"], item["title"], item["url"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/yandex-serp-scraper').call({ queries: ['wireless noise cancelling headphones'], maxPages: 2 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets, or webhooks from the **Integrations** tab.

#### How Max results interacts with pagination and incremental mode

`maxItems` (Max results) is a hard cap on rows emitted across the whole run, counted by rows actually pushed to the dataset, not by results merely scanned. On web and video search, each query's own walk stops early once a page returns no new organic results, even before `maxPages` or `maxItems` is reached; image search keeps paging past a page of repeats and stops on an empty page.

In incremental mode, a page's share of the remaining cap is taken before unchanged rows are suppressed, so on a capped run some new results further down that page can be skipped even though the cap was not filled. They are not lost: they are returned as `NEW` on a later run.

**Known limitation:** when incremental mode's `emitExpired` is on, `maxItems` only governs the newly collected `NEW` / `UPDATED` / `UNCHANGED` rows. The `EXPIRED` batch (previously tracked results this run's queries no longer found) is pushed as a separate step that is not counted against `maxItems`. If your tracked scope holds far more previously-seen results than this run's queries still match, a tightly capped run can still emit, and be billed for, far more rows than `maxItems` once the `EXPIRED` batch is added. Leave `emitExpired` off on tightly capped runs, or budget for it separately from `maxItems`.

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`): paste a previous run ID or dataset ID to continue an interrupted pull. Every query is searched again from page 1, and results whose URL was already collected there are skipped, never re-emitted. Cannot be combined with incremental mode.
- **Incremental mode** (`incrementalMode`): for daily or recurring monitoring of the same search. The first run returns everything as `NEW`; later runs return only `NEW` and `UPDATED` rows, with `changeType`, `changedFields`, `firstSeenAt`, and `lastSeenAt` on every returned result row. A pure rank move alone is not a content change (position and page are excluded from the fingerprint), so a result that only shifted rank classifies `UNCHANGED`. `UNCHANGED` rows are suppressed unless `emitUnchanged` is on. `EXPIRED` rows are only produced after a run that fully scanned every query and page in scope, and only when `emitExpired` is on. State is keyed per search scope, or per your own `stateKey`, in a named key-value store. Compared fields are `title`, `snippet` and `domain` (the URL is the record identity); `position`, `page`, media and counter fields are never compared. Use `ignoreFieldsForChanges` to leave any of the compared fields out of change detection: tracked results are re-checked against the stored per-field snapshot with the current list, so changing it causes no one-time wave of `UPDATED` rows (results saved by older runs have no per-field snapshot and are compared on all fields once).

**Known limitation:** `resumeFromRunId`'s dedup set is built from every URL in the resumed dataset, without regard to which query originally returned it. If two different queries in your list can legitimately return the same URL (common for closely related terms), and that URL was already collected under one query in the resumed run, this run silently skips it under every query, including a query that never actually returned it before.

**Known limitation:** on web and video search, a page where every result was already collected ends that query's walk, just like a page with no new results. So a resume does not reach later pages of a query whose first page was fully collected before the interruption: a `maxPages: 3` query interrupted after page 1 returns nothing new on resume. The same applies across queries: if a query's first page consists only of URLs another query already collected, its later pages are never requested. Positions on resumed rows are numbered from 1 over the newly returned results only, so they do not continue the earlier run's numbering. If you need complete, correctly ranked results for a query, run it again fresh (without `resumeFromRunId`), one query at a time if queries overlap.

### Send results into your apps (MCP connectors)

Optionally send a condensed summary of the results to apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.

1. Authorize a connector once under **Apify > Settings > Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the `mcpConnectors` input field.
3. For Notion, also set `notionParentPageUrl` to the page where items should be created.
4. `maxNotifyListings` caps how many results are sent to each connector per run.

The connection goes through Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `searchType` | string | `web` | Which Yandex vertical to search: `web`, `images`, or `videos`. Images and videos return media fields like thumbnails and durations. |
| `queries` | array | sample query | One SERP walk per query. Each query gets a fresh proxy session. Required. |
| `maxPages` | integer | `0` | SERP pages per query; 0 or empty means 1 page, with a hard cap of 30 pages per query. Stops early when a page returns no new results. |
| `lr` | integer | (none) | Yandex region code for the SERP locale, for example `213` (Moscow), `2` (Saint Petersburg), `21265` (San Francisco). Leave empty for auto-detection. |
| `domain` | string | `yandex.com` | `yandex.com` or `yandex.ru`. Images always run on yandex.com. |
| `startPage` | integer | 1 | First SERP page to fetch; with Max pages per query it selects a page window. |
| `lang` | string | (any) | Two-letter result language, for example `en` or `ru` (web search). |
| `period` | string | `all` | Freshness window: `day`, `two_weeks` or `month` (web search only). |
| `sortBy` | string | `relevance` | `date` sorts web results newest first. |
| `imageType` | string | (any) | Image search: `photo`, `clipart`, `lineart`, `demotivator` or `face`. |
| `imageColor` | string | (any) | Image search: `color`, `gray`, or one color such as `red` or `blue`. |
| `imageOrientation` | string | (any) | Image search: `horizontal`, `vertical` or `square`. |
| `imageFileType` | string | (any) | Image search: `jpg`, `png` or `gifan`. |
| `imageWidth` | integer | (none) | Image search: exact width in pixels; applies only together with `imageHeight`. |
| `imageHeight` | integer | (none) | Image search: exact height in pixels; applies only together with `imageWidth`. |
| `imageSite` | string | (any) | Image search: only images hosted on this site, for example `example.com`. |
| `includeAds` | boolean | `false` | Web search: also return sponsored results as separate rows with `kind` `ad`; never numbered. Each row counts toward Max results and is billed like any result row. With Incremental mode on, these rows are not tracked: they are re-sent and billed on every run, even when unchanged. The advertiser site is in `domain` and `url`; Yandex's own tracking link is in `clickUrl`. |
| `includeKnowledgeGraph` | boolean | `false` | Web search: also return the knowledge panel (when Yandex shows one) as a row with `kind` `knowledge_graph`. Counts toward Max results and is billed like any result row. With Incremental mode on, these rows are not tracked: they are re-sent and billed on every run, even when unchanged. |
| `includeInlineMedia` | boolean | `false` | Web search: also return inline image and video strips as rows with `kind` `inline_images` or `inline_videos`. Counts toward Max results and is billed like any result row. With Incremental mode on, these rows are not tracked: they are re-sent and billed on every run, even when unchanged. |
| `maxItems` | integer | `100000` | Hard cap on emitted dataset items across the whole run. The default is also the maximum (100000), so a run you do not cap yourself is effectively uncapped. In incremental mode a run that stops at this cap skips EXPIRED detection. Very large runs need a long run timeout, each query holds one browser session. |
| `proxySettings` | object | Apify residential | Proxy configuration. Residential is the default and returns the fullest result pages; it needs a paid Apify plan (see Plan Requirement), or enter your own proxy URLs. |
| `resumeFromRunId` | string | (none) | Paste a previous run ID or dataset ID to continue an interrupted pull without returning results already collected there. Cannot be combined with incremental mode. |
| `mcpConnectors` | array | (none) | Optional. Send a condensed summary of the results to apps you authorized under Apify Settings, Integrations, via Model Context Protocol (MCP) connectors. |
| `notionParentPageUrl` | string | (none) | Notion connector only: URL or id of the page under which result pages are created. |
| `maxNotifyListings` | integer | `50` | Maximum results sent to each connector per run. Does not affect the dataset. |
| `ignoreFieldsForChanges` | array | (none) | Incremental mode only, last option of the Resume & recurring updates section. Title: Ignore fields when detecting changes (incremental mode only). Supported values: `title`, `snippet`, `domain`. |
| `incrementalMode` | boolean | `false` | Turn this on for daily or recurring monitoring. The first run returns every matching result as `NEW`. Later runs normally return only `NEW` and `UPDATED` results. A pure rank move alone is not a content change. Cannot be combined with `resumeFromRunId`. |
| `stateKey` | string | (none) | Optional. Name this monitoring campaign to keep its state stable, or deliberately share state across differently configured runs. Leave empty to derive a key automatically from the search settings. |
| `emitUnchanged` | boolean | `false` | Off by default. Turn on to also return records that have not changed since the last run, marked `UNCHANGED`. This returns, and bills, extra rows you already have. |
| `emitExpired` | boolean | `false` | Off by default. Turn on to also return records that were present in a previous run but are no longer found, marked `EXPIRED`. Only produced once a run has fully scanned the tracked search. |

Each row also carries `searchedAt`, `servedDomain`, and on web rows `servedLr` and, when Yandex shows one, `totalResultsFound`. These add no extra billed rows.

### Output Example

> Sample shape: values are illustrative placeholders, not from a live SERP.

```json
{
  "query": "wireless noise cancelling headphones",
  "page": 1,
  "position": 1,
  "title": "Sample Audio Shop - Noise Cancelling Headphones",
  "url": "https://www.example-audio-shop.com/noise-cancelling-headphones",
  "domain": "example-audio-shop.com",
  "breadcrumb": "example-audio-shop.com › headphones › noise-cancelling",
  "snippet": "Compare top noise cancelling headphones in stock at Sample Audio Shop..."
}
```

### Plan Requirement

Yandex refuses plain, unproxied requests with a captcha redirect, so this actor needs a proxy connection. The default connection uses Apify residential proxy. Apify free plans do not include residential proxies (or any Apify proxy): on a free plan the run logs "The selected connection is unavailable to this run. Apify free plans do not include residential proxies." and continues on a backup connection, noting "Served over the backup connection" when it is used; some pages may still be unavailable. If that backup cannot load the pages the run stops with "No results: residential proxies are unavailable on this run ... Upgrade at https://apify.com/pricing or supply your own proxy URLs". If your account's proxy access is refused outright the run stops with "The proxy connection was refused for this account ... a paid plan is required". For reliable results use a paid Apify plan, or enter your own proxy URLs in **Proxy & connection**. This actor charges per event: a fixed run-start charge plus one charge per dataset row returned: every row counts as one result, whether it is an organic, image or video row, an opt-in ad, knowledge panel or inline media row, or an `UNCHANGED` / `EXPIRED` row when you turn those on. The **Pricing** tab shows current rates. Occasional automated verification checks are resolved by the actor itself and are never billed to you as a separate Apify event; see the FAQ below for how that affects a run that fails instead of returning results.

### FAQ

#### How much does it cost?

You pay a run-start charge plus one charge per dataset row returned: organic, image and video rows, the opt-in ad, knowledge panel and inline media rows, and `UNCHANGED` / `EXPIRED` rows when you turn those on. The **Pricing** tab shows the current rates. Use **Max results** to cap the cost of any run.

#### Is it legal to scrape Yandex?

This actor collects only publicly available search result data. You are responsible for how you use it: follow Yandex's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Search snippets and thumbnails may reflect third-party content and rights.

#### Can I get only new or changed results on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new and updated results, and unchanged ones are not billed unless you also turn on **Emit unchanged**.

#### Why did my run fail instead of returning an empty dataset?

If a search is refused on every attempt, the run stops with a clear message so "no results for this query" is never confused with "nothing could be read." Run it again in a few minutes. A run can also stop if the cost of resolving automated verification checks would exceed a fixed ceiling relative to the run's size, a sign that the connection is being refused unusually hard; retrying, often with a different proxy country, usually clears it.

#### Why does an incremental run with Emit expired sometimes return far more rows than Max results?

`emitExpired` pushes every previously tracked result your queries no longer found, and that batch is not capped by `maxItems`. On a scope with a large tracked history, this can push and bill well beyond your `maxItems` setting in a single run. Leave `emitExpired` off for tightly capped runs, or expect it to add rows outside that cap.

#### Why is a shared result missing from one of my queries after a resume?

`resumeFromRunId` skips any URL already present anywhere in the resumed dataset, regardless of which query returned it. If two queries can legitimately share a result, the second query's copy is silently skipped when it was already collected under the first, and if that query's whole first page was already collected, its later pages are not requested at all. Resume is most useful for queries the interrupted run never reached, and even those can lose results shared with other queries; for a query's complete, correctly ranked result set, run it fresh without `resumeFromRunId`.

#### Can I use it with AI agents or automation tools?

Yes. Call it from any Apify integration, the API, or an MCP-compatible client, and route the results into your own pipeline or app.

### 🔗 Want more Yandex data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🧩 <a href="https://apify.com/abotapi/yandex-maps-scraper"><b>Yandex Maps Scraper</b></a><br>Scrape Yandex Maps businesses and places by keyword or URL for lead generation, research...</td><td>🤖 <a href="https://apify.com/abotapi/ai-web-search-tool"><b>AI Search Tool</b></a><br>Give your AI agents real-world knowledge. This Actor provides high-quality web, news...</td></tr>
<tr><td>🧩 <a href="https://apify.com/abotapi/webtoons-com-scraper"><b>WEBTOON Scraper</b></a><br>Scrape the WEBTOON catalog into structured data. Extract series, genres, rankings and...</td><td>🧩 <a href="https://apify.com/abotapi/dzen-ru-scraper"><b>Dzen.ru Scraper</b></a><br>Scrape Dzen.ru (ex Yandex.Zen): articles, videos and channels. Full article text with...</td></tr>
<tr><td>🧩 <a href="https://apify.com/abotapi/shortmax-scraper"><b>ShortMax Scraper</b></a><br>Scrape the ShortMax short-drama catalog: titles, descriptions, creators, view and follow...</td><td>🤖 <a href="https://apify.com/abotapi/web-scraper-for-llms"><b>Web Scraper For Llms</b></a><br>Stealth web scraping engine built for LLMs. Converts any web page to clean markdown or...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/yandex-serp-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `searchType` (type: `string`):

Which Yandex vertical to search: web results, images, or videos. Images and videos return media fields like thumbnails and durations.

## `queries` (type: `array`):

One SERP walk per query. Each query gets a fresh proxy session.

## `maxPages` (type: `integer`):

SERP pages per query (page 1 is \&p=0). 0 or empty reads as 1 page. Hard cap of 30 pages per query. Stops early if a page returns no new organic results.

## `domain` (type: `string`):

Which Yandex site to query. yandex.ru returns Russian-market results (use a RU region); yandex.com is the international site. Images always use yandex.com.

## `startPage` (type: `integer`):

First SERP page to fetch (1-based). Combine with Max pages per query to continue a walk, e.g. start 4 with 3 pages returns pages 4-6. Each row's page is the real SERP page, but position counts from 1 inside the fetched window, so it is not the absolute rank when you start after page 1.

## `lang` (type: `string`):

Two-letter language code to restrict results to (for example en, ru, de, tr, uk). Leave empty for any language. Web search only.

## `period` (type: `string`):

Restrict web results to a time window. Web search only; image and video search ignore it.

## `sortBy` (type: `string`):

Order web results by relevance (Yandex default) or by date, newest first.

## `imageType` (type: `string`):

Images only. Restrict to one image kind.

## `imageColor` (type: `string`):

Images only. Restrict to a color or to color / gray.

## `imageOrientation` (type: `string`):

Images only.

## `imageFileType` (type: `string`):

Images only.

## `imageWidth` (type: `integer`):

Images only. Exact width in pixels; must be set together with Image height.

## `imageHeight` (type: `integer`):

Images only. Exact height in pixels; must be set together with Image width.

## `imageSite` (type: `string`):

Images only. Only images hosted on this site, for example example.com.

## `lr` (type: `integer`):

Yandex region code for the SERP locale, e.g. 213 (Moscow), 2 (Saint Petersburg), 21265 (San Francisco). Only meaningful on yandex.ru. Leave empty for auto-detection.

## `maxItems` (type: `integer`):

Hard cap on emitted dataset items across the whole run. Defaults to 100000, which is also the maximum, so a run you do not cap yourself is effectively uncapped (very large runs need a long run timeout — each query holds one browser session). A run that stops at this cap skips EXPIRED detection in incremental mode.

## `includeAds` (type: `boolean`):

Web search only. Also return the sponsored results shown on the page as separate rows with kind set to ad. They are never numbered, so organic positions stay true ranks. Each one is a normal result row: it counts toward Max results and is billed like any other result. With Incremental mode on, these rows are not tracked: they are re-sent and billed on every run, even when unchanged.

## `includeKnowledgeGraph` (type: `boolean`):

Web search only. Also return the knowledge panel Yandex shows for some queries as a separate row with kind set to knowledge_graph. Most queries have none. It counts toward Max results and is billed like any other result row. With Incremental mode on, these rows are not tracked: they are re-sent and billed on every run, even when unchanged.

## `includeInlineMedia` (type: `boolean`):

Web search only. Also return the image and video strips embedded in the page as separate rows with kind set to inline_images or inline_videos and their thumbnail links in mediaUrls. They count toward Max results and are billed like any other result row. With Incremental mode on, these rows are not tracked: they are re-sent and billed on every run, even when unchanged.

## `proxySettings` (type: `object`):

Proxy configuration. Residential is the default (RU for yandex.ru, US for yandex.com) — it returns the fullest result pages.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue an interrupted pull without returning results already collected there. Cannot be combined with incremental mode.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns every matching result as NEW. Later runs normally return only NEW and UPDATED results. A result is UPDATED when its title, snippet or domain changed (its URL is its identity). Position, page, media and counter fields are never compared, so a pure rank move is not an UPDATED row. Cannot be combined with resumeFromRunId.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or deliberately share state across differently configured runs. Leave empty to derive a key automatically from the search settings.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return records that have not changed since the last run, marked UNCHANGED. This returns, and bills, extra rows you already have.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return records that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search.

## `ignoreFieldsForChanges` (type: `array`):

Optional. Fields to leave out when deciding whether a result changed. Supported: title, snippet, domain. A result whose only difference is in an ignored field stays UNCHANGED and is not returned as UPDATED. Tracked results from earlier runs are re-checked with this list, so adding a field does not trigger a wave of UPDATED rows (results saved before this option existed are compared on all fields once).

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify, Settings, Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON; the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "searchType": "web",
  "queries": [
    "best coffee grinder"
  ],
  "maxPages": 0,
  "domain": "yandex.com",
  "startPage": 1,
  "period": "all",
  "sortBy": "relevance",
  "maxItems": 100000,
  "includeAds": false,
  "includeKnowledgeGraph": false,
  "includeInlineMedia": false,
  "proxySettings": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `organicResults` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

## `webResults` (type: `string`):

No description

## `imageResults` (type: `string`):

No description

## `videoResults` (type: `string`):

No description

## `extras` (type: `string`):

No description

## `changes` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchType": "web",
    "queries": [
        "best coffee grinder"
    ],
    "proxySettings": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/yandex-serp-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "searchType": "web",
    "queries": ["best coffee grinder"],
    "proxySettings": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/yandex-serp-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchType": "web",
  "queries": [
    "best coffee grinder"
  ],
  "proxySettings": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false
}' |
apify call abotapi/yandex-serp-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/yandex-serp-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/aTD9PlMHIXn9O9boV/builds/feKtjkq7gVJOe4yqH/openapi.json
