# PriceRunner Competitor Price & Rating Monitor (`scrapyx/pricerunner-price-scraper`) Actor

Track what every merchant charges for a product. Search PriceRunner by keyword or monitor product IDs on a schedule, and get each seller's price, shipping, stock and delivery window, plus the min/max spread, rating, review count and full specs. UK, SE, DK and NO.

- **URL**: https://apify.com/scrapyx/pricerunner-price-scraper.md
- **Developed by:** [Ibnu Adzim](https://apify.com/scrapyx) (community)
- **Categories:** E-commerce, Business, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.75 / 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.
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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## PriceRunner Competitor Price & Rating Monitor

Track what **every merchant** charges for a product, not just the headline
price. Search by keyword to discover competitors, or monitor product IDs on a
schedule to watch prices move.

### What you get

One row per product (`recordType: PRODUCT`):

| Group | Fields |
|---|---|
| Product | `productId`, `productName`, `productUrl`, `brandName`, `categoryName`, `categoryRank`, `imageUrl`, `condition`, `outOfStock` |
| Headline price | `lowestPriceAmount`, `cheapestOfferAmount`, `cheapestOfferMerchantId`, `merchantCount` |
| **Competitor spread** | `offers[]` — every merchant's `priceAmount`, `shippingCostAmount`, `stockStatus`, `availability`, `deliveryMinDays`/`deliveryMaxDays`, `labels`, `gotoStoreUrl` |
| Spread summary | `offerCount`, `offerMinPriceValue`, `offerMaxPriceValue`, `offerSpreadValue`, `offerMinPriceInStockAmount` |
| Merchants | `merchants[]` — id, name, PriceRunner profile URL, rating, verification labels |
| Reputation | `ratingAverage`, `ratingCount`, `reviewAverage`, `reviewCount` |
| Specs | `specifications` — the full attribute table, flattened to `"Group / Attribute": value` |

Plus a `SEARCH_SUMMARY` per query and an `ERROR` row per failed input, so
**every input maps to at least one row**.

Measured on a real run: `iphone 15` in the UK returned 25 products carrying
**49–107 merchant offers each**, with price spreads from £20.99 to £925.55.

### Two entry points, one row shape

```json
{ "searchQueries": ["iphone 15"], "includeOffers": true, "country": "UK" }
```

```json
{ "productIds": ["3208336570", "3472630395"], "country": "UK" }
```

They are not modes — they are two ways of naming the same thing, and both
produce identical PRODUCT rows. The normal workflow uses both: discover
competitors by keyword once, then track those IDs on a schedule. `productIds`
always fetches offers, since that is the whole point of tracking one.

### Known limits — read before you buy

- **~1,100 products per query.** Upstream rejects a paging offset above 1,000
  with `offset must be less than 1000`, so one query reaches at most
  1,000 + one page. `upstreamTotalHits` routinely reports far more (7,942 for
  `laptop`), and the summary carries both numbers side by side plus a
  `resultWindowShorterThanTotal` flag. Split into narrower queries to go
  further.
- **Review *text* is not available.** PriceRunner's public endpoints expose
  the rating average and count (`4.25` from `3,692` ratings), not individual
  review bodies. This actor ships what exists rather than an empty column.
- **Four markets: UK, SE, DK, NO.** Germany is deliberately absent —
  `pricerunner.de` answers 404 and redirects to `klarna.com` with no product
  data. Prices, merchants and stock all differ by market.
- **`includeOffers` costs 2 requests per product.** Off by default. 25
  products with offers = 52 requests.

### How it works

No API key, no login, no browser. Two internal REST endpoints:

```
GET /{locale}/api/search-edge-rest/public/search/v6/{COUNTRY}
    ?q=…&facetsSize=10                      → first 20 products + facets
GET /{locale}/api/search-edge-rest/public/search/v6/products/{COUNTRY}
    ?q=…&size=100&offset=…                  → subsequent pages
GET /{locale}/api/product-detail-edge-rest/public/product-detail/v0/initial/{COUNTRY}/{cat}/{id}
GET /{locale}/api/product-detail-edge-rest/public/product-detail/v0/offers/{COUNTRY}/{id}
```

**The URL bar will not page this site.** The search results page is
infinite-scroll, and *every* plausible parameter — `offset`, `page`, `p`,
`from`, `start`, `skip`, `pageNumber`, `o` — is silently ignored: HTTP 200
with the identical first 20 products every time. A scraper that trusted
`?offset=` would loop forever on page 1 and report success. The real endpoint
came out of the route bundle, which names both fetchers literally.

### WAF and TLS

**None enforced.** `chrome124`, `chrome131`, `chrome136`, `firefox133`,
`safari18_0` and `chrome99_android` all returned the full 1.6 MB document.
The page *does* reference a Klarna bot-challenge script
(`__WAF_CHALLENGE_SCRIPT__` → `owp.klarna.com/…/challenge.js`), so the
capability exists and simply is not being served — block detection stays in
the client for the day that changes.

Datacenter proxy is the default, pinned to the selected country. That pin is a
**correctness** setting, not an anti-blocking one: PriceRunner serves prices
and merchant availability per market.

### Policy

`www.pricerunner.com/robots.txt` names **no** AI crawler group at all — no
ClaudeBot, anthropic-ai, GPTBot or CCBot — and `/search`, `/results`, `/pl/`
and `/ri/` are allowed for `User-agent: *`.

Only public product and price listings are read. No login, no user data, no
personal profiles.

# Actor input Schema

## `searchQueries` (type: `array`):

Product keywords to search, one per entry — e.g. 'iphone 15' or 'gaming laptop'. Use this to discover what competitors are selling. Each query can page to about 1,100 products; upstream caps the paging offset at 1,000 regardless of how many matches it reports.

## `productIds` (type: `array`):

Numeric PriceRunner product IDs to monitor directly, taken from a product URL (the 10-digit number in /pl/27-3472630395/...). Use this for scheduled price tracking of products you already identified. Offers and ratings are always fetched for these, regardless of the 'Include offers' toggle.

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

Which PriceRunner market to query. Prices, merchants and stock all differ by market. Germany is not offered: pricerunner.de returns 404 and redirects to klarna.com with no product data.

## `includeOffers` (type: `boolean`):

For each product found by search, also fetch every merchant offer (price, shipping, stock, delivery window), the merchant list, full specifications and the rating summary. This is the competitor-price comparison. Costs 2 extra requests per product, so it is off by default.

## `maxProductsPerQuery` (type: `integer`):

Stop after this many products per search query. Upstream's own hard ceiling is about 1,100 per query because it rejects a paging offset above 1,000 — split into narrower queries to go beyond that.

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

How merchant offers are ordered within each product's offer list. Does not change which products a search returns.

## `pageSize` (type: `integer`):

How many products to request per paging call. Upstream accepts up to 100 exactly and answers 400 'size must be less than or equal to 100' above that. The first page always returns 20 regardless, because it comes from a different endpoint that also carries the facet data.

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

Upper bound on requests in flight across all queries and detail fetches. Paging within one query is always sequential regardless, because each page depends on the previous offset.

## `minRequestInterval` (type: `string`):

Minimum delay between the START of consecutive requests. This, not concurrency, is the honest speed control: once the rate cap binds, extra concurrency buys nothing.

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

Proxy used for every request. Residential is the default and is pinned to the selected market's country. Datacenter exits are answered HTTP 403 by PriceRunner's API regardless of TLS fingerprint (measured: 4/4 attempts failed from datacenter, the same build succeeded from residential), and prices and merchant availability differ per market, so the country pin is a correctness setting too.

## Actor input object example

```json
{
  "searchQueries": [
    "iphone 15"
  ],
  "country": "UK",
  "includeOffers": false,
  "maxProductsPerQuery": 100,
  "sortBy": "RECOMMENDED",
  "pageSize": 100,
  "maxConcurrency": 4,
  "minRequestInterval": "0.25",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# 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 = {
    "searchQueries": [
        "iphone 15"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/pricerunner-price-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 = { "searchQueries": ["iphone 15"] }

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/pricerunner-price-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 '{
  "searchQueries": [
    "iphone 15"
  ]
}' |
apify call scrapyx/pricerunner-price-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapyx/pricerunner-price-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/TF5jFA3dgglik2eDC/builds/MpYZvMvfv7gAjVKci/openapi.json
