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
12 hours ago
Last modified
Categories
Share
PyPI Package Intel — downloads, versions, requirements, vulnerabilities
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. 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 — pin
name==versionto see the OSV advisories (CVE/GHSA aliases, fixed-in) for exactly the version you ship; flag yanked releases. - 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. - Lead generation for dev tools —
newPackages+keywords(e.g.mcp,agent,llm) surfaces projects launching in your category, with author e-mail when published. - 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 |
packages | string[] | — | Project names; name==1.2.3 or name@1.2.3 pins a version. Names are PEP 503-normalized (case/-/_/. insensitive). 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). |
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[] | [] | Whitelist of top-level output fields; id and name are always kept. |
Existing inputs (packages, versionsLimit) keep their old meaning.
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).
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 }
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,"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": "Sebastián Ramírez","authorEmail": "tiangolo@gmail.com","owners": [{ "user": "tiangolo", "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": null, "downloadsWeek": null, "downloadsMonth": null,"downloadsByPythonMinor30d": null,"downloadsBySystem30d": null,"downloadsError": "pypistats unavailable: 429 Too Many Requests after 4 attempts: https://pypistats.org/api/packages/fastapi/recent","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"}
This sample was captured while pypistats was rate-limiting our IP, so it shows the degraded shape. With pypistats available the download fields are integers and {category: count} maps, e.g. for requests (captured 2026-09-12): downloadsHistory.series entries like { "date": "2026-09-11", "downloads": 42125636 }, downloadsBySystem30d like { "Linux": …, "Darwin": …, "Windows": …, "null": … }, downloadsByPythonMinor30d like { "3.12": …, "3.11": …, "2.7": … }.
| Field | Meaning |
|---|---|
id | PEP 503 normalized name — stable key for joins/dedupe |
version, requestedVersion, latestVersion, isLatest | Returned version; pinned version 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 (Name <email> lists are split into names / e-mails) and PyPI account owners/maintainers |
homepage, repository, documentation, changelog, issues, funding, projectUrls | Normalized links (synonyms mapped, GitHub inferred) + raw map |
downloadsDay/Week/Month, downloadsByPythonMinor30d, downloadsBySystem30d, downloadsHistory, downloadsError | pypistats data; downloadsError explains a null (new package, rate limit) |
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, publishedAt, pypiUrl, sourceUrl, fetchedAt.
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). 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 or could not be fetched are not items and are not charged; batch names into one run to amortize the start fee.
Limits & FAQ
- Rate limits. PyPI's JSON API is CDN-backed and generous. pypistats.org is IP rate-limited; the actor paces calls (~1.2 s apart) and backs off on 429 (up to ~1 min). If pypistats is rate-limited despite retries, the actor stops calling it for the rest of the run and every item carries
downloadsErrorexplaining why — metadata never depends on it. Counts are the same for 24 h, so re-run later for the numbers. KeepdownloadsBreakdown/downloadsHistoryDaysoff for large batches. - Freshness. Metadata is live. Downloads are computed once a day by pypistats (BigQuery) with ~1–2 days lag; do not poll download numbers more than once a day. 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[], anderrors[{input, error}]for failed lookups, with counts) and 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 (pypistats provides them but only for the last 180 days and per request — on the roadmap), reverse dependencies (needs libraries.io key or deps.dev — roadmap), downloading/inspecting wheels.
- Roadmap: per-version downloads, deps.dev dependents count, requirements.txt / pyproject upload as input.
Made by Yadroo. Sibling actors: npm-package-intel · github-repo-intel · hackernews-search · stackexchange-search