Hong Kong Port Throughput Scraper: Container & Cargo
Pricing
from $1.20 / 1,000 throughput observation scrapeds
Hong Kong Port Throughput Scraper: Container & Cargo
Scrape Hong Kong's official monthly port container throughput, split by Kwai Tsing terminals vs mid-stream, direction and laden/empty, plus the companion seaborne and river cargo tonnage series.
Pricing
from $1.20 / 1,000 throughput observation scrapeds
Rating
0.0
(0)
Developer
Arman Hossain
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Hong Kong Port Throughput Scraper: Container, Seaborne & River Cargo

Pulls Hong Kong's official monthly port statistics: container throughput split by Kwai Tsing terminals vs mid-stream handling, inward vs outward, and laden vs empty, plus the companion seaborne and river cargo tonnage series. The source publishes each level figure alongside its own year-on-year percentage change as two separate rows; this Actor pivots them back together so one dataset row carries both the physical figure and its year-on-year move.
The source is the Census and Statistics Department of the Hong Kong SAR, published as structured JSON. There's no browser, no proxy and no login required to use this Actor.
Agent skill: SKILL.md
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/hong-kong-port-throughput-scraper.md
What you get
| Field | What it holds |
|---|---|
tableId | The source table id, e.g. 410-55294 |
series | container-throughput, seaborne-cargo, river-cargo, or a title-derived label for any other table id you pass in |
tableTitle | The table's own title, in the language you requested |
measureCode | The source's internal measure code (e.g. PORT_CONTAINER_TP) |
locationCode, location | Kwai Tsing Container Terminals vs "other than Kwai Tsing" (mid-stream), or null for the cargo tables, which don't carry this split |
cargoModeCode, cargoMode | Seaborne or River, or null for the container table, which doesn't carry this split |
directionCode, direction | Inward, outward, or the aggregate total |
containerTypeCode, containerType | Laden, empty, or the aggregate total (container table only, null elsewhere) |
shipmentTypeCode, shipmentType | Direct shipment, transhipment, or the aggregate total (cargo tables only, null elsewhere) |
freq | M (monthly) or Y (annual) |
period | YYYYMM for a monthly row, YYYY for an annual row |
unit | The physical unit of figure, e.g. ('000 TEUs) or ('000 tonnes) |
figure | The level value, in the unit above |
figureFlag | The source's own flag on the level figure: p (provisional), [§3] (rounds to a negligible change) or N.A. |
yoyChangePct | The year-on-year percentage change for the same dimension combination and period |
yoyChangeFlag | The source's flag on the year-on-year figure, same vocabulary as figureFlag |
provisional | true if either the level or the year-on-year figure is still provisional |
scrapedAt | Run timestamp |
RUN_SUMMARY in the key-value store holds per-table counts, the filters you used, each table's own title/source/footnotes, and any table id that failed.
Use cases
- Kwai Tsing vs mid-stream tracking. Watch how much of Hong Kong's container volume is handled at the main Kwai Tsing terminals versus mid-stream and river operations, a split most free trade-statistics sources don't break out.
- Port competitiveness modelling. Feed monthly TEU throughput and its year-on-year move into a model tracking Hong Kong against Shenzhen and Guangzhou.
- Laden/empty balance. Compare laden vs empty container flows by direction to read import/export imbalance.
- Cargo mode mix. Compare the seaborne and river cargo tonnage series to see how much of total throughput moves by river trade.
- Provisional-to-final tracking. Re-run monthly and diff on
figureFlag/yoyChangeFlagto see when a provisional figure firms up.
Quick start
The default run, monthly data across all three built-in tables, capped at 150 rows per table:
{}
Just container throughput, a wider monthly window:
{"tableIds": ["410-55294"],"frequency": "M","maxRecordsPerTable": 500}
Annual container figures for a specific stretch of years:
{"tableIds": ["410-55294"],"frequency": "Y","periodFrom": "2015","periodTo": "2025"}
Input
| Field | Type | Default | Notes |
|---|---|---|---|
tableIds | array | all three built-in ids | 410-55294 (container throughput), 410-55111A (seaborne cargo) and 410-55112A (river cargo) are documented by name. Any other table id published in the same id=NNN-NNNNN shape is still read, with reduced dimension detail if it uses dimension names this Actor doesn't already recognise. |
frequency | string | M | M for monthly, Y for annual, ALL for both. |
periodFrom | string | "" | Earliest period to keep, as YYYY-MM, YYYYMM or YYYY. Applied after fetching. Empty means the full history. |
periodTo | string | "" | Latest period to keep, same format. Empty means the most recent published period. |
maxRecordsPerTable | integer | 150 | Cap on saved rows per table, most recent periods first. The source has no server-side pagination or date filter, so every run downloads each table's complete history regardless of this setting; the cap only bounds what gets saved and charged. |
lang | string | en | en, tc (Traditional Chinese) or sc (Simplified Chinese). Only affects tableTitle and the location/cargoMode/direction/containerType/shipmentType labels; the *Code fields stay the same in every language. |
Nothing is required. Running with no input returns recent monthly data across all three built-in tables.
Output example
A record from 410-55294, showing the Kwai Tsing vs mid-stream split:
{"tableId": "410-55294","series": "container-throughput","tableTitle": "Port container throughput by main cargo handling location","measureCode": "PORT_CONTAINER_TP","locationCode": "NCT","location": "Other than Kwai Tsing Container Terminals","cargoModeCode": null,"cargoMode": null,"directionCode": "Out","direction": "Outward","containerTypeCode": "Laden","containerType": "Laden","shipmentTypeCode": null,"shipmentType": null,"freq": "M","period": "202605","unit": "('000 TEUs)","figure": 98,"figureFlag": "p","yoyChangePct": -4.4,"yoyChangeFlag": "p","provisional": true,"scrapedAt": "2026-08-21T22:49:51.330Z"}
A record from 410-55111A, showing the cargo-mode dimension the container table doesn't have:
{"tableId": "410-55111A","series": "seaborne-cargo","tableTitle": "Seaborne cargo throughput by month","cargoModeCode": "S","cargoMode": "Seaborne","locationCode": null,"location": null,"shipmentTypeCode": "TS","shipmentType": "Transhipment","freq": "M","period": "202605","unit": "('000 tonnes)"}
Table ids and what they return
Each id maps to one JSON table on the source, keyed by dimension code plus a description pair, a frequency, a period and two rows per combination: a level figure and a year-on-year percentage change, which this Actor merges into one output row.
| Table id | Series | Dimensions besides direction/period |
|---|---|---|
410-55294 | container-throughput | Location (Kwai Tsing terminals vs mid-stream), container type (laden/empty) |
410-55111A | seaborne-cargo | Shipment type (direct shipment/transhipment) |
410-55112A | river-cargo | Shipment type (direct shipment/transhipment) |
API example
curl -X POST "https://api.apify.com/v2/acts/arman-bd~hong-kong-port-throughput-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"tableIds": ["410-55294"],"frequency": "M","maxRecordsPerTable": 200}'
JavaScript example
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_TOKEN' });const run = await client.actor('arman-bd/hong-kong-port-throughput-scraper').call({tableIds: ['410-55294', '410-55111A', '410-55112A'],frequency: 'M',maxRecordsPerTable: 300,});const { items } = await client.dataset(run.defaultDatasetId).listItems();for (const r of items) {console.log(`${r.period} ${r.series} ${r.location ?? r.cargoMode ?? 'total'}: ${r.figure} ${r.unit} (${r.yoyChangePct}% YoY)`);}
Notes
- The source has no date-range or pagination parameter: every run downloads each requested table's complete history (roughly 5-7 MB per table), and
frequency,periodFrom,periodToandmaxRecordsPerTableall filter after the fact. The download size per table is fixed regardless of how narrow these filters are. - A level figure and its year-on-year change arrive from the source as two separate rows sharing the same dimensions, frequency and period. This Actor pairs them into one output row. Very early periods can be missing one side (year-on-year needs a prior-year baseline that doesn't exist for the first 12 months of a series), in which case the missing side is
null. figureFlag/yoyChangeFlagcarry the source's own footnote codes verbatim:pfor provisional,[§3]for a change under 0.05%,N.A.for not available.provisionalistruewhenever either flag containsp.locationandcargoModeare mutually exclusive across the three built-in tables: the container table has a location split and no cargo-mode split, the cargo tables have a cargo-mode split and no location split. Every record still carries both fields,nullon whichever doesn't apply, so combining tables in one run keeps a single dataset shape.- One bad table id is recorded in
RUN_SUMMARY.failureswhile the rest of the run continues. The run only errors out if every requested table id failed. - Requests are retried up to three times with linear backoff on a transient error. A table id or language code the source rejects is a permanent failure for that table and is not retried.
FAQ
Do I need an account with the source? No. You supply no credentials.
Can I get quarterly figures? No. The source publishes monthly and annual observations only; frequency accepts M, Y or ALL.
Why is maxRecordsPerTable needed if I only want recent months? The source's API always returns a table's entire history in one response; there is no way to ask it for less. maxRecordsPerTable (and periodFrom/periodTo) filter what gets saved, not what gets fetched.
Can I read a table id this Actor doesn't already know about? Yes, pass it in tableIds. It's read through the same endpoint and pivoted the same way; you'll get generic series/dimension labelling if it uses dimension names outside the ones this Actor maps by name.
How current is the data? Monthly figures typically land two to three months after the reference month, and the latest month or two is marked provisional (figureFlag/yoyChangeFlag of p) until the source finalises it.
Can I plug it into something else? Yes. Apify API, the client libraries, webhooks, scheduled runs, dataset exports to JSON, CSV or Excel, or MCP. The output is structured JSON.