# Tori.fi MCP Server — Finnish Marketplace for AI (`longanimous_bracken/tori-fi-scraper`) Actor

MCP Server for Tori.fi. Search Finnish marketplace. AI-agent ready.

- **URL**: https://apify.com/longanimous\_bracken/tori-fi-scraper.md
- **Developed by:** [petteri mähönen](https://apify.com/longanimous_bracken) (community)
- **Categories:** E-commerce, MCP servers
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.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/platform/actors/running/actors-in-store#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

## Tori.fi Scraper — Finnish Classifieds Marketplace

Scrape listings from [Tori.fi](https://www.tori.fi), Finland's largest classifieds marketplace with millions of active listings across categories (electronics, vehicles, furniture, real estate, and more).

### Features

#### Search & Extraction

- Search by free-text query (Finnish or English keywords)
- Extract **title, price, location, region, image URL, listing URL, item ID** from search results
- 54 listings per page, up to 50 pages per run

#### Filtering

- **Price range** — `priceMin` / `priceMax` in EUR (post-extraction, since Tori.fi strips URL price params)
- **Region filter** — filter by one or more of 19 Finnish regions (e.g., `["Uusimaa", "Pirkanmaa"]`)
- **Sort order** — relevance, newest, cheapest, most expensive

#### Detail Page Extraction

Toggle `fetchDetails: true` to visit each listing's detail page and extract via **JSON-LD structured data**:

- Full description text
- Item condition (new/used/refurbished)
- Brand name
- Category breadcrumb path
- All listing images (array of URLs)
- Published date & last modified timestamp
- Seller type detection

#### Output

- **Dataset** — default, push to Apify Dataset
- **CSV** — `results.csv` in key-value store (RFC 4180 compliant)
- **JSON** — `results.json` in key-value store (pretty-printed)
- **Webhook** — POST all listings to any HTTPS endpoint on completion

#### Anti-Detection

- User-agent rotation (7-browser pool)
- Stealth injection (webdriver flag, Chrome runtime spoof, permissions patch)
- Random delays with jitter between pages and detail visits
- Human-like reading pauses before extraction
- CAPTCHA & block page auto-detection

#### Error Handling

- 404 detection — removed listings flagged as `(Listing removed)`
- Redirect detection — listings redirected to homepage are caught
- Network error classification — timeouts, DNS failures, connection resets logged separately
- goBack fallback — re-navigates to search if browser back fails
- Stale selector warnings — logs page title when cards found but extraction returns 0

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `searchQuery` | string | `"iphone"` | Search term (Finnish or English) |
| `maxPages` | integer | `1` | Pages to scrape (54 listings/page) |
| `priceMin` | integer | — | Minimum price in EUR |
| `priceMax` | integer | — | Maximum price in EUR |
| `regions` | string\[] | `[]` | Filter by Finnish regions |
| `sort` | string | `"relevance"` | `relevance` / `newest` / `cheapest` / `most_expensive` |
| `fetchDetails` | boolean | `false` | Visit detail pages for enriched data |
| `detailDelaySecs` | integer | `2` | Delay between detail page visits |
| `outputFormat` | string | `"dataset"` | `dataset` / `csv` / `json` / `all` |
| `webhookUrl` | string | — | HTTPS URL for POST delivery |

#### Example: Basic search

```json
{
  "searchQuery": "polkupyörä",
  "maxPages": 3
}
```

#### Example: Filtered search with detail extraction

```json
{
  "searchQuery": "MacBook",
  "maxPages": 5,
  "priceMin": 500,
  "priceMax": 1500,
  "regions": ["Uusimaa"],
  "sort": "newest",
  "fetchDetails": true,
  "detailDelaySecs": 3,
  "outputFormat": "all"
}
```

### Output

Each listing contains:

```json
{
  "title": "iPhone 15 Pro 256GB",
  "price": "650 €",
  "price_numeric": 650,
  "location": "Tampere, Pirkanmaa",
  "region": "Pirkanmaa",
  "url": "https://www.tori.fi/item/12345678",
  "image_url": "https://.../photo.jpg",
  "item_id": "12345678",
  "search_query": "iphone",
  "page_number": 1,
  "scraped_at": "2026-07-14T10:00:00.000Z",
  "description": "Myydään iPhone 15 Pro...",
  "condition": "Käytetty",
  "brand": "Apple",
  "category_path": "Matkapuhelimet > Apple iPhone",
  "images": ["https://.../photo1.jpg", "https://.../photo2.jpg"],
  "seller_type": null,
  "published_date": "2026-07-10",
  "last_modified": "2026-07-10"
}
```

Fields marked `null` are populated when `fetchDetails: true`.

### Technical Details

- **Crawler:** PlaywrightCrawler (Crawlee 3.x)
- **Memory:** 4 GB
- **Proxy:** No proxy required (datacenter works)
- **Docker image:** `apify/actor-node-playwright-chrome`
- **Language:** Node.js 22

### Limitations

- Price and region filters are applied **post-extraction** — Tori.fi strips these params from search URLs
- Detail page extraction adds ~2-5 seconds per listing (network + human-like delays)
- Very large searches (50 pages) may trigger rate limiting — use with `fetchDetails: false` for bulk
- Some listings may be removed between search and detail fetch — these are flagged, not lost

### Pricing

**Pay per event** — charged per listing extracted.

### Use Cases

- **Price tracking** — run weekly via cron to monitor market prices for specific items
- **Market research** — analyze supply/demand across Finnish regions
- **Arbitrage** — find undervalued listings by brand or category
- **Lead generation** — scrape dealer listings in specific regions
- **Data pipeline** — feed classifieds data into analytics, ML models, or re-listing tools

### 💬 Quick Start for AI Assistants

Copy and paste this into ChatGPT, Claude, or another AI assistant to get help using this actor:

***

You are helping me use the "Tori.fi Scraper" on Apify (actor ID: FZcMBYjcsJ8er23Ri). It extracts listings from tori.fi, Finland's largest classifieds marketplace.

Input fields:

- searchQuery: string, e.g. "iphone" or "polkupyörä"
- maxPages: number, pages to scrape (54 listings/page, default: 1)
- priceMin, priceMax: number, filter by EUR (default: none)
- regions: string array, e.g. \["Uusimaa", "Pirkanmaa"]
- sort: "relevance" | "newest" | "cheapest" | "most\_expensive"
- fetchDetails: boolean, visit detail pages for JSON-LD enriched data
- detailDelaySecs: number, seconds between detail visits (default: 2)
- outputFormat: "dataset" | "csv" | "json" | "all"
- webhookUrl: string, optional HTTPS URL for POST delivery

Output fields: title, price, price\_numeric, location, region, url, image\_url, item\_id, search\_query, page\_number, scraped\_at + detail fields (description, condition, brand, category\_path, images, seller\_type, published\_date, last\_modified).

## Help me with the right input, output processing, or troubleshooting.

### 📋 Changelog

| Version | Date | Changes |
|---------|------|---------|
| 0.9.0 | 2026-07-15 | Added AI assistant prompt block, updated actor title with keywords |
| 0.8.1 | 2026-07-14 | Added price filtering, sort options, region filtering, detail page extraction via JSON-LD |
| 0.8.0 | 2026-07-13 | Added search query support, region selection, detail page extraction, CSV/JSON output, webhook integration |
| 0.1.0 | 2026-07-12 | Initial release — Tori.fi listing extraction |

# Actor input Schema

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

Search term (e.g. 'iphone', 'huoneisto', 'polkupyörä')

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

Maximum number of search result pages to scrape (54 listings per page). Default: 1.

## `sort` (type: `string`):

How to sort results. Default: relevance.

## `priceMin` (type: `integer`):

Minimum price filter in euros. Listings below this price are excluded. Leave empty for no minimum.

## `priceMax` (type: `integer`):

Maximum price filter in euros. Listings above this price are excluded. Leave empty for no maximum.

## `regions` (type: `array`):

Filter by Finnish regions. Leave empty for all regions. Options: Uusimaa, Pirkanmaa, Varsinais-Suomi, Satakunta, Kanta-Häme, Päijät-Häme, Kymenlaakso, Etelä-Karjala, Etelä-Savo, Pohjois-Savo, Pohjois-Karjala, Keski-Suomi, Etelä-Pohjanmaa, Pohjanmaa, Keski-Pohjanmaa, Pohjois-Pohjanmaa, Lappi, Ahvenanmaa.

## `fetchDetails` (type: `boolean`):

Visit each listing's detail page to extract: description, condition, brand, seller type, images, category path, published date. Slower but richer data.

## `detailDelaySecs` (type: `integer`):

Delay between detail page visits when fetchDetails=true. Helps avoid rate limiting. Default: 2 seconds.

## `outputFormat` (type: `string`):

How to deliver results. 'dataset' = Apify dataset (default). 'csv' = CSV file in key-value store. 'json' = JSON file in key-value store. 'all' = every format.

## `webhookUrl` (type: `string`):

Optional webhook URL to POST results to on completion. Sends all listings as JSON payload. Leave empty to skip.

## Actor input object example

```json
{
  "searchQuery": "iphone",
  "maxPages": 1,
  "sort": "relevance",
  "regions": [],
  "fetchDetails": false,
  "detailDelaySecs": 2,
  "outputFormat": "dataset"
}
```

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("longanimous_bracken/tori-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("longanimous_bracken/tori-fi-scraper").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{}' |
apify call longanimous_bracken/tori-fi-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=longanimous_bracken/tori-fi-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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