# npm Download Stats Scraper: Trends & Rankings (`arman-bd/npm-download-stats-scraper`) Actor

Track npm download volume per package across day, week, month, year or a custom range. Daily series, peak days and share-of-total. No key, no proxy.

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

## Pricing

from $0.37 / 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/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

## npm Download Stats Scraper: Trends & Rankings

![npm Download Stats Scraper: Download volume for any set of npm packages, totals, a daily series, average and peak day, and share of the set](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/npm-download-stats-scraper.jpg)

Reads download volume for any set of npm packages straight from the official npm downloads API. You get totals, a day-by-day series, average per day, peak day and each package's share of the set you asked about.

This is the counts endpoint at `api.npmjs.org/downloads`, not the registry, so there's no key, no login, no proxy and no browser, just JSON. Unscoped packages are batched 128 at a time, so a 500-package comparison is four HTTP requests and finishes in a couple of seconds.

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

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

### What you get

| Field | What it holds |
|---|---|
| `package` | Package name as npm reports it |
| `period`, `granularity` | The window you asked for, and whether a daily series was requested |
| `start`, `end`, `days` | Actual window npm served, and its length in days |
| `totalDownloads` | Downloads across the whole window |
| `dailySeries` | `[{ day, downloads }]`, populated when `granularity` is `range` and empty for `point` |
| `averagePerDay` | `totalDownloads / days`, rounded |
| `peakDay`, `peakDownloads` | Busiest day in the series and its count, `range` only |
| `shareOfComparisonSet` | Fraction of the combined downloads of every package in the run, `compareMode` only |
| `url` | npmjs.com package page |
| `scrapedAt` | Run timestamp |

Records are sorted by `totalDownloads` descending, so the dataset is already a ranking. `RUN_SUMMARY` in the key-value store holds counts, the request count, the filters you used, and any package that returned nothing.

### Use cases

- **Library benchmarking.** Compare your library against its alternatives in one run, with share of total included.
- **Adoption inflection points.** Pull a year of daily data and diff week over week.
- **OSS traction reporting.** Defensible numbers from the canonical source, on a schedule.
- **Supply-chain and SBOM triage.** Weight dependency risk by how much the ecosystem actually uses a package.
- **DevRel dashboards.** Feed a chart directly from the dataset, no transformation needed.

### Quick start

Last month's totals for three frameworks:

```json
{
 "packages": ["express", "fastify", "koa"],
 "period": "last-month",
 "granularity": "point"
}
```

A year of daily data with share of total, for a competitive chart:

```json
{
 "packages": ["react", "vue", "svelte", "@angular/core"],
 "period": "last-year",
 "granularity": "range",
 "compareMode": true
}
```

An explicit window, for the release quarter you actually care about:

```json
{
 "packages": ["zod", "yup", "joi", "valibot"],
 "period": "2026-01-01:2026-06-30",
 "granularity": "range",
 "compareMode": true
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `packages` | array | required | Package names, scoped or plain. Duplicates are removed before any request. |
| `period` | string | `last-month` | `last-day`, `last-week`, `last-month`, `last-year`, `YYYY-MM-DD`, or `YYYY-MM-DD:YYYY-MM-DD`. |
| `granularity` | string | `range` | `point` for a single total, `range` to also get the daily series. |
| `compareMode` | boolean | `false` | Fills in `shareOfComparisonSet`. Only meaningful with two or more packages. |

`compareMode` compares the packages in this run, so the share is relative to the set you passed, not to all of npm. Combine it with `granularity: "point"` when you only want the ranking and want the dataset small.

### Output example

A real record, produced by `{"packages": ["express".], "period": "last-week", "granularity": "range", "compareMode": true}`:

```json
{
 "package": "express",
 "period": "last-week",
 "granularity": "range",
 "start": "2026-07-30",
 "end": "2026-08-05",
 "days": 7,
 "totalDownloads": 130006647,
 "dailySeries": [
 { "day": "2026-07-30", "downloads": 22186799 },
 { "day": "2026-07-31", "downloads": 19639130 },
 { "day": "2026-08-01", "downloads": 10836680 },
 { "day": "2026-08-02", "downloads": 10368617 },
 { "day": "2026-08-03", "downloads": 21441612 },
 { "day": "2026-08-04", "downloads": 22925745 },
 { "day": "2026-08-05", "downloads": 22608064 }
 ],
 "averagePerDay": 18572378,
 "peakDay": "2026-08-04",
 "peakDownloads": 22925745,
 "shareOfComparisonSet": 0.8015,
 "url": "https://www.npmjs.com/package/express",
 "scrapedAt": "2026-08-06T11:40:59.376Z"
}
```

The weekend dip visible in that series, 10.8M on Saturday against 22.9M on Tuesday, is normal for developer tooling and is exactly why `range` beats `point` for anything trend-shaped.

### Choosing a period

| You want | `period` | `granularity` |
|---|---|---|
| Yesterday's number | `last-day` | `point` |
| A weekly leaderboard | `last-week` | `point` |
| A trend chart | `last-year` | `range` |
| A specific quarter | `2026-01-01:2026-03-31` | `range` |
| One historical day | `2026-07-01` | `point` |

npm keeps roughly 18 months of daily history. Ask for more and it quietly moves `start` forward, so check the `start` field on the record rather than the `period` you sent.

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~npm-download-stats-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "packages": ["express", "fastify", "koa"],
 "period": "last-month",
 "granularity": "point",
 "compareMode": true
 }'
```

