# npm + PyPI Package Scraper (`dami_studio/package-registry-scraper`) Actor

npm and PyPI package scraper: look up packages by exact name, or search npm by keyword. Each row has the package metadata: latest version, description, author, repository, licence, keywords and project link, plus monthly downloads on npm. PyPI matches exact names only. $2 per 1,000 packages.

- **URL**: https://apify.com/dami_studio/package-registry-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Developer tools, Integrations, Other
- **Features:** Limited permissions
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.94 / 1,000 packages

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

**Look up npm and PyPI packages by exact name, or search npm by keyword, and get one row per package**: the latest version, description, author, homepage, repository, licence, keywords and the project page, plus last month's download count on npm. Both registries come back in the same row shape.

The two registries are not symmetrical, and it matters before you plan a run. **npm does keyword search and exact names. PyPI does exact names only**, because it publishes no clean search API for anyone to call. And an npm search row never carries the licence, so a licence audit goes through names.

| | |
|---|---|
| **Input** | Exact package names on npm or PyPI, or keywords for an npm search |
| **Output** | One row per package: version, description, author, homepage, repository, licence, keywords, page link, npm monthly downloads |
| **Ceiling** | 1,000 packages per npm search. Name lists are not capped |
| **Account needed** | None, and no registry key |
| **Price** | $2.00 per 1,000 packages on the Free plan, a little less on paid plans. The free plan's $5 a month covers about 2,500 |

### 🔍 What Package Registry Scraper does

Two ways in, and you can use both at once on npm.

**Exact names.** Put a list in `packageNames` and each one is looked up directly. Scoped npm names like `@types/node` work. A name that does not exist gets its own `NOT_FOUND` row, uncharged, and the run carries on with the rest.

**npm keyword search.** Put words in `searchQuery` and the registry's own search decides what matches, ordered by its relevance score, up to your `maxItems`.

There is a real difference between the two that the field list hides: **a search row has no licence**, because npm's search index does not carry it. Repository, homepage and keywords come through on most search rows but not all. If you are doing a licence audit, feed the names in through `packageNames` and look them up properly.

`includeDownloads` is on by default and adds last month's download count to npm rows. PyPI has no public endpoint for that, so PyPI rows never carry it.

### 📋 What data you get from each npm or PyPI package

| What you get | Field |
|---|---|
| Name, latest version and which registry | `name`, `version`, `registry` |
| What it is | `description`, `keywords` |
| Who wrote or published it | `author` |
| Where its code and docs live | `repository`, `homepage` |
| The declared licence | `license` |
| The project page on npmjs.com or pypi.org | `url` |
| Last month's downloads, npm only | `monthlyDownloads` |
| npm's search relevance, search rows only | `score` |

### ▶️ How to scrape npm and PyPI packages

