--- name: alphaliner-carrier-fleet-scraper description: Read the Alphaliner Top 100 container-carrier table as structured records via the Apify Actor arman-bd/alphaliner-carrier-fleet-scraper. Returns one record per carrier (up to 100, ranked by operated TEU) with owned vs chartered TEU/ship split, orderbook size and share, global market share, every vessel's IMO number in the operated fleet, and global cellular-fleet totals. Use for carrier fleet-strategy tracking, orderbook/supply analysis, market concentration studies, and building an IMO-to-operator master mapping. Not for historical time series (each run is a current snapshot), per-vessel charter status, or carriers outside the global Top 100. --- # Alphaliner Carrier Fleet Scraper Apify Actor `arman-bd/alphaliner-carrier-fleet-scraper`. One call reads the entire Alphaliner Top 100 operated-fleet table: no pagination, no per-carrier requests. It runs without credentials, and with no input at all it returns all 100 carriers, fully populated including every vessel IMO number. ## When to use it - Carrier fleet-strategy tracking: compare `ownedTeu` vs `charteredTeu` across successive weekly runs to see who is buying tonnage vs leasing it. - Orderbook and supply-side analysis: `orderbookTeu` and `orderbookSharePercent` show which operators are adding the most future capacity relative to today's fleet. - Market concentration studies: `marketSharePercent` across all 100 rows gives a full picture of global container-shipping concentration. - Building a carrier master-data table or an IMO-to-operator join key: `vesselImos` covers roughly 7,600 cellular ships across the Top 100. - A quick "how big is carrier X" lookup via the `carriers` name filter. ## When not to use it - Historical time series. Each run is a current snapshot; there is no date-range parameter. Build history yourself by running on a schedule and keying on `scrapedAt`. - Per-vessel charter status (owned vs chartered per ship). The split is only available aggregated as TEU/ship counts, not attached to individual IMOs in `vesselImos`. - Carriers outside the global Top 100 (the source table has exactly 100 rows). - Vessel-level detail beyond the IMO number itself (no vessel name, capacity, age, flag or class here: join `vesselImos` against an AIS or vessel-registry source for that). ## Call it ```js import { ApifyClient } from 'apify-client'; const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); const run = await client.actor('arman-bd/alphaliner-carrier-fleet-scraper').call({ carriers: [], minTotalTeu: 0, maxCarriers: 100, includeVesselImos: true, includeGlobalTotals: true, }); const { items } = await client.dataset(run.defaultDatasetId).listItems(); const { value: summary } = await client .keyValueStore(run.defaultKeyValueStoreId) .getRecord('RUN_SUMMARY'); ``` One-shot over HTTP, when you want the rows back in the same request: ```bash curl -X POST "https://api.apify.com/v2/acts/arman-bd~alphaliner-carrier-fleet-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"minTotalTeu":500000,"maxCarriers":30}' ``` The Actor is also exposed through Apify's MCP server as `arman-bd/alphaliner-carrier-fleet-scraper`, so an MCP-capable agent can call it with no extra wiring. ## Input | Field | Type | Required | Default | Notes | |---|---|---|---|---| | `carriers` | string[] | no | `[]` | Keep only carriers whose `operator` or `carrierName` contains one of these terms, case-insensitive substring match. Empty means all 100. | | `minTotalTeu` | integer | no | `0` | Drop carriers whose `totalTeu` is below this. `0` keeps everyone. | | `maxCarriers` | integer | no | `100` | Stop after this many carriers, counted after the filters above are applied. The table is pre-sorted by rank (largest fleet first), so a lower value keeps the top of the list. Clamped to 1-100 regardless of what is passed in. | | `includeVesselImos` | boolean | no | `true` | Populate `vesselImos`. Switch off for a much smaller payload; `vesselCount` stays populated either way. | | `includeGlobalTotals` | boolean | no | `true` | Attach the `globalFleet` object to every row. Switch off to get `globalFleet: null` and skip the second request. | ## Output One record per carrier. | Field | Type | Notes | |---|---|---| | `rank` | number | 1 (largest) to 100, derived from the table's own sort order. | | `operator` | string | Full/legal operator name. | | `carrierName` | string | Brand/trading name. | | `carrierCode` | string \| null | Short internal carrier code. | | `companyId`, `companyRefId` | string \| null | Alphaliner's internal identifiers. `companyRefId` is only populated for roughly the top 20 rows in the source data. | | `website` | string \| null | Carrier schedule/website URL. | | `flagIconUrl` | string \| null | Flag icon for country of registration. | | `notes` | string \| null | Free-text note, e.g. subsidiary brands folded into this entry. `null` when empty. | | `totalTeu`, `totalShips` | number | Total operated capacity: owned + chartered. | | `ownedTeu`, `ownedShips` | number | Owned-fleet portion. | | `charteredTeu`, `charteredShips`, `charteredSharePercent` | number | Chartered-in portion, and its share of the total fleet. | | `orderbookTeu`, `orderbookShips`, `orderbookSharePercent` | number | Vessels on order, and the orderbook as a percentage of the current fleet. | | `marketSharePercent` | number | Share of total global operated TEU. | | `top30` | boolean | `true` for the 30 largest operators, regardless of any filter applied. | | `vesselImos` | string[] | Every vessel's IMO number in the operated fleet. Empty when `includeVesselImos` is `false`. | | `vesselCount` | number | Length of the fleet's IMO list, populated even when `vesselImos` itself is switched off. | | `globalFleet` | object \| null | Global active cellular fleet totals, identical on every row: `activeVessels`, `activeTeu`, `activeDwt`, `fullyCellularVessels`, `fullyCellularTeu`, `transatlanticTeu`, `transpacificTeu`, `europeTeu`. `null` when `includeGlobalTotals` is `false`. | | `scrapedAt` | string | Run timestamp, ISO 8601. | A real record: ```json { "rank": 1, "operator": "Mediterranean Shg Co", "carrierName": "MSC", "totalTeu": 7389280, "ownedTeu": 4737110, "charteredTeu": 2652170, "charteredSharePercent": 35.9, "orderbookTeu": 2935318, "orderbookSharePercent": 39.7, "marketSharePercent": 21.5, "top30": true, "vesselCount": 1010, "globalFleet": { "activeVessels": 7629, "activeTeu": 34594537, "activeDwt": 411462664 }, "scrapedAt": "2026-08-21T12:00:00.000Z" } ``` ## RUN_SUMMARY Written to the run's key-value store under the key `RUN_SUMMARY`. ```json { "carriersReturnedByApi": 100, "carriersMatched": 30, "carriersSaved": 30, "callsFailed": 0, "failures": [], "filters": { "carriers": [], "minTotalTeu": 500000, "maxCarriers": 30, "includeVesselImos": true, "includeGlobalTotals": true }, "globalFleetIncluded": true, "finishedAt": "2026-08-21T12:00:03.041Z" } ``` `carriersReturnedByApi` is always 100 on a successful call, since the source has no concept of a partial table. `carriersMatched` is what survived `carriers` and `minTotalTeu` before `maxCarriers` truncated further; `carriersSaved` is what actually landed in the dataset (and is what is charged). `failures` names which of the two RPC calls (`getTop100Table`, `getTextCalculations`) failed, if either did; a `getTextCalculations` failure alone does not stop `getTop100Table` rows from being saved, it only leaves `globalFleet: null` on every row. ## Behaviour to plan around - **Everything is a single-run snapshot.** There is no date-range or history parameter. For a time series, run on a schedule and key your archive on `scrapedAt`. The source itself refreshes on a weekly cadence, so more frequent runs will mostly return identical figures. - **`rank` is computed from array order, not copied from the source's own rank field.** The source only populates its own rank field for roughly the top 20 rows; this Actor uses the table's guaranteed largest-first sort order for all 100 instead, which is more reliable. - **`top30` always reflects true global rank**, even when `carriers` or `minTotalTeu` has filtered the row set down. It is not recomputed relative to what you kept. - **`globalFleet` is one global figure repeated on every row**, not a per-carrier statistic. Every row in the same run carries an identical `globalFleet` object (or all carry `null` together, if `includeGlobalTotals` is `false` or the second call failed). - **`vesselCount` is independent of `includeVesselImos`.** The count is always computed from the source's fleet string; only whether the array itself is included in the record depends on the toggle. - **A handful of the smallest carriers carry no vessel list at all** (the source sends no fleet string for them), so `vesselCount: 0` and `vesselImos: []` for those rows is correct data, not a scraping gap. - **`maxCarriers` truncates after filtering, not before.** A tight `carriers` or `minTotalTeu` filter combined with a low `maxCarriers` can return fewer rows than `maxCarriers` allows; that is the filter working as intended, not a bug. - **Out-of-range `maxCarriers` is clamped, never treated as unlimited.** Values outside 1-100 (including 0 or negative) are clamped into that range rather than passed through. ## Recipes **Fast lookup on a handful of known carriers.** ```json { "carriers": ["MSC", "Maersk", "CMA CGM", "COSCO", "Hapag-Lloyd"], "includeVesselImos": false } ``` Skips the (large) IMO lists for a quick market-share/TEU comparison. **Concentration study on the top of the market.** ```json { "minTotalTeu": 500000, "includeVesselImos": false } ``` Keeps only carriers large enough to matter for an HHI-style concentration calculation, without the per-vessel payload. **Full carrier master-data / IMO mapping pull.** ```json {} ``` Every field populated for all 100 carriers, including the full `vesselImos` list per operator, the closest thing to a free IMO-to-operator lookup table for the global cellular fleet.