npm Scraper - Package Statistics, Downloads & Dependencies avatar

npm Scraper - Package Statistics, Downloads & Dependencies

Pricing

from $1.00 / 1,000 run start fees

Go to Apify Store
npm Scraper - Package Statistics, Downloads & Dependencies

npm Scraper - Package Statistics, Downloads & Dependencies

Look up npm packages by name or search the registry. Every row carries version, licence, dependencies, release history, maintainers, deprecation flags and real download counts — one table for dependency audits, licence checks and ecosystem research. No key, no login.

Pricing

from $1.00 / 1,000 run start fees

Rating

0.0

(0)

Developer

SR

SR

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

16 days ago

Last modified

Share

This npm scraper turns a list of package names — or one registry search — into a clean table of package statistics: version, licence, declared dependencies, release history, maintainers, deprecation flags and real download counts. Use it for dependency audits, licence checks and ecosystem research, then export the rows as JSON, CSV or Excel. No key, no login: it reads npm's own public APIs.

What the npm scraper does

The question it answers that a package page does not: is this dependency actually alive?

request is the example worth keeping in mind. It has been deprecated for years. It still returns a complete package document, a valid version, a licence and a repository link — and 59,191,622 downloads in the last month, a real figure from the run below. Nothing about the response looks wrong.

npm never deletes a package, it flags it. A scraper that reads the document and reports what it finds will tell you a dead dependency is healthy. This Actor lifts deprecated and deprecated_reason onto every row, and the run summary counts how many of your packages are flagged. The companion signal is days_since_last_release: sort your dependency tree by it and what has been abandoned under you becomes obvious.

Three endpoints, because each holds something the others do not

EndpointWhat only it has
registry.npmjs.org/<pkg>Every version, dependencies, licence, maintainers, release dates
registry.npmjs.org/-/v1/searchThe composite score and the dependents count
api.npmjs.org/downloadsActual download numbers

So the two modes return different columns, and the run summary says which mode you are in rather than leaving you to wonder why a field is empty.

By name returns the full document. This is the package statistics path: dependencies, release history, deprecation, tarball and dist-tags.

packages: ["express", "lodash", "request"]

By search returns scores and dependents, which the package document does not carry.

search: "http client"

Download counts are added in either mode, one extra request per package.

A note on npm's scores

npm publishes a score object with a composite final value and a detail breakdown of quality, popularity and maintenance. The breakdown is dead: in the search results this Actor was measured against, the composites ran from 414.12 to 663.79 while quality, popularity and maintenance came back as exactly 1 for every single package. Three columns that are always 1 look like signal and are not, so this Actor does not return them.

What it does return is score, which genuinely varies, and dependents — the count of packages depending on this one. npm sends that number as text, so it arrives as text ("45" for a niche HTTP client), not as an integer.

Input

FieldTypeDefault
packageslist of exact names, scoped names includedno default; the editor prefills ["express","lodash","request"]
searchregistry search term—
include_downloadsfetch real download countstrue
downloads_periodlast-day, last-week, last-monthlast-month
limit1–200050
retries1–63
cursorcontinuation from next_cursor—

Fill packages or search. When both are filled, packages wins; search is only read when the package list is empty. A single string split on commas, semicolons or spaces also works as a package list. downloads_period is validated even when include_downloads is false — only its use depends on that flag — and it chooses the field name you get back: downloads_day, downloads_week or downloads_month.

limit is clamped to 1–2000 and also caps the package list before anything is fetched, so a prefill of 200 names with limit: 10 returns 10 rows. retries counts backoff attempts per request: a 404 is definitive and is not retried, a 500 or timeout is.

Scoped names (@types/node, @actions/http-client) work everywhere a plain name does. The slash is preserved through the request, which is the detail that most quickly-written npm scrapers get wrong: a naive encode turns @types/node into %40types%2Fnode and the registry answers 404.

Output

One dataset row per package. This is a real row, from a run on the prefilled input (express, lodash, request) with downloads for the last month:

{
"name": "request",
"version": "2.88.2",
"description": "Simplified HTTP request client.",
"license": "Apache-2.0",
"homepage": "https://github.com/request/request#readme",
"repository": "https://github.com/request/request",
"keywords": ["http", "simple", "util", "utility"],
"dependencies": ["qs", "aws4", "uuid", "extend", "caseless", "isstream", "aws-sign2", "form-data", "mime-types", "oauth-sign", "safe-buffer", "tough-cookie", "tunnel-agent", "forever-agent", "har-validator", "is-typedarray", "http-signature", "combined-stream", "performance-now", "json-stringify-safe"],
"dependency_count": 20,
"dev_dependency_count": 20,
"peer_dependency_count": 0,
"engines": {"node": ">= 6"},
"maintainers": ["fredkschott", "mikeal", "nylen", "simov"],
"version_count": 126,
"first_release_at": "2011-01-22 00:36:12",
"last_release_at": "2020-02-11 16:35:36",
"days_since_last_release": 2430,
"deprecated": true,
"deprecated_reason": "request has been deprecated, see https://github.com/request/request/issues/3142",
"tarball": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
"unpacked_size_bytes": 209281,
"dist_tags": ["latest"],
"url": "https://www.npmjs.com/package/request",
"downloads_month": 59191622,
"position": 2,
"fetched_at": "2026-10-07T03:23:49Z"
}

