# Eurostat Scraper: EU Statistics & Economic Series (`arman-bd/eurostat-scraper`) Actor

Extract Eurostat datasets: GDP, employment, trade, energy, demographics and prices across EU member states, with full dimension metadata. JSON-stat parsed into flat rows.

- **URL**: https://apify.com/arman-bd/eurostat-scraper.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** Business, Developer tools, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.56 / 1,000 series 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?

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

## Eurostat Scraper: EU Statistics & Economic Series

![Eurostat Scraper: GDP, employment, trade and prices unflattened from Eurostat's JSON-stat cubes into one labelled row per observation](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/eurostat-scraper.jpg)

**Eurostat Scraper** extracts official European statistics, GDP, employment, trade, energy, demographics, prices, straight from the Eurostat dissemination API, and turns the raw **JSON-stat 2.0** hypercube into flat, labelled rows you can put in a spreadsheet.

Eurostat does not publish rows. It publishes a cube: a flat map of numbers keyed by a single linear index, plus a separate block describing the axes. Nothing in the payload pairs a number with the country and year it belongs to. This Actor does that reconstruction for you, correctly, and hands back one record per observation with every dimension resolved to both its code and its human-readable label.

**Agent skill: [SKILL.md](https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/eurostat-scraper.md)**

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/eurostat-scraper.md
```

### What you get

| Output field | Meaning |
|---|---|
| `dataset` | Eurostat dataset code, e.g. `nama_10_gdp` |
| `datasetLabel` | Full dataset title in your chosen language |
| `geo`, `geoLabel` | Territory code and its name, `DE` / `Germany`, `EU27_2020` / `European Union - 27 countries (from 2020)` |
| `time` | Period as Eurostat writes it: `2023`, `2023-Q1`, `2023-01` |
| `value` | The observation itself, as a number |
| `unit` | Unit of measure label, e.g. `Current prices, million euro` |
| `dimensions` | **Every** dimension of the cube, each with `code`, `label` and `dimensionLabel`, nothing from the source is dropped |
| `flags` | Observation status marks, decoded, `p` → `provisional`, `e` → `estimated`, `b` → `break in time series` |
| `lastUpdate` | When Eurostat last refreshed the dataset |
| `scrapedAt` | Run timestamp |

A `RUN_SUMMARY` record in the key-value store holds per-dataset counts, the cube shape, the filters used, and any dataset that failed.

### Common use cases

- **EU market analysis and country comparison.** Pull one indicator across every member state on one time base.

```json
{
"datasets": ["nama_10_gdp"],
"geo": ["DE", "FR", "IT", "ES", "PL", "NL"],
"timeFrom": "2015",
"filters": { "unit": "CP_MEUR", "na_item": "B1GQ" }
}
```

- **Policy and economic research.** Unemployment and inflation on a common panel, ready for regression.

```json
{
"datasets": ["une_rt_a", "prc_hicp_aind"],
"geo": ["EU27_2020", "DE", "FR", "IT"],
"timeFrom": "2010"
}
```

- **Feed a European business dashboard.** A small, cheap nightly refresh of headline population figures.

```json
{
"datasets": ["tps00001"],
"geo": ["EU27_2020", "DE", "FR", "IT", "ES"],
"timeFrom": "2020"
}
```

### Quick start

Headline euro-area GDP, current prices, since 2018:

```json
{
 "datasets": ["nama_10_gdp"],
 "geo": ["EU27_2020", "DE", "FR", "IT", "ES"],
 "timeFrom": "2018",
 "filters": { "unit": "CP_MEUR", "na_item": "B1GQ" }
}
```

Two units side by side, so you can see nominal and real growth together:

```json
{
 "datasets": ["nama_10_gdp"],
 "geo": ["DE", "FR", "IT"],
 "timeFrom": "2020",
 "timeTo": "2024",
 "filters": { "unit": ["CP_MEUR", "CLV10_MEUR"], "na_item": "B1GQ" }
}
```

Exploring a dataset you do not know yet, grab a capped slice and read the `dimensions` block to learn its axes:

```json
{
 "datasets": ["une_rt_a"],
 "geo": ["DE"],
 "timeFrom": "2023",
 "maxSeriesPerDataset": 50
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `datasets` | array | - | **Required.** Dataset codes such as `nama_10_gdp`. Data Browser URLs are accepted and reduced to the code. |
| `geo` | array | `[]` | Territory codes: countries (`DE`), aggregates (`EU27_2020`, `EA20`), NUTS regions (`DE21`). Empty = every territory in the dataset. |
| `timeFrom` | string | `""` | Oldest period, in the dataset's own format (`2015`, `2015-Q1`, `2015-01`). Empty = full history. |
| `timeTo` | string | `""` | Newest period, same format. Empty = up to the latest release. |
| `filters` | object | `{}` | Any other dimension, as `{"unit": "CP_MEUR", "na_item": "B1GQ"}`. Values may be a single code or an array. |
| `maxSeriesPerDataset` | integer | `0` | Cap observations saved per dataset. `0` = no limit. |
| `language` | string | `EN` | Label language: `EN`, `FR` or `DE`. Codes are unaffected. |

**Which combinations make sense.** `filters` is applied to *every* dataset in the run, so batch datasets that share a dimension vocabulary. `nama_10_gdp` and `nama_10_pc` both understand `unit` and `na_item`; `tps00001` does not define `unit` at all and will be rejected with `INVALID_QUERY_DIMENSION` if you send it. That failure is recorded per dataset and the rest of the run continues, but if your datasets do not share dimensions, give each one its own run.

`geo`, `timeFrom`/`timeTo` and `filters` all apply server-side, so every filter you add makes the download smaller and the run faster. Filtering matters more here than in most APIs: unfiltered, `nama_10_gdp` returns 1,093,133 observations in an 18 MB response. Adding `unit` and `na_item`, the two dimensions that dataset multiplies out most, usually cuts it by two orders of magnitude. If you do not know a dataset's dimensions yet, run it once with `maxSeriesPerDataset: 50` and read them off the `dimensions` field.

### Output example

A real record, GDP for Germany in 2023:

```json
{
 "dataset": "nama_10_gdp",
 "datasetLabel": "Gross domestic product (GDP) and main components (output, expenditure and income) - annual data",
 "geo": "DE",
 "geoLabel": "Germany",
 "time": "2023",
 "value": 4219310,
 "unit": "Current prices, million euro",
 "dimensions": {
 "freq": { "code": "A", "label": "Annual", "dimensionLabel": "Time frequency" },
 "unit": { "code": "CP_MEUR", "label": "Current prices, million euro", "dimensionLabel": "Unit of measure" },
 "na_item": { "code": "B1GQ", "label": "Gross domestic product at market prices","dimensionLabel": "National accounts indicator (ESA 2010)" },
 "geo": { "code": "DE", "label": "Germany", "dimensionLabel": "Geopolitical entity (reporting)" },
 "time": { "code": "2023", "label": "2023", "dimensionLabel": "Time" }
 },
 "flags": [{ "code": "p", "label": "provisional" }],
 "lastUpdate": "2026-08-04T23:00:00+0200",
 "scrapedAt": "2026-08-06T11:39:16.686Z"
}
```

`flags` is an array because Eurostat concatenates status marks, a `bp` cell is a break in the series *and* provisional, and both are decoded separately.

`RUN_SUMMARY` looks like this:

```json
{
 "datasetsRequested": 2,
 "datasetsFailed": 0,
 "failures": [],
 "seriesSaved": 240,
 "perDataset": [
 { "dataset": "nama_10_gdp", "cells": 240, "observations": 236, "saved": 236, "capped": false }
 ],
 "filters": {
 "geo": ["DE", "FR"],
 "timeFrom": "2018",
 "timeTo": "",
 "dimensions": { "unit": "CP_MEUR", "na_item": "B1GQ" },
 "maxSeriesPerDataset": 0,
 "language": "EN"
 },
 "finishedAt": "2026-08-06T11:39:16.686Z"
}
```

`cells` is the size of the cube, `observations` the number that actually carry data. The gap is normal, Eurostat cubes are sparse, and a country that did not report a given year simply has no cell.

### Finding a dataset code

Open the [Eurostat Data Browser](https://ec.europa.eu/eurostat/databrowser/) and pick any table. The code is the last path segment of the URL:

| URL you see | Code |
|---|---|
| `…/databrowser/view/NAMA_10_GDP` | `nama_10_gdp` |
| `…/databrowser/view/tps00001/default/table` | `tps00001` |
| `…/databrowser/view/une_rt_a` | `une_rt_a` |

You can paste the whole URL, the Actor extracts the code itself. The same page lists every dimension of the dataset and the codes each accepts; those are what go in `filters`.

Useful starting points: `nama_10_gdp` (GDP and main components), `tps00001` (population on 1 January), `une_rt_a` (annual unemployment rate), `prc_hicp_aind` (harmonised inflation), `nrg_bal_c` (energy balances), `ext_lt_intratrd` (intra-EU trade).

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~eurostat-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "datasets": ["nama_10_gdp"],
 "geo": ["EU27_2020", "DE", "FR"],
 "timeFrom": "2020",
 "filters": { "unit": "CP_MEUR", "na_item": "B1GQ" }
 }'
```

### JavaScript example

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/eurostat-scraper').call({
 datasets: ['nama_10_gdp'],
 geo: ['DE', 'FR', 'IT'],
 timeFrom: '2020',
 filters: { unit: 'CP_MEUR', na_item: 'B1GQ' },
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const row of items) console.log(`${row.geoLabel} ${row.time}: ${row.value} (${row.unit})`);
```

### Limits and behaviour

- **JSON-stat is dimension-indexed, not tabular.** The whole point of this Actor is the unflattening: `value` is a flat map keyed by a linear index into a cube whose axes are listed in `id` with sizes in `size`, last axis varying fastest. The decoder in `src/jsonstat.js` reverses that arithmetic and asserts the cube's shape before it starts, because a wrong stride order does not throw, it silently attaches every number to the wrong country and year.
- **Sparse cubes are normal.** A cube of 54 territories may carry 46 values. Missing cells are skipped, never emitted as zero or null.
- **Unfiltered queries are big.** `nama_10_gdp` with no filters is 18 MB and over a million observations. The Actor logs a warning when you run with no filters at all, and `maxSeriesPerDataset` exists as a hard stop.
- **A bad dataset code does not kill the run.** Eurostat replies HTTP 404 with an explanatory `error.label`, which is recorded verbatim in `RUN_SUMMARY.failures`; the Actor only errors out if *every* dataset fails.
- **Invalid dimension names are fatal per dataset, not per run.** Passing a dimension the dataset does not define returns HTTP 400 `INVALID_QUERY_DIMENSION`, recorded the same way.
- **Transient errors are retried.** 429 and 5xx get three attempts with linear backoff. 404 and 400 are not retried, because they will never succeed.

### FAQ

**Do I need a proxy?** No. Proxy configuration is not required to run this Actor.

**Do I need a Eurostat account or API key?** No. You supply no credentials.

**What happens if one dataset is unavailable?** It is logged, added to `RUN_SUMMARY.failures`, and the run continues with the rest.

**Why do I get fewer rows than `cells` in the summary?** Eurostat cubes are sparse. `cells` counts every possible combination of dimensions; `observations` counts the ones with a published figure.

**How do I know which `filters` a dataset accepts?** Run it once with `maxSeriesPerDataset: 50` and read the `dimensions` object on any returned row, it names every axis and shows an example code for each. The Data Browser page lists the full code lists.

**What do the flags mean?** They are Eurostat's observation status marks. The most common are `p` (provisional), `e` (estimated), `b` (break in time series), `d` (definition differs), `u` (low reliability). The Actor decodes each one to its label as published with the dataset.

**Can I get quarterly or monthly data?** Yes, pick a dataset that publishes at that frequency and use its period format in `timeFrom`/`timeTo` (`2023-Q1`, `2023-01`). The `freq` dimension in the output tells you what you got.

**Can I schedule it?** Yes. Eurostat publishes on a release calendar, and `lastUpdate` on every row lets you skip runs where nothing changed.

**Can I integrate it with something else?** Yes, Apify API, client libraries, webhooks, scheduled runs, dataset exports (JSON/CSV/Excel) or MCP. Output is structured JSON.

# Actor input Schema

## `datasets` (type: `array`):

Dataset codes as they appear in the Eurostat Data Browser URL. for https://ec.europa.eu/eurostat/databrowser/view/NAMA_10_GDP the code is 'nama_10_gdp'. Full Data Browser URLs are accepted and reduced to the code automatically.

## `geo` (type: `array`):

Eurostat geo codes to keep: two-letter country codes (DE, FR, IT), aggregates (EU27_2020, EA20) or NUTS regions (DE21). Leave empty to get every territory the dataset covers. which for a country-by-country dataset can be 50+ series per period.

## `timeFrom` (type: `string`):

Oldest time period to include, in the dataset's own period format. '2015' for annual data, '2015-Q1' for quarterly, '2015-01' for monthly. Leave empty for the full published history, which often reaches back to the 1970s.

## `timeTo` (type: `string`):

Newest time period to include, same format as the earliest period. Leave empty to run through to the most recent release.

## `filters` (type: `object`):

Extra dimension filters as a code-to-value map. Dimension names and their allowed codes are listed under each dataset in the Data Browser. for GDP, 'unit': 'CP_MEUR' is current prices in million euro and 'na_item': 'B1GQ' is gross domestic product. Values may be a single code or an array of codes. Omit this and Eurostat returns every combination.

## `maxSeriesPerDataset` (type: `integer`):

Cap the observations saved from each dataset. Useful as a safety net. an unfiltered dataset such as nama_10_gdp holds over a million observations. Set 0 for no limit.

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

Language for dataset titles and dimension labels. Codes themselves are language-independent, so only the human-readable text changes.

## Actor input object example

```json
{
  "datasets": [
    "nama_10_gdp",
    "tps00001",
    "une_rt_a"
  ],
  "geo": [
    "EU27_2020",
    "DE",
    "PL"
  ],
  "timeFrom": "2015",
  "timeTo": "2024",
  "filters": {
    "unit": [
      "CP_MEUR",
      "CLV10_MEUR"
    ],
    "na_item": "B1GQ"
  },
  "maxSeriesPerDataset": 0,
  "language": "EN"
}
```

# Actor output Schema

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

Every record the run produced.

## `runsummary` (type: `string`):

The RUN_SUMMARY record from the run's key-value store.

# 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 = {
    "datasets": [
        "nama_10_gdp"
    ],
    "geo": [
        "EU27_2020",
        "DE",
        "FR",
        "IT",
        "ES"
    ],
    "timeFrom": "2018",
    "filters": {
        "unit": "CP_MEUR",
        "na_item": "B1GQ"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/eurostat-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 = {
    "datasets": ["nama_10_gdp"],
    "geo": [
        "EU27_2020",
        "DE",
        "FR",
        "IT",
        "ES",
    ],
    "timeFrom": "2018",
    "filters": {
        "unit": "CP_MEUR",
        "na_item": "B1GQ",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/eurostat-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 '{
  "datasets": [
    "nama_10_gdp"
  ],
  "geo": [
    "EU27_2020",
    "DE",
    "FR",
    "IT",
    "ES"
  ],
  "timeFrom": "2018",
  "filters": {
    "unit": "CP_MEUR",
    "na_item": "B1GQ"
  }
}' |
apify call arman-bd/eurostat-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/eurostat-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/twZnUreHvNObskX0c/builds/DgvYCaTGfIksuzoQj/openapi.json
