PyPI Package Scraper — Downloads, Releases, Vulnerabilities
Pricing
from $1.40 / 1,000 result items
PyPI Package Scraper — Downloads, Releases, Vulnerabilities
Python package intelligence for AI agents and due diligence: downloads (day/week/month, by Python version and OS), release history and cadence, parsed requirements, license (PEP 639), development status, known vulnerabilities (OSV), owners, project URLs.
Pricing
from $1.40 / 1,000 result items
Rating
0.0
(0)
Developer
Samat Makatov
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
10 days ago
Last modified
Categories
Share
One clean JSON per Python package: downloads (day/week/month, by Python version and OS), release history and cadence, parsed requirements, normalized license, development status, known vulnerabilities, owners and project links. Built for AI agents, dependency due diligence and developer-tool market research. Paste names or whole requirements.txt lines (requests>=2.31,<3). Plus two feed modes that watch PyPI for new releases and new projects in your niche. No API key, no proxy, no browser.
Use cases
- Dependency due diligence — before adopting a library, get license (PEP 639 expression, field or classifier), maintenance cadence, wheel availability, Python support, owners and open vulnerabilities in one item.
- Supply-chain / security review — paste the pins of your requirements.txt (
name==version) to see the OSV advisories (CVE/GHSA aliases, fixed-in) for exactly the versions you ship; flag yanked releases. Thesecuritydataset view shows them side by side. - Competitor & market tracking — compare monthly downloads, Python 3.x adoption and OS split of your SDK vs. alternatives; schedule weekly and diff.
- Abandonment alerts —
daysSinceLastRelease,releasesLast365d,developmentStatus(Inactive/Mature) feed a "stale dependency" monitor. - Launch tracking —
newPackages+keywords(e.g.mcp,agent,llm) surfaces projects launching in your category. - Release monitoring —
recentUpdates+keywordscatches new versions of an ecosystem (e.g.langchain,openai) minutes after upload.
Input
| Field | Type | Default | Notes |
|---|---|---|---|
mode | string | packages | packages · recentUpdates · newPackages (exactly these values: Apify refuses any other before the run starts) |
packages | string[] | — | Project names; name==1.2.3 or name@1.2.3 pins a version; a range (>=2.31,<3, ~=1.26, ==4.2.*) returns the newest release in it. Lines pasted from requirements.txt work (extras, markers, comments, --hash are dropped). Names are PEP 503-normalized (case/-/_/. insensitive); the same name + version twice is fetched and charged once. Required in packages mode. |
keywords | string[] | [] | Feed modes only: keep items whose name/summary contains any keyword (case-insensitive). |
maxItems | integer | 100 | Feed modes only (1–200). The PyPI feeds carry the latest ~40 entries. |
includeDownloads | boolean | true | Day/week/month downloads from pypistats.org (mirrors excluded), ~3 s per package (see Limits). |
downloadsBreakdown | boolean | false | Adds downloadsByPythonMinor30d and downloadsBySystem30d (last 30 days). +2 pypistats requests per package. |
downloadsHistoryDays | integer | 0 | 0–180. Adds downloadsHistory daily series + total. +1 request. |
includeMirrors | boolean | false | Count mirror traffic in breakdown/history. |
includeVulnerabilities | boolean | true | vulnerabilities[] from PyPI (OSV). vulnerabilityCount always present. |
includeReleases | boolean | true | recentVersions[] (newest first). |
versionsLimit | integer | 10 | 0–200 entries in recentVersions. |
includeClassifiers | boolean | true | Raw Trove classifiers[]. |
descriptionChars | integer | 0 | 0 = omit; N = first N chars of the long description (README). |
fields | string[] | [] | Top-level columns to keep, in this order; id and name are always kept. Case, -, _ and spaces do not matter; unknown names are ignored with a warning (status + SUMMARY), and the run fails when no name is a column. |
Reference
Sources and what they provide
| Source | Endpoint | Used for |
|---|---|---|
| PyPI JSON API | https://pypi.org/pypi/{name}/json, /pypi/{name}/{version}/json | metadata, releases, files, vulnerabilities, ownership |
| PyPI RSS | https://pypi.org/rss/updates.xml, /rss/packages.xml | recentUpdates, newPackages modes |
| pypistats.org | /api/packages/{name}/recent, /python_minor, /system, /overall | downloads (updated once a day, 180-day depth) |
PyPI has no search API (XML-RPC search was disabled in 2021); use the feed modes with keywords, or a search engine, to discover names.
Development Status classifiers → developmentStatus
| Code | Label |
|---|---|
| 1 | Planning |
| 2 | Pre-Alpha |
| 3 | Alpha |
| 4 | Beta |
| 5 | Production/Stable |
| 6 | Mature |
| 7 | Inactive |
Trove classifier groups (894 classifiers as of Sep 2026 — full list: https://pypi.org/classifiers/)
| Group | Count | Example |
|---|---|---|
| Development Status | 7 | Development Status :: 5 - Production/Stable |
| Environment | 74 | Environment :: Web Environment |
| Framework | 185 | Framework :: Django :: 5.1 |
| Intended Audience | 14 | Intended Audience :: Developers |
| License | 84 | License :: OSI Approved :: MIT License |
| Natural Language | 64 | Natural Language :: English |
| Operating System | 43 | Operating System :: OS Independent |
| Programming Language | 102 | Programming Language :: Python :: 3.12 |
| Topic | 320 | Topic :: Software Development :: Libraries |
| Typing | 2 | Typing :: Typed |
pythonVersions is extracted from Programming Language :: Python :: 3.N classifiers (3.0 … 3.16 exist today); requiresPython is the authoritative constraint.
pypistats categories
downloadsBySystem30dkeys:Linux,Windows,Darwin,null(unknown installer/CI), occasionallyother.downloadsByPythonMinor30dkeys:2.7,3.6…3.14,null.downloadsHistorycategories:without_mirrors(default) orwith_mirrors(includeMirrors: true).
Version ranges and requirements.txt lines
name==1.2.3,name===1.2.3,name@1.2.3andname 1.2.3pin a release (yanked releases included — that is what you pinned).- Any other specifier (
>=,<=,>,<,!=,~=,==1.2.*, comma-separated) is resolved like pip does (PEP 440): the newest release that satisfies it, has files and is not yanked; pre-releases only when the range names one or nothing else matches. The row then carriesrequestedSpecifierand that release's metadata and vulnerabilities. No release in the range → the entry is listed inSUMMARY.notFound(not charged). - Extras (
requests[socks]), environment markers (; python_version < "3.9"), comments (# …) and per-line options (--hash=…) are dropped; markers are not evaluated. A pasted file with line breaks and\continuations in one entry is split into lines. - Lines that are not packages —
-r other.txt,-e …, URLs,name @ https://…— are skipped and listed inSUMMARY.invalid(not charged).
License normalization (licenseSource)
expression (PEP 639 license_expression, SPDX) → field (short license string ≤ 60 chars) → classifier (License :: … leaf) → null. Projects that paste the whole license text into the field fall back to the classifier; the raw leaves are in licenseClassifiers.
Examples
Dependency due diligence with security and adoption data
{ "packages": ["requests", "fastapi", "langchain"], "downloadsBreakdown": true, "downloadsHistoryDays": 30 }
Audit the exact versions in your lockfile
{ "packages": ["pillow==9.0.0", "urllib3==1.26.5", "cryptography==41.0.0"], "includeDownloads": false, "includeReleases": false }
Paste requirements.txt lines (ranges resolve to the newest matching release)
{ "packages": ["requests[socks]>=2.31,<3", "urllib3~=1.26", "django==4.2.* # LTS"], "includeDownloads": false, "fields": ["version", "requestedSpecifier", "latestVersion", "vulnerabilityCount"] }
Lean weekly competitor tracker (small items, cheap to diff)
{ "packages": ["openai", "anthropic", "google-genai", "mistralai"], "fields": ["version", "downloadsWeek", "downloadsMonth", "lastRelease", "releasesLast90d", "vulnerabilityCount"] }
Watch for new MCP / agent projects launching on PyPI (run every 15 min)
{ "mode": "newPackages", "keywords": ["mcp", "agent", "llm"] }
Catch new releases in the LangChain ecosystem
{ "mode": "recentUpdates", "keywords": ["langchain", "langgraph"], "maxItems": 50 }
Output
One item per package (trimmed):
{"id": "fastapi","name": "fastapi","version": "0.141.1","requestedVersion": null,"requestedSpecifier": null,"isLatest": true,"latestVersion": "0.141.1","summary": "FastAPI framework, high performance, easy to learn, fast to code, ready for production","license": "MIT","licenseSource": "expression","licenseExpression": "MIT","licenseClassifiers": [],"developmentStatus": "Beta","developmentStatusCode": 4,"requiresPython": ">=3.10","pythonVersions": ["3.10", "3.11", "3.12", "3.13", "3.14"],"requires": [{ "name": "starlette", "normalized": "starlette", "specifier": ">=0.46.0", "extras": [], "marker": null, "extra": null, "url": null }],"requiresCount": 5,"requiresOptionalCount": 27,"extras": ["standard", "standard-no-fastapi-cloud-cli", "all"],"author": "Jane Doe","authorEmail": "jane@example.com","owners": [{ "user": "example-user", "role": "Owner" }],"organization": null,"homepage": "https://github.com/fastapi/fastapi","repository": "https://github.com/fastapi/fastapi","documentation": "https://fastapi.tiangolo.com/","changelog": "https://fastapi.tiangolo.com/release-notes/","issues": "https://github.com/fastapi/fastapi/issues","downloadsDay": 4012345, "downloadsWeek": 27654321, "downloadsMonth": 112345678,"downloadsByPythonMinor30d": { "3.12": 41000000, "3.11": 30000000, "3.13": 18000000, "null": 9000000 },"downloadsBySystem30d": { "Linux": 98000000, "Darwin": 7000000, "Windows": 6000000, "null": 1345678 },"downloadsError": null,"downloadsFetchedAt": "2026-09-12T23:29:51.120Z","vulnerabilities": [],"vulnerabilityCount": 0,"versionsCount": 317, "firstRelease": "2018-12-08T08:14:13.103167Z", "lastRelease": "2026-07-29T17:18:04.364385Z","daysSinceLastRelease": 45, "releasesLast90d": 24, "releasesLast365d": 92, "yankedVersionsCount": 0,"latestFiles": { "count": 2, "wheels": 1, "sdist": true, "totalSize": 557753, "publishedAt": "2026-07-29T17:18:04.364385Z" },"hasWheel": true, "yanked": false, "yankedReason": null,"recentVersions": [{ "version": "0.141.1", "publishedAt": "2026-07-29T17:18:04.364385Z", "files": 2, "hasWheel": true, "hasSdist": true, "yanked": false, "requiresPython": ">=3.10", "totalSize": 557753 }],"pypiUrl": "https://pypi.org/project/fastapi/","sourceUrl": "https://pypi.org/pypi/fastapi/json","fetchedAt": "2026-09-12T23:29:53.340Z"}
Download numbers in this sample are illustrative; downloadsHistory.series entries look like { "date": "2026-09-11", "downloads": 42125636 }. When pypistats has no numbers for a package (brand-new, or it rate-limited the run) the counts are null and downloadsError says why — the item is still returned.
| Field | Meaning |
|---|---|
id | PEP 503 normalized name — stable key for joins/dedupe |
version, requestedVersion, requestedSpecifier, latestVersion, isLatest | Returned version; pinned version if any; requested range if any; newest release on PyPI; whether they match |
license, licenseSource, licenseExpression, licenseClassifiers | See License normalization |
developmentStatus, developmentStatusCode | From the Development Status classifier (null if not declared) |
requiresPython, pythonVersions | Declared constraint; minor versions advertised via classifiers |
requires[], requiresCount, requiresOptionalCount, extras | Parsed PEP 508 requirements; count of mandatory vs. extra-only deps |
author, authorEmail, maintainer, maintainerEmail, owners[], organization | Metadata contacts as the project published them (Name <email> lists are split into names / e-mails) and PyPI account owners/maintainers. Names and e-mails are personal data: use them only for a legitimate purpose (e.g. reporting a security issue) |
homepage, repository, documentation, changelog, issues, funding, projectUrls | Normalized links (synonyms mapped, GitHub inferred) + raw map |
downloadsDay/Week/Month, downloadsByPythonMinor30d, downloadsBySystem30d, downloadsHistory, downloadsError, downloadsFetchedAt | pypistats data for the whole package (all versions); downloadsError explains a null (new package, rate limit); downloadsFetchedAt = when pypistats answered (a 24 h cached answer keeps its time) |
vulnerabilities[], vulnerabilityCount | OSV advisories for the returned version: id, aliases, summary, details, fixedIn, link, withdrawn |
versionsCount, firstRelease, lastRelease, daysSinceLastRelease, releasesLast90d, releasesLast365d, yankedVersionsCount | Release cadence |
latestFiles, hasWheel, yanked, yankedReason | Artifacts of the returned version |
recentVersions[] | Release history entries |
description, descriptionContentType | Only when descriptionChars > 0 |
pypiUrl, sourceUrl, fetchedAt | Provenance |
Feed items (recentUpdates / newPackages): id (name@version or name), feed, name, version, summary, author (the uploader's address as the feed publishes it, when any), publishedAt, pypiUrl, sourceUrl, fetchedAt.
Dataset views: overview, health (maintenance cadence), security (version, latest version, vulnerability count and advisories, yanked) and feed. The run's SUMMARY record (linked in the run output) lists delivered rows, packages not found or skipped (not charged), pypistats rate limits and input warnings.
Use it from code / agents
curl -X POST "https://api.apify.com/v2/acts/yadroo~pypi-package-intel/run-sync-get-dataset-items?token=$APIFY_TOKEN" \-H 'Content-Type: application/json' \-d '{"packages":["requests","fastapi"],"fields":["version","license","downloadsMonth","vulnerabilityCount"]}'
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('yadroo/pypi-package-intel').call({ packages: ['langchain', 'llama-index'], downloadsBreakdown: true });const { items } = await client.dataset(run.defaultDatasetId).listItems();
from apify_client import ApifyClientclient = ApifyClient(os.environ["APIFY_TOKEN"])run = client.actor("yadroo/pypi-package-intel").call(run_input={"packages": ["pillow==9.0.0"], "includeDownloads": False})items = client.dataset(run["defaultDatasetId"]).list_items().items
MCP: add https://mcp.apify.com to your agent (Claude, Cursor, etc.) and call the yadroo/pypi-package-intel tool with the same JSON input.
Pricing
Pay-per-event: $0.001 per run start + $0.002 per item (package or feed item). Bronze −10 %, Silver −20 %, Gold and above −30 % on the item price; the start event is the same on every plan; platform usage is included. A due-diligence run on 10 packages costs about $0.021; a newPackages poll returning 40 items about $0.081. Packages/versions that do not exist, could not be fetched or repeat an earlier entry are not items and are not charged; batch names into one run to amortize the start fee. The run stops at your Maximum cost per run and says so in its status ("Stopped at your spending limit: N rows delivered"); the packages it did not reach are listed in SUMMARY.notReached (not charged).
Limits & FAQ
- Rate limits. PyPI's JSON API is CDN-backed and generous. pypistats.org is a volunteer-run API with per-IP rate limits that asks for at most one fetch per package and day and points bulk users to BigQuery. The actor waits 3 s between pypistats requests (one per package; +2 with
downloadsBreakdown, +1 withdownloadsHistoryDays), honours itsRetry-After, and when it answers 429 pauses all pypistats requests and retries those packages after the others — waiting at most 2 minutes per run in total. A package whose numbers still could not be fetched is returned withnullcounts and adownloadsErrorthat says so, and the status message counts them. Metadata never depends on pypistats. - 24 h cache. pypistats answers are kept for 24 h in the key-value store
pypi-package-intel-cacheof the account that runs the actor, so a re-run the same day does not ask pypistats again (downloadsFetchedAtkeeps the original time). Delete the store any time; the next run rebuilds it. - Capacity (measured 02.10.2026). With downloads on, about 12–15 packages a minute (the 3 s pypistats pacing); without downloads, about 50 a minute (big projects such as boto3 ship multi-MB metadata) — so a 15-minute run handles roughly 180 or 700 packages. A run that reaches its timeout stops starting new packages, saves what it has read and ends with "Stopped before the run timeout: N rows saved" — raise the timeout or split the list. For thousands of packages' download numbers use pypistats' BigQuery source, not this API.
- Freshness. Metadata is live. Downloads are computed once a day by pypistats (BigQuery) with ~1–2 days lag and are per package, not per version. Feeds are near-real-time but only expose the latest ~40 events — poll every 5–15 min to avoid gaps in busy niches.
- Vulnerabilities are what PyPI reports (OSV / PyPA advisory database) for the returned version; pin versions to audit older releases. An empty array means "none known", not "audited safe".
- Unknown package or version → no item (not charged); it is listed in the
SUMMARYrecord of the default key-value store (notFound[],errors[{input, error}]for failed lookups,invalid[]for lines that are not package names,duplicateEntries[],notReached[{input, reason}]for packages a spending limit, timeout or abort stopped before they were saved, with counts) and counted in the run's status message. The run continues with the remaining names and fails only when no package could be fetched. - Not supported: search by keyword across PyPI (no public API), per-version download counts, reverse dependencies (needs libraries.io key or deps.dev — roadmap), downloading/inspecting wheels, evaluating environment markers.
- Roadmap: deps.dev dependents count, pyproject.toml / lockfile upload as input.
Made by Yadroo. Sibling actors: npm-package-intel · github-repo-intel · hackernews-search · stackexchange-search