Fields

  • Identity: name (scoped names included), version, description, keywords, url
  • Legal: license
  • Links: homepage, repository (normalised from git+https://…​.git to a plain https URL)
  • Dependencies: dependencies, dependency_count, dev_dependency_count, peer_dependency_count, engines
  • People: maintainers, publisher
  • History: version_count, first_release_at, last_release_at, days_since_last_release
  • Health: deprecated, deprecated_reason
  • Artefact: tarball, unpacked_size_bytes, dist_tags
  • Popularity: downloads_month (or _week / _day), score, dependents
  • Run: position (1..n across the whole job), fetched_at

The two modes do not return the same set. Name mode gives the full document above; search mode gives publisher, score, dependents and downloads_weekly instead of the dependency block. Fields a mode does not have are absent from the row, not null, so a missing value always means "not applicable here" rather than "empty".

license comes back only when npm publishes it as a plain string. Some packages declare a licence as an object or an SPDX expression array, and those arrive null rather than being flattened into something that looks canonical and is not. Rows in name mode come back in the order the registry answered, not necessarily the order you typed.

Summary, errors and empty results

Besides the rows, the run writes a summary record with the keys a report needs: packagesReturned, mode (package or search), totalMatches plus a samplingNote when the registry matched more than you asked for, deprecated with a deprecatedNote when it is above zero, unmaintainedOver2Years (nothing released in the last 730 days), withDownloads, downloadsPeriod, fetchedInSeconds and errorCount (always, next to rows as well as on an empty run). Failures land in a separate errors record with the source codes (bad_input, not_found, fetch_failed, no_results) so you can tell "you typed something wrong" from "the registry said no".

On a free plan, a run that fills the 10-result cap also writes an OUTPUT record saying the limit was reached, so the stopped-at-10 case is visible in the run rather than only in the row count.

A name that does not exist returns a not_found error naming it, never a row of nulls. A run with nothing to return finishes successfully with an empty dataset and no row charge.

Large jobs

Package documents are heavy: the typescript package document alone measured 15.4 MB and 19 seconds to fetch. A run that reaches its time budget stops cleanly and returns a next_cursor; pass that value back as cursor with the same input and the job continues where it stopped, so a list of thousands of packages is a series of runs rather than one that quietly truncates.

Use cases

  • Dependency audit. Paste the dependencies from your package.json and get every licence, deprecation flag and last-release date back in one table.
  • Supply-chain review. dependency_count and the dependencies list show how much weight a package drags in; maintainers shows how many people can publish a release.
  • Abandonment check. Sort by days_since_last_release and filter on deprecated.
  • Ecosystem research. Search a term and rank by dependents to see what the ecosystem actually builds on rather than what markets itself best.
  • Package selection. Compare candidates on downloads, dependents, dependency weight and release recency in a single run.
  • Download tracking. Re-run the same list on a schedule and keep the datasets to follow download counts over time for your own packages or a competitor's.

npm scraper compared with the alternatives

This Actornpm Registry API yourselfnpm-stat, npmtrends & friendsGeneric web scraper
SetupPaste names or a search termWrite three clients, retry and rate-limit codeOne package at a time in a browserConfigure selectors per page
Columns in one rowDocument, score, dependents and downloads togetherOne endpoint each, you join themDownloads, or metadata, rarely bothWhatever the page renders
Deprecationdeprecated plus the reason, counted in the summaryPresent in the document, you extract itUsually absentOften invisible
OutputFlat rows, JSON/CSV/Excel, ready to sortNested JSON per endpointCharts on screenDepends on your selectors
CredentialsNoneNoneNoneNone

If you already run your own integration against the registry API, you do not need this. If you want one table of package statistics in a minute — with deprecation, downloads and dependents already on the same row — this is quicker than assembling three endpoints by hand, and far sturdier than scraping package pages.

Pricing

The price is $0.001 per run (run_start) plus $0.001 per package row (package). A run that returns nothing only incurs the run-start fee, and an empty result is free beyond that. Users on a free Apify plan receive at most 10 package results per run; you can use a lower limit to request fewer.

As an example: a dependency review of 200 packages costs $0.001 + 200 × $0.001 = $0.201.

FAQ

Do I need an npm token or API key to use this npm scraper?

No. Everything comes from npm's public endpoints, so there is no account to create and no key to paste. Rate limits are npm's, and the Actor retries politely before it reports anything.

Why are some licences empty?

npm publishes a licence as a plain string for most packages, but some declare an object or an SPDX expression array. Those come back null instead of being flattened into a value that looks canonical and is not.

What happens when a package does not exist?

The run finishes successfully and the errors record names the missing package with not_found. You never get a row of nulls that you have to filter out later. A 404 is definitive, so it is not retried; a 500 or a timeout is retried with backoff first.

Does the npm scraper return the full dependency tree?

No. You get a package's own declared dependencies, not the resolved tree beneath them. Feed the dependency names back in as packages — dependency_count tells you which ones are worth the next level — and walk the tree one layer per run.

Can I follow a very large job across several runs?

Yes. When a run reaches its time budget it returns a next_cursor; send it back as cursor with unchanged input and the job continues. The summary reports what was delivered so far, and rows keep their position across the pieces.

Does it return download history over time?

No. Each run returns the download count for the period you asked for (last-day, last-week or last-month). Schedule the Actor and keep the datasets to build your own history.