1. Open [Package Registry Scraper](https://apify.com/dami_studio/package-registry-scraper) and click **Try for free**.
2. Pick a **Registry**.
3. Paste names into **Package names (exact)**, one per line. For an npm search, type into **Search query (npm only)** instead.
4. Leave **Include monthly downloads (npm)** on unless you do not want it, then click **Start**.
5. Download the dataset as JSON, CSV, Excel or XML.

### 💰 How much does it cost to scrape npm and PyPI packages?

**$2.00 per 1,000 packages** on the Free plan, and the same on both registries. Paid plans pay a little less: $1.98 on Starter, $1.96 on Scale and $1.94 on Business. The free plan's $5 a month covers about 2,500 packages.

You pay per package row. The same name twice in one run is charged once. `NOT_FOUND` names, a search that matched nothing and every diagnostic row are not charged. Turning downloads on does not change the price, and a row whose download count could not be read is still a charged row.

If you set a maximum charge for a run, it stops at the last row that fits.

### 📥 What you give it

```json
{
  "registry": "npm",
  "packageNames": ["react", "@types/node"],
  "includeDownloads": true
}
```

| Field | Default | What it is |
|---|---|---|
| `registry` | `npm` | `npm` or `pypi`. |
| `packageNames` | none | Exact names. Works on both registries, and is the only mode PyPI supports. |
| `searchQuery` | none | Keywords, npm only. Ignored on PyPI unless you sent no names, in which case you get `BAD_INPUT`. The Console shows `react state management` as an example, but that is a prefill, so an API call has to send its own. |
| `maxItems` | `50` | 1 to 1,000. **Caps the npm search only.** It does nothing to a list of names. |
| `includeDownloads` | `true` | Last month's downloads for npm packages. Costs one extra lookup per package, not extra money. |
| `notionConnector` | none | Optional. Writes every delivered package into your Notion. Authorise a connector once under Settings, API and Integrations, MCP connectors, then pick it here. |
| `notionParentId` | none | Optional. The Notion data source id to write into. Leave it empty and the pages are created privately in your workspace. |
| `proxyConfiguration` | off | Optional network setting. Off is right for a normal run. |

**`packageNames` has no ceiling.** Paste a 4,000-line `requirements.txt` and you get up to 4,000 charged rows, because `maxItems` is not watching that path. Trim the list to what you need.

### 📤 What you get back

A real row from a name lookup on 24 September 2026:

```json
{
  "ok": true,
  "registry": "npm",
  "name": "typescript",
  "version": "7.0.2",
  "description": "TypeScript is a language for application scale JavaScript development",
  "author": "Microsoft Corp.",
  "homepage": "https://www.typescriptlang.org/",
  "repository": "https://github.com/microsoft/TypeScript",
  "license": "Apache-2.0",
  "keywords": ["TypeScript", "Microsoft", "compiler", "language", "javascript"],
  "score": null,
  "url": "https://www.npmjs.com/package/typescript",
  "monthlyDownloads": 1004355023
}
```

And a real row from an npm search on 2 October 2026, which shows the search-path gap at its worst: no licence, and this package's repository, homepage and keywords are empty too.

```json
{
  "ok": true,
  "registry": "npm",
  "name": "unstated-next",
  "version": "1.1.0",
  "description": "200 bytes to never think about React state management libraries ever again",
  "author": "thejameskyle",
  "homepage": null,
  "repository": null,
  "license": null,
  "keywords": [],
  "score": 375.834,
  "url": "https://www.npmjs.com/package/unstated-next",
  "monthlyDownloads": 443321
}
```

| Field | How to read it |
|---|---|
| `version` | The latest published version. npm's `latest` tag, PyPI's current release. |
| `license` | The declared licence string, as the package wrote it: `Apache-2.0` on one, `Apache License 2.0` on another. `null` on every npm search row. |
| `repository` | Normalised into a clickable https URL rather than a git address. |
| `score` | npm's own search relevance. Present on search rows, `null` on npm name lookups, and the key is absent from PyPI rows entirely. |
| `monthlyDownloads` | npm only. `null` if the count could not be read, and the key is absent when `includeDownloads` is off or the row is from PyPI. |
| `author` | Sometimes the publishing account's handle rather than a person or company: `next` comes back as `vercel-release-bot`. |

### 🧾 Reading the output

Packages carry `ok: true`. Anything with `ok: false` carries an `errorCode` and is not charged. One run can contain both, which is normal when a name list has a typo in it. There is no sample row.

| Code | What it means |
|---|---|
| `BAD_INPUT` | Neither names nor a query, or a PyPI run with a `searchQuery` and no names. |
| `NOT_FOUND` | That one name is not in the registry. Check the spelling, the scope, and whether it was unpublished. |
| `NO_RESULTS` | Nothing at all came back. An npm search that matched no packages ends here. |
| `NOT_SERVED` | The registry did not serve that name or search on this run. Re-run it later. Runs before 13 October 2026 wrote `BLOCKED` here, and runs from 13 to 17 October wrote `NETWORK`. |
| `RATE_LIMITED` | The registry asked the run to slow down and was still asking after several tries. Try again in a few minutes, or ask for fewer packages. |
| `SERVER_ERROR` | The registry answered with a server error several times over. It is usually brief, so re-run it later. |
| `NETWORK` | The registry was unreachable or answered with something unusable. Re-run it. |
| `TIME_LIMIT` | The run got close to its time limit and stopped on its own. The packages delivered before that are complete and charged. Give the run more time, or ask for fewer packages, to get the rest. |

A run that ends on one of these rows still finishes as succeeded, including when the time limit
stopped it before any package came back: the `TIME_LIMIT` row and an amber status message say so.

The Console's **Overview** table hides `license` and `repository`, which are the two columns a licence audit actually needs. Download the dataset as JSON, CSV or Excel, or switch the table to all fields.

### 💡 What people use it for

- Auditing the licences behind a `package.json` or a `requirements.txt` by feeding the names in and reading the `license` column.
- Comparing two libraries side by side on version, downloads and links before choosing one.
- Watching a dependency on a schedule and firing an alert when `version` moves.
- Sizing a niche: search npm for the keywords, then rank by `monthlyDownloads` to see what people actually install.

### 🚧 What it does not do

- **No PyPI search.** Exact names only on that side.
- **No PyPI download counts.** There is no public endpoint to read them from.
- **No licence on npm search rows.** Use `packageNames` when the licence is the point.
- **No dependency trees and no version history.** One row is the current state of one package.
- **No download trend.** `monthlyDownloads` is last month's total, a single number, not a series.
- **No GitHub stars or issues.** `repository` gives you the link, and [GitHub Scraper](https://apify.com/dami_studio/github-scraper) reads the rest.
- **Rows are a snapshot.** Versions and download counts move daily.
- It is not an official npm tool. Dami's Studio is independent and is not affiliated with or endorsed by npm, PyPI or GitHub, or by any other company named on this page.

### 🧭 Which developer data scraper do you need?

| If you want | Use |
|---|---|
| npm and PyPI package metadata and downloads | This one |
| Repositories, stars, forks and issues from GitHub | [GitHub Scraper](https://apify.com/dami_studio/github-scraper) |
| Stack Overflow questions, tags and scores | [Stack Overflow Scraper](https://apify.com/dami_studio/stackexchange-scraper) |
| Developer articles by tag or author | [DEV Community Scraper](https://apify.com/dami_studio/devto-scraper) |
| Show HN launches and developer discussion | [Hacker News Scraper](https://apify.com/dami_studio/hacker-news-scraper) |
| Package lookups from inside Claude, Cursor or ChatGPT, beside other research tools | [Research MCP Server](https://apify.com/dami_studio/research-mcp-server) |

### ❓ Questions people ask

#### Do I need an npm or PyPI account or API key?

No. Both registries publish this openly, and the run needs nothing from you but the names or keywords.

#### Can I search PyPI by keyword?

No. PyPI has no clean public search API, so names are the only route. Find the name elsewhere and look it up here.

#### Why is the licence empty?

You came in through the npm search. Search results do not carry it. Take the names from that run and look them up in a second run.

#### Can I mix a search and a name list?

On npm, yes. Both run and the results are deduplicated.

#### Does maxItems protect me from a huge name list?

No. It caps the search only. Trim the list yourself.

#### Can I call it from code or connect it to an AI assistant?

The [API tab](https://apify.com/dami_studio/package-registry-scraper/api/python) has code for Python, JavaScript and
the CLI. MCP clients like Claude and ChatGPT use
`https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/package-registry-scraper`; coding assistants can load
[our skill
file](https://api.apify.com/v2/key-value-stores/BTBHU3DdiuoF4JOsK/records/apify-finance-sports-entertainment-SKILL.md).
All three bill your Apify account at the same price.

#### Is it legal to scrape npm and PyPI?

Both registries publish this through open APIs, and it is package metadata rather than personal data, though author names can be people. Apify's write-up on [the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/) is a good starting point, and we are not lawyers.

### 🆘 If something breaks

Open the **Issues** tab on the actor page. Send the registry, the names or query you used and the run ID. The `errorCode` on the diagnostic row usually names the problem by itself.

# Actor input Schema

## `registry` (type: `string`):

Which package registry to use. npm supports both keyword search and exact-name lookup; PyPI supports exact-name lookup only (it has no clean public search API).

## `searchQuery` (type: `string`):

Keywords to search the npm registry for (e.g. "react state management"). npm only, ignored for PyPI. Leave empty if you are looking up exact package names instead.

## `packageNames` (type: `array`):

Exact package names to look up directly. Works for BOTH registries. For PyPI this is the only supported mode (e.g. \["requests", "fastapi"]). For npm, scoped names like "@types/node" are supported.

## `maxItems` (type: `integer`):

Maximum number of packages to return from an npm search query. Only applies to npm search; ignored for exact-name lookups.

## `includeDownloads` (type: `boolean`):

Fetch last-month download counts for each npm package via the npm downloads API. npm only. PyPI does not expose a public download-count endpoint. Adds one request per package. Note: monthlyDownloads is null when this is off, for PyPI packages, or if the downloads API call fails for a given package (a warning is logged in that case).

## `notionConnector` (type: `string`):

Optional. Write each package as a page into your Notion when the run finishes. Authorize a Notion connector once in Settings → API & Integrations → MCP connectors, then pick it here. Leave empty to skip (default), results are always saved to the dataset regardless.

## `notionParentId` (type: `string`):

Optional. The Notion data source ID of the database to write into (only used if a Notion connector is set). Leave empty to create the pages privately in your workspace instead.

## `proxyConfiguration` (type: `object`):

Optional, and not needed for a normal run.

## Actor input object example

```json
{
  "registry": "npm",
  "searchQuery": "react state management",
  "packageNames": [],
  "maxItems": 50,
  "includeDownloads": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

Scraped rows are stored in the default dataset (one row per result). Blocked/empty/error runs return a single uncharged diagnostic row instead.

# 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 = {
    "searchQuery": "react state management",
    "packageNames": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/package-registry-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 = {
    "searchQuery": "react state management",
    "packageNames": [],
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/package-registry-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 '{
  "searchQuery": "react state management",
  "packageNames": []
}' |
apify call dami_studio/package-registry-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/package-registry-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/FAWICz3l54yX2nqBd/builds/m45sf5xANyQdhvOgR/openapi.json