### JavaScript example

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

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/npm-download-stats-scraper').call({
 packages: ['react', 'vue', 'svelte'],
 period: 'last-year',
 granularity: 'range',
 compareMode: true,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const s of items) {
 console.log(`${s.package}: ${s.totalDownloads.toLocaleString()} (${(s.shareOfComparisonSet * 100).toFixed(1)}%)`);
}
```

### Limits and behaviour

- Unscoped packages are batched, scoped ones are not. npm's bulk endpoint accepts up to 128 comma-separated names but rejects anything starting with `@`, so scoped packages get one request each. The Actor splits your list automatically and logs how many requests it made.
- A missing package does not kill the run. Unknown names come back `null` inside a bulk response, or 404 on their own. Either way they land in `RUN_SUMMARY.failures` and everything else is still saved. The Actor errors only if nothing resolved at all.
- Transient errors get retried. 429 and 5xx get three attempts with linear backoff. 400 and 404 are fatal for that batch, because a malformed period will never succeed.
- Counts are downloads, not users. CI runners, mirrors and Docker builds all count. Treat the numbers as a relative signal over time, not as an installed base.
- Public data only. No authentication, no personal data, nothing that gets around access controls.

### FAQ

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

**Do I need an npm account or token?** No. You supply no credentials.

**How many packages can I pass?** As many as you like. Unscoped names cost one request per 128, and scoped names cost one request each. A thousand unscoped packages is eight requests.

**Why is a package missing from my dataset?** Either the name is wrong, or it has never been published, or it has no downloads in the window. `RUN_SUMMARY.failures` names it explicitly.

**How far back does history go?** About 18 months of daily data. Longer ranges are trimmed by npm rather than rejected, so always read `start` and `end` from the record.

**Does this give me versions and dependencies too?** No, that is a different npm API. Use npm Package Scraper for registry metadata.

**Can I schedule it?** Yes, it is designed for it. Run daily with `granularity: "point"` and build your own history, or weekly with `range` for ready-made charts.

# Actor input Schema

## `packages` (type: `array`):

npm package names, scoped (@scope/name) or plain. Unscoped names are batched 128 per request; scoped names must be fetched one at a time because the bulk endpoint rejects them.

## `period` (type: `string`):

A named window (last-day, last-week, last-month, last-year), an explicit range as YYYY-MM-DD:YYYY-MM-DD, or a single YYYY-MM-DD for one day. npm serves at most 18 months of history and silently trims longer ranges.

## `granularity` (type: `string`):

'point' returns one total for the whole period. 'range' also returns the day-by-day series, which is what you need for trend charts, peak detection and inflection analysis.

## `compareMode` (type: `boolean`):

Fill in shareOfComparisonSet, each package's percentage of the combined downloads of every package in this run. Leave off and the field is null.

## Actor input object example

```json
{
  "packages": [
    "react",
    "vue",
    "@angular/core"
  ],
  "period": "2026-01-01:2026-06-30",
  "granularity": "range",
  "compareMode": false
}
```

# 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 = {
    "packages": [
        "express",
        "fastify",
        "koa"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/npm-download-stats-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 = { "packages": [
        "express",
        "fastify",
        "koa",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/npm-download-stats-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 '{
  "packages": [
    "express",
    "fastify",
    "koa"
  ]
}' |
apify call arman-bd/npm-download-stats-scraper --silent --output-dataset

```

## MCP server setup

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