# K-Ruoka Scraper — Finnish Grocery Products & Prices (`studio-amba/k-ruoka-fi-scraper`) Actor

Scrape products, prices, and availability from K-Ruoka.fi (Kesko's Finnish grocery chain). Returns EAN, brand, category, and per-unit pricing. No login required.

- **URL**: https://apify.com/studio-amba/k-ruoka-fi-scraper.md
- **Developed by:** [Studio Amba](https://apify.com/studio-amba) (community)
- **Categories:** E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 result scrapeds

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

## K-Ruoka Scraper

Scrape grocery products, prices, and stock status from [K-Ruoka.fi](https://www.k-ruoka.fi) —
the online storefront for Kesko's K-Citymarket, K-Supermarket, K-Market, and
Neste K chains, one of Finland's two largest grocery groups alongside S-Group
(Prisma). Returns EAN codes, brand, category breadcrumb, and per-unit pricing
for each product a search query matches.

### Input

| Field | Type | Description |
|-------|------|-------------|
| `searchQuery` | string | Finnish search term (default: `"kahvi"` for coffee) |
| `maxResults` | integer | Limit, capped at 100 (default: 100) — see [Limitations](#limitations) |
| `sbrWsCdp` | string | Bright Data Scraping Browser CDP endpoint. Optional — the actor ships with one preconfigured |
| `proxyConfiguration` | object | Unused (kept for platform convention) — see [Anti-bot handling](#anti-bot-handling) |

### Output

| Field | Type | Example |
|-------|------|---------|
| `name` | string | `"Pirkka Costa Rica kahvi 500g vaalea paahto"` |
| `brand` | string | `"Pirkka"` |
| `price` | number | null | `5.99` |
| `currency` | string | `"EUR"` |
| `originalPrice` | number | `6.79` (only present during an active promotion) |
| `pricePerUnit` | string | `"11.98 €/kg"` |
| `discount` | string | `"−11 %"` |
| `ean` | string | `"6410405181190"` |
| `sku` | string | Kesko internal PIM code |
| `productId` | string | Site product ID (matches EAN) |
| `inStock` | boolean | null | `true` — `null` when the store doesn't publish availability for this item, never assumed `false` |
| `imageUrl` | string | Product image URL |
| `category` | string | `"Juomat > Kahvi ja suodatinpussit > Jauhettu kahvi vaalea"` |
| `url` | string | Full product page URL |
| `searchQuery` | string | Input echo |
| `searchStoreId` | string | Kesko store the price/availability is scoped to (site-determined default, see below) |
| `scrapedAt` | string | ISO 8601 timestamp |

### Anti-bot handling

K-Ruoka.fi runs Cloudflare Bot Management on every route, including the
sitemap. Plain HTTP requests and headless-Chromium navigations both get a
hard 403 "Pyyntö estetty" (Request blocked) page — confirmed live, this isn't
a solvable interactive challenge. Bright Data Web Unlocker also fails on this
domain: its `expect_element` heuristic waits for a `.nav-button` selector that
never appears and times out after 30 seconds on every attempt, homepage and
API alike.

The only working route is a real, fully-rendered browser session — this
actor connects to a Bright Data Scraping Browser over CDP, navigates the
search page, and reads the JSON response the page's own client code requests
from K-Ruoka's `/kr-api/v2/product-search/` endpoint. No DOM scraping is
involved: the JSON already carries EAN, brand, category, per-unit pricing,
and the image URL. A programmatic `fetch()` injected into the same page
against that endpoint gets a 404 (the site appears to fingerprint request
origin beyond headers alone), so the actor only reads the network response
the page generates on real navigation — block detection is content-based
(checking the rendered page title/body for "estetty"/"blocked"), not just
the HTTP status code, since Cloudflare's deny page itself returns as a
normal-looking response from the browser's point of view.

### Limitations

- **Up to ~100 results per search query.** K-Ruoka's search API returns a
  single batch of up to 100 products per query and no further pagination
  (infinite scroll, a "show more" click, and offset-based navigation were
  all tested — none returned additional results). Run the actor again with
  a different search term for broader catalogue coverage.
- **Store-scoped pricing.** K-Ruoka prices and stock are set per physical
  store. The site resolves a default store automatically (Bright Data's
  Finnish exit currently resolves to store `N106`, K-Citymarket Iso Omena)
  — this isn't user-selectable input today, but every record carries
  `searchStoreId` so you know which store's pricing you're looking at.
- **Per-result cost**: this actor uses a Bright Data Scraping Browser session
  (one browser page load per search query, not per product), so cost is
  dominated by the flat per-run Scraping Browser session rather than scaling
  per item — see [Cost estimate](#cost-estimate).
- Usage cost for a run only settles once the run reaches **SUCCEEDED** —
  a run that fails partway through is not billed for completed items.

### How to use K-Ruoka Scraper

1. Open the **Input** tab and enter a Finnish search term (e.g. `maito`,
   `leipä`, `jauheliha`)
2. Adjust `maxResults` if you want fewer than the default 100
3. Click **Start** and wait for the run to complete (typically under a
   minute)
4. Download results from the **Output** tab in JSON, CSV, Excel, XML, or HTML
5. Schedule recurring runs from the **Schedule** tab for ongoing price
   tracking

### How to scrape K-Ruoka data

This actor automates extracting structured product data from K-Ruoka.fi. Run
it directly from the Apify Console, the Apify API, or any of the official
SDKs (JavaScript, Python). It handles the Cloudflare bypass, network
interception, and retries — you get clean JSON, not raw HTML to parse.

Typical workflows:

- **One-off export**: enter a search term, set `maxResults`, and run
- **Scheduled monitoring**: set a daily cron in the Schedule tab to track
  K-Ruoka prices and promotions over time
- **Programmatic integration**: trigger runs from your backend via the
  Apify API and pull the dataset when finished
- **Webhook automation**: receive a callback the moment a run completes and
  pipe results into Zapier, Make, n8n, BigQuery, or Google Sheets
- **Cross-retailer comparison**: pair with [Prisma Scraper](https://apify.com/studioamba/prisma-scraper)
  to compare K-Ruoka (Kesko) against Prisma (S-Group) — Finland's two
  largest grocery chains

### Why use K-Ruoka Scraper

- **Price monitoring** — track K-Ruoka prices, promotions, and stock at scale
- **Competitive intelligence** — benchmark your catalogue against Kesko's
  pricing and assortment
- **Market research** — analyze category trends and promotional patterns in
  the Finnish grocery market
- **Data feeds** — build product datasets for affiliate sites, price
  comparison tools, or price-tracking apps
- **No login required** — public search data, no account or cookies needed

### Cost estimate

Pricing on the Apify platform is usage-based — you pay for what you actually
run. As a rough guide:

- **A single search (up to 100 products)**: a few cents
- **10 searches (up to ~1,000 products)**: typically under $1 USD

Cost is dominated by the Bright Data Scraping Browser session needed to pass
Cloudflare, not by result count — running more searches per call (higher
`maxResults` within the 100 cap) is more cost-efficient than many small runs.

### Tips for best results

- **Use specific Finnish terms** — generic English queries return few or no
  matches; try the Finnish product/category name
- **Start small** — run with `maxResults: 10` to verify your search term
  before scaling up
- **Combine multiple searches** — since each run covers one query, chain
  several search terms in a Task or via the API for broader catalogue
  coverage
- **Schedule runs** — daily or weekly runs are usually enough for price
  monitoring on a fixed product list

### FAQ and support

**Is it legal to scrape K-Ruoka?** This actor extracts publicly available
product data. Always review the website's Terms of Service before scraping
at scale, and respect rate limits.

**Why am I getting fewer than 100 results?** The search term may simply have
fewer matching products — check `totalHits` isn't the bottleneck by trying a
broader term.

**Can I pick a specific store?** Not directly today — the site resolves a
default store server-side. Every record includes `searchStoreId` so you can
see which store's pricing was returned.

**The actor fails immediately.** This almost always means the preconfigured
Bright Data Scraping Browser endpoint is temporarily unavailable — retry the
run, or supply your own `sbrWsCdp` endpoint in the input.

For issues, feature requests, or bug reports, open a ticket in the Issues tab
on the actor page. We monitor every actor and ship fixes quickly when sites
change.

# Actor input Schema

## `searchQuery` (type: `string`):

Search term to find products on K-Ruoka.fi (e.g. 'kahvi', 'maito', 'leipä'). The site's search API returns up to ~100 products per query — run the actor again with a different term for broader coverage.

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

Maximum number of products to return (capped by what a single search query returns, typically up to 100).

## `sbrWsCdp` (type: `string`):

Bright Data Scraping Browser WebSocket CDP endpoint. Required to bypass K-Ruoka.fi's Cloudflare Bot Management (plain HTTP and Bright Data Web Unlocker both fail here). Can also be set as the SBR\_WS\_CDP environment variable — the actor has one preconfigured.

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

Not used for network requests (the Scraping Browser handles the anti-bot bypass), kept for platform convention.

## Actor input object example

```json
{
  "searchQuery": "kahvi",
  "maxResults": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (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 = {
    "searchQuery": "kahvi",
    "maxResults": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("studio-amba/k-ruoka-fi-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 = {
    "searchQuery": "kahvi",
    "maxResults": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("studio-amba/k-ruoka-fi-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 '{
  "searchQuery": "kahvi",
  "maxResults": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call studio-amba/k-ruoka-fi-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,studio-amba/k-ruoka-fi-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/BhKXRLd4EpAc2ud7s/builds/fxnpMQVkxiZ3PYvdj/openapi.json
