Spanish Port Traffic Statistics Scraper
Pricing
from $1.50 / 1,000 result delivereds
Spanish Port Traffic Statistics Scraper
Monthly Spanish port traffic statistics from Puertos del Estado as JSON: tonnes by cargo type, TEU, passengers and cruise calls, one item per port section and concept. Select a month or get the latest. Repeat runs of the same month emit zero records and charge nothing beyond the start event.
Pricing
from $1.50 / 1,000 result delivereds
Rating
0.0
(0)
Developer
Steadfetch
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
20 hours ago
Last modified
Security
Limited permissionsShare
Spanish Port Traffic Scraper (Puertos del Estado Monthly Data)
Extract the official monthly Spanish port traffic summary published by Puertos del Estado (Ministry of Transport). Select a month and get one structured record per traffic concept: tonnes by cargo type, TEU, passengers, cruise calls, vessels, gross tonnage and vehicles. No browser, no login, no CAPTCHA.
What it does
Puertos del Estado publishes a monthly workbook, CuadrosResumen_YYYY_MM.xlsx, linked from its public statistics listing. The Actor reads that listing, downloads the workbook for the requested month (or the latest published month) and extracts the RESUMEN DEL TRAFICO PORTUARIO (accented in the workbook) table from the Resumen general sheet.
- Official source only: the workbook linked from
puertos.es/datos/estadisticas/mensuales. The Actor never uses third-party copies or scrapes the HTML tables. - One table in scope (v1): the summary table with the
CONCEPTO | <month> | Acumulado desde Enero | Variacionheader. Extraction stops at the first row whose first column is only a numeric code, so the second table in the sheet is never emitted. - Hierarchy preserved:
sectionis column A filled down;conceptis column B and is inherited when B is empty (for exampleEn contenedoresunderMercancia general);breakdownis column C. - Values as published: tonnage rows carry
unit: "tonnes". Traffic rows that count other things keep their own unit (TEU,persons,units,vehicles,vessels,GT,cruiseCalls) and are never converted. - Monitor deduplication: delivered
(period, section, concept, breakdown)keys are stored in a named key-value store (puertos-estado-trafico-state), which persists between runs. A repeated run for the same month emits zero records and charges nothing beyond the run start. - Schema validation: every record is validated against
.actor/dataset_schema.jsonbefore it reaches the dataset. An invalid record fails the run instead of pushing malformed data. - Scope guard: robots.txt is checked on every run. Only the listing and the matching
file-downloadURL are requested. If the download redirects to a login page or returns a non-XLSX content type, the run aborts.
Data and personal information
The source is a public statistics workbook from a Spanish public administration. It contains aggregated port traffic figures only: no personal data is read, derived or added.
The Actor identifies itself with an honest User-Agent (steadfetch-puertos-estado-trafico-extractor/0.1) and makes at most three requests per run after the robots.txt check: the listing page and the XLSX file.
Example output
{"url": "https://www.puertos.es/file-download/download/private/6200","section": "MERCANC\u00cdAS SEG\u00daN SU PRESENTACI\u00d3N","concept": "Graneles l\u00edquidos","breakdown": "Total","unit": "tonnes","period": "2026-08","monthValue": 16078357,"monthValuePrevYear": 14913586,"accumValue": 123171330,"accumValuePrevYear": 118486641,"variationAbs": 4684689,"variationPct": 3.95,"publishedAt": "2026-09-22","sourceUrl": "https://www.puertos.es/file-download/download/private/6200","scrapedAt": "2026-10-21T03:00:00.000Z"}
The Unicode escapes in this ASCII README decode to the exact accented Spanish labels published in the workbook. url and sourceUrl point at the official XLSX file. variationPct is rounded to two decimals. Missing source numbers are returned as null; no value is estimated or converted.
A full August 2026 run returns 28 records, from cargo categories to Pasajeros (n) and Automoviles (uds.).
Input
{"period": "2026-08"}
| Field | Type | Description |
|---|---|---|
period | string | Month to extract in YYYY-MM format. Optional: when omitted, the Actor uses the latest CuadrosResumen_YYYY_MM.xlsx in the listing. |
The period is cross-checked against the month label inside the workbook; a mismatch fails the run instead of labelling data with the wrong month.
Project structure
.actor/actor.json Actor definition (payPerEvent, dataset schema, output schema).actor/input_schema.json INPUT options (period).actor/dataset_schema.json Item JSON Schema (fields) + table view for the Store.actor/pay_per_event.json PPE pricing (two synthetic platform events)src/puertos-client.ts Listing fetch/parse, period selection, guarded XLSX downloadsrc/robots.ts robots.txt parser and per-run allow checksrc/workbook.ts exceljs XLSX reader (isolated from pure extraction)src/extract.ts Pure table extraction, hierarchy, units, cutoff (fixture-tested)src/dedupe.ts Pure cross-run dedupe state for the key-value storesrc/validate-output.ts Ajv validator shared by the run and the testssrc/main.ts Entry point: robots, listing, download, parse, validate, pushtest/ Offline Vitest suites and the real XLSX/listing fixtures
Pay-per-event pricing
The Actor uses Apify pay-per-event pricing with the two synthetic platform events:
| Event | Charged when | Price |
|---|---|---|
apify-actor-start | a run starts (synthetic, automatic) | 0.00005 USD per run (Apify-recommended default; includes 5 seconds of free compute) |
apify-default-dataset-item | a new, validated concept-period record is saved to the dataset (synthetic, automatic) | 0.002 USD per record = 2.00 USD per 1,000 results |
There is no custom per-item event and no Actor.charge() call: the platform already charges apify-default-dataset-item per pushed item, so adding another event would bill users twice. Listing and XLSX downloads, rows outside the v1 table, failed runs and records already delivered for the same period are not charged.
The 2.00 USD per 1,000 price follows the direct reference band for port and cruise data on the Apify Store (1.50-3.99 USD per 1,000) and stays inside Steadfetch's 1-10 USD band.
Cost per 1,000 results
Measured locally on 2026-10-21 with the real August 2026 workbook fixture, without any platform run:
- XLSX size: 1,225,444 bytes, downloaded in one request.
- Parse plus extraction: 0.59 s total (0.59 s exceljs parse, 0.002 s pure extraction), 28 records emitted.
- Peak resident memory: 165 MB, inside the 256 MB container limit.
The run-start event includes the first 5 seconds of compute. Even with a conservative bound of 10 seconds at 256 MB, the platform compute is a fraction of a cent; total cost is below 0.01 USD per 1,000 records. With 28 records per month, a month costs about 0.06 USD in result events at the 2.00 USD per 1,000 price. These are measured local figures, not a platform bill: no production run has been made before publication and the first approved smoke run will confirm them.
Development
npm installnpm test # lint + typecheck + offline Vitest suites (no credit, no APIFY_TOKEN)npm run publish:check # Steadfetch publish gatenpm run schemas:validate # apify-cli schema validation
The default test suite runs entirely offline against the committed 1.2 MB XLSX and listing fixtures and never touches the Apify platform. It covers the real workbook values, the spec example row, section/concept hierarchy, per-family units, the numeric-code cutoff, Spanish number formats, month mismatch handling, listing parsing, download redirect/content-type guards, robots.txt decisions, dedupe state and output-schema validation.
FAQ
Does the Actor need a login or an API key?
No. The listing and the XLSX download are public and anonymous. The private segment in the file-download path is the site's route name, not an authentication wall; the Actor still aborts if that route starts redirecting to a login page.
Does it need a browser? No. The source is a server-rendered listing plus an XLSX file, so plain HTTP and an XLSX parser are far cheaper and faster than a browser.
Which months are available?
Every month published in the listing, currently January to August 2026. Ask for a period that is not published and the run fails with the list of available periods.
Why do some records say unit: "TEU" or "persons" instead of tonnes?
The summary mixes cargo weights with other traffic measures. The Actor labels each family with its own unit and never converts values. GT is gross tonnage, cruiseCalls is the number of cruise calls and units covers UTIS.
How does deduplication work?
The Actor stores the (period, section, concept, breakdown) key of every delivered record in the named key-value store puertos-estado-trafico-state, which persists between runs. The default store is per run and is purged on start, so it cannot hold monitor state. A second run for the same month finds those keys and pushes nothing, so it is not charged per record. A new month starts with an empty set.
When am I charged?
Once per run start (apify-actor-start) and once per new record saved to the dataset (apify-default-dataset-item). Failed or aborted runs, rows outside the v1 table and monitor duplicates are not charged.
Is the output schema stable?
Yes. New fields are additive and the required fields in .actor/dataset_schema.json do not change.
Does it return personal data? No. The workbook contains aggregated port traffic statistics only, with no names, contact details or personal identifiers.
Maintenance
This Actor is tested daily: automated runs check that the official Puertos del Estado listing and workbook are reachable and that the output still matches the published schema. Failures are reported as issues in the Steadfetch Actors repository. The table layout is validated on every run: if the summary header, publication date or data rows cannot be found, the run fails loudly instead of returning an empty dataset.