# Multi-Engine SERP - Bing, DuckDuckGo, Yahoo and Baidu Merged (`s-r/multi-serp`) Actor

Ask several search engines the same question and get one merged ranking. Results are fused with reciprocal rank fusion and every row shows which engines found it and where they ranked it, so you can see what they agree on.

- **URL**: https://apify.com/s-r/multi-serp.md
- **Developed by:** [SR](https://apify.com/s-r) (community)
- **Categories:** SEO tools, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 per-run start fees

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

## Multi-Engine SERP

Ask several search engines the same question and get back **one merged
ranking**, with every row showing which engines found it and where each of them
put it.

Bing, DuckDuckGo, Yahoo and Baidu, in any combination.

### What the merged ranking is for

A single engine's top result tells you what that engine likes. A page that
three engines all rank tells you something about the page.

That is the number this actor produces: `engine_count` is how many engines
returned a URL, `engine_ranks` is where each of them put it, and `score` fuses
those into one ordering. Set **Minimum engines per result** to 2 and you get
only the pages more than one engine vouched for, which is a much shorter and
much more trustworthy list.

### How the ranking is computed

Reciprocal rank fusion, the standard method for combining ranked lists:

```
score = sum over engines of 1 / (60 + rank)
```

Nothing invented here. It has the property you want: being ranked third by
three engines beats being ranked first by one, but a single strong result is
not buried. Averaging positions cannot do this, because a page an engine did
not return has no position to average.

### The same page from two engines is one row

Engines return the same page in different clothes:
`https://example.com/x`, `http://www.example.com/x/`, and
`https://example.com/x?utm_source=bing`.

All three collapse to one row. The scheme, the `www`, a trailing slash and
tracking parameters are all ignored when matching; a real query parameter like
`?id=2` is not, because that is a different page. `normalised_url` shows the
form used for matching, and `url` keeps what the engine actually gave.

Without this the actor would report agreement as disagreement, which is the one
thing it exists to measure.

### Expect the engines to disagree

Measured on `price monitoring software` with Bing, DuckDuckGo and Yahoo:
21 merged results, and only **29%** were found by more than one engine. Fifteen
of the twenty-one came from a single engine.

That is the finding, not a fault. These indexes genuinely cover different parts
of the web, which is the argument for asking several: one engine was never
showing you the whole picture. It also means a consensus-only list is short, so
if you set **Minimum engines** to 2, expect a handful of results rather than a
page of them.

### When an engine fails

Engines run concurrently and one failing does not take the run with it. Its
name lands in `errors` and the merge proceeds with the rest, which is the whole
point of asking more than one.

`rowsPerEngine` in the run summary shows what each engine contributed before
merging. An engine sitting at zero there is the signal that it returned nothing
at all, which a merged list alone would hide.

### Market

**Market** is language and country as `en-US` or `nl-NL`. Each engine is given
its own dialect of that, because Bing wants `en-US`, DuckDuckGo wants `us-en`
and Yahoo wants just the language.

### Errors

| Code | Meaning |
|---|---|
| `engine_failed` | One engine could not be read for one keyword |
| `no_results` | No engine returned anything for a keyword |
| `bad_input` | No keyword, or no known engine, was given |

### Related actors

The single-engine actors if you want one index on its own: `bing-serp`,
`duckduckgo-serp`, `yahoo-serp` and `baidu-serp`.

# Actor input Schema

## `keywords` (type: `array`):

One search per line.

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

Which engines to ask. More engines means better coverage and a stronger consensus signal; each one adds a request per keyword.

## `resultsPerEngine` (type: `integer`):

How deep to go in each engine before merging.

## `minEngines` (type: `integer`):

Drop results fewer than this many engines found. Set to 2 for a consensus-only list, which is far shorter and far higher confidence.

## `market` (type: `string`):

Language and country as language-COUNTRY, such as en-US or nl-NL. Each engine is given its own dialect of this.

## `maxResults` (type: `integer`):

Stop after this many merged rows. Also the cost ceiling for the run.

## Actor input object example

```json
{
  "keywords": [
    "best crm for small business"
  ],
  "engines": [
    "bing",
    "duckduckgo",
    "yahoo"
  ],
  "resultsPerEngine": 10,
  "minEngines": 1,
  "market": "nl-NL",
  "maxResults": 200
}
```

# Actor output Schema

## `results` (type: `string`):

One row per merged URL.

## `summary` (type: `string`):

What each engine contributed, and how much they agreed.

## `errors` (type: `string`):

Engines or keywords that could not be read.

# 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 = {
    "keywords": [
        "price monitoring software"
    ],
    "engines": [
        "bing",
        "duckduckgo",
        "yahoo"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("s-r/multi-serp").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 = {
    "keywords": ["price monitoring software"],
    "engines": [
        "bing",
        "duckduckgo",
        "yahoo",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("s-r/multi-serp").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 '{
  "keywords": [
    "price monitoring software"
  ],
  "engines": [
    "bing",
    "duckduckgo",
    "yahoo"
  ]
}' |
apify call s-r/multi-serp --silent --output-dataset

```

## MCP server setup

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

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/19BntgbujzBzDcKsz/builds/afU8bqQilQz183vIV/openapi.json
