# SERP Multi — 6 Search Engines × Country-Native Routing (`selleraim.com/serp-multi`) Actor

Search 6 major engines in a single run — Google, Bing, DuckDuckGo, Yahoo, Baidu, and Yandex — with country-native domain & language routing across 25+ markets. One JSON input, one structured dataset row per organic result, plus a cross-engine SUMMARY for rank-tracking and SEO research.

- **URL**: https://apify.com/selleraim.com/serp-multi.md
- **Developed by:** [Seller Aim](https://apify.com/selleraim.com) (community)
- **Categories:** SEO tools, E-commerce, AI
- **Stats:** 5 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## SERP Multi — 6 Search Engines × Country-Native Routing

Aggregated organic search-results scraper for **Google, Bing, DuckDuckGo, Yahoo, Baidu, and Yandex**, with built-in country-native domain & language routing across 25+ markets. One JSON input → one structured dataset row per organic result → one cross-engine `SUMMARY.json`.

A multi-engine alternative to SerpAPI: you don't lose Baidu's Chinese-language coverage or Yandex's Russian results when you change country, and you can compare cross-engine ranking in a single run.

### Quick start

Open the Actor in Apify Console, paste the input below, run.

```json
{
  "queries": ["coffee maker", "best wireless earbuds"],
  "country": "US",
  "engines": ["google", "bing", "duckduckgo", "yahoo", "baidu", "yandex"],
  "resultsPerQuery": 10,
  "safeSearch": "moderate",
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

### Input

| Field | Required | Description |
|---|---|---|
| `queries` | yes | 1–50 search queries (any language) |
| `country` | yes | ISO 3166-1 alpha-2 — picked from the supported 25-country dropdown |
| `engines` | no | Subset of `{google, bing, duckduckgo, yahoo, baidu, yandex}`. Leave empty/all-selected = query every supported engine. Baidu/Yandex auto-skip outside their native markets unless explicitly selected |
| `resultsPerQuery` | no | 1–100, default 10. Note: Google often returns fewer than `resultsPerQuery` organic rows because it reserves slots for People Also Ask, news, videos, etc. Set 15–20 if you want 10 organic |
| `language` | no | ISO 639-1 override (e.g. `en`, `zh`, `ru`). Default derived from country |
| `safeSearch` | no | `off` / `moderate` / `strict`; default `moderate` |
| `maxRequestsPerRun` | no | default 5000 |
| `maxConcurrency` | no | default 10 |
| `summaryTopN` | no | top-N domains per query in SUMMARY; default 50 (0 disables) |
| `proxyConfiguration` | no | default `{ useApifyProxy: true, apifyProxyGroups: ["RESIDENTIAL"] }`. Residential strongly recommended |

### Output

#### Dataset — one row per organic result

```json
{
  "query": "coffee maker",
  "engine": "google",
  "position": 1,
  "title": "Best Coffee Makers 2026 — Wirecutter",
  "url": "https://www.nytimes.com/wirecutter/...",
  "displayUrl": "nytimes.com › wirecutter › ...",
  "snippet": "Our pick after 80 hours of testing is the …",
  "country": "US",
  "language": "en",
  "scrapedAt": "2026-05-13T05:01:43.502Z"
}
```

#### Key-value store `SUMMARY` — cross-engine analytics

- `meta` — country, language, queries, enginesUsed/Skipped/WithErrors, per-engine attempt/success/blocked/empty counts, request timings, Crawlee stats
- `perQuery[query]`:
  - `totalResults`, `uniqueDomains`
  - `topDomainsByCoverage` — domains ranked by how many engines surface them, with best position
  - `overlapMatrix` — Jaccard similarity between every engine pair on this query's URL set (host+path, query-string stripped)

Example: `overlapMatrix["bing-vs-google"] = 0.55` means 55% of the URLs are common between Bing and Google for that query.

### Engine notes

#### Google (delegated)

Google is delegated to Apify's official **[`apify/google-search-scraper`](https://apify.com/apify/google-search-scraper)** Actor via `Actor.call()`. They handle JavaScript rendering, CAPTCHA solving, IP rotation — work we can't replicate without specialized infrastructure. **You'll see a child Actor run in your Apify Console for every serp-multi run that includes Google, and your bill will include that child Actor's cost** (priced per result by the upstream Actor).

If you want to skip Google to avoid the child-Actor cost, set `engines` to anything excluding `"google"`.

#### Bing, DuckDuckGo, Yahoo, Baidu, Yandex (in-house)

These five engines are scraped directly via Cheerio + Apify residential proxies. Each engine has its own adapter in `src/adapters/` that builds the right URL and parses the SERP HTML. Block-detection per engine is reported in `SUMMARY.meta.enginesWithErrors`.

#### Country routing & soft-skip rules

Each country has a native-engines list in `src/locale.js`. By default:

- **Baidu** only runs for CN (and where you've explicitly selected it).
- **Yandex** only runs for RU/TR (and where you've explicitly selected it).
- **Yahoo** is hard-skipped for countries with no working Yahoo subdomain (e.g., SE, PL, KR, CN, RU…).

You can force any engine for any country by explicitly listing it in `engines`. A warning is logged if results are likely to be weak.

### Pricing & cost model

Two cost components:

1. **serp-multi compute** — small; mostly idle except during result aggregation.
2. **apify/google-search-scraper compute + per-result fee** — only when Google is included. See the [upstream Actor's pricing](https://apify.com/apify/google-search-scraper#pricing).

To estimate: each query × Google ≈ 1 child-Actor task. Multiple queries are batched into a single `Actor.call`, so 10 queries = 1 child run.

### Local development

```bash
npm install
npm test                 # 39 unit tests (locale, adapter parsers, aggregate)
npm run start            # local Actor run (reads .actor/INPUT.json)
npm run test:integration # manual real-network smoke (does NOT delegate Google)
```

Note: a local `npm run start` will try `Actor.call('apify/google-search-scraper', ...)` only if your local environment has a valid Apify token (`APIFY_TOKEN` env var); otherwise the Google engine is reported as failed and other engines still run.

### Architecture

- `src/main.js` — orchestration: validate → resolve locale → filter engines → split (Google → Apify Actor.call, others → CheerioCrawler) → aggregate → SUMMARY → exit
- `src/adapters/` — one file per engine. Each exports `{ name, buildUrl(...), parseResponse(html, $) }`. Google's adapter is retained for unit-test value and a future self-hosted fallback path.
- `src/locale.js` — country → per-engine routing params (Google domain/gl/hl, Bing cc/setlang, etc.). 25 countries + generic English fallback.
- `src/aggregate.js` — dataset rows → SUMMARY (overlapMatrix + topDomainsByCoverage + error thresholds).
- `test/` — `node:test` + fixtures captured from real engine HTML.

### Limitations

- v0.3 returns **organic results only** — no ads, knowledge panel, featured snippet, related searches, images, videos, news, or shopping. Those modules are deferred to v0.4+.
- One country per run (cross-country comparison requires multiple runs).
- Pagination is implicit (we request `num=100` / `count=50` / engine-equivalent in a single HTTP request); no `start=10/20/…` paging support.
- **Yandex reliability**: Yandex aggressively serves a CAPTCHA verification page (`<title>Верификация</title>`) to scraper traffic. Even with `apifyProxyCountry: 'RU'` + session rotation, individual queries may exhaust all retries with CAPTCHA. Expect 30-70% per-query success rate on RU country; rerun if needed. Bing/DuckDuckGo/Google work reliably on RU.
- **Yahoo**: A/B layout variants occasionally produce 0-row results even on supported countries. Multiple selector fallbacks are in place but the failure mode isn't fully eliminated.
- **DuckDuckGo** is auto-skipped for CN (network-level blocked); SUMMARY's `enginesSkipped` will indicate the reason.

# Actor input Schema

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

1–50 search queries.

## `country` (type: `string`):

Target market. Engine routing (Google domain, Bing cc, Yahoo subdomain, Yandex lr) is derived from this.

## `engines` (type: `array`):

Engines to query. Leave empty (deselect all) to query every supported engine. Baidu/Yandex are auto-skipped outside their native markets unless explicitly selected.

## `resultsPerQuery` (type: `integer`):

Organic results to request per (engine, query). 1–100.

## `language` (type: `string`):

ISO 639-1 (e.g. en, zh, ru). Leave blank to derive from country.

## `safeSearch` (type: `string`):

Family-friendly filter level applied to each engine that supports it: off / moderate / strict.

## `maxRequestsPerRun` (type: `integer`):

Hard cap on generated HTTP requests across all (engine × query) tuples. Round-robin truncation by query if exceeded.

## `maxConcurrency` (type: `integer`):

Global max parallel HTTP requests across all engines.

## `summaryTopN` (type: `integer`):

Cap on topDomainsByCoverage per query in SUMMARY.json. 0 disables topDomainsByCoverage.

## `proxyConfiguration` (type: `object`):

Apify proxy configuration. Residential proxies strongly recommended for Google / Baidu / Yandex.

## Actor input object example

```json
{
  "queries": [
    "coffee maker"
  ],
  "country": "US",
  "engines": [
    "google",
    "bing",
    "duckduckgo",
    "yahoo",
    "baidu",
    "yandex"
  ],
  "resultsPerQuery": 10,
  "safeSearch": "moderate",
  "maxRequestsPerRun": 5000,
  "maxConcurrency": 10,
  "summaryTopN": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `summary` (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 = {
    "queries": [
        "coffee maker"
    ],
    "country": "US",
    "engines": [
        "google",
        "bing",
        "duckduckgo",
        "yahoo",
        "baidu",
        "yandex"
    ],
    "resultsPerQuery": 10,
    "safeSearch": "moderate",
    "maxRequestsPerRun": 5000,
    "maxConcurrency": 10,
    "summaryTopN": 50,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("selleraim.com/serp-multi").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 = {
    "queries": ["coffee maker"],
    "country": "US",
    "engines": [
        "google",
        "bing",
        "duckduckgo",
        "yahoo",
        "baidu",
        "yandex",
    ],
    "resultsPerQuery": 10,
    "safeSearch": "moderate",
    "maxRequestsPerRun": 5000,
    "maxConcurrency": 10,
    "summaryTopN": 50,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("selleraim.com/serp-multi").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 '{
  "queries": [
    "coffee maker"
  ],
  "country": "US",
  "engines": [
    "google",
    "bing",
    "duckduckgo",
    "yahoo",
    "baidu",
    "yandex"
  ],
  "resultsPerQuery": 10,
  "safeSearch": "moderate",
  "maxRequestsPerRun": 5000,
  "maxConcurrency": 10,
  "summaryTopN": 50,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call selleraim.com/serp-multi --silent --output-dataset

```

## MCP server setup

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

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/IN6IqzNMElefnE5oI/builds/VaJAbqw3a5UzFAA69/openapi.json
