--- name: homebrew-formulae-scraper description: Read Homebrew formulae and casks as structured records via the Apify Actor arman-bd/homebrew-formulae-scraper. Returns version, tap, licence, runtime and build dependencies, caveats, deprecation and disable status, and Homebrew's official 30-, 90- and 365-day install counts split into total installs and installs a user asked for by name. Use when a task needs macOS developer-tool adoption figures, dependency graphs, deprecation audits of an installed fleet, or a snapshot of the whole catalogue. Not for Linux distribution packages, npm or PyPI registries, or whether a specific machine's packages are outdated. --- # Homebrew Scraper Apify Actor `arman-bd/homebrew-formulae-scraper`. Name packages and get one record each, or leave the name list empty and get the entire catalogue. The install counts are the reason to reach for this: almost no other package registry publishes real adoption numbers. The Actor takes no credentials. ## When to use it - Measuring real developer-tool adoption on macOS, and diffing it month over month. - Separating genuine end-user tools from transitive dependencies, using the gap between total installs and on-request installs. - Auditing a fleet: feed in the output of `brew list` and filter on `deprecated` or `disabled`. - Building a dependency graph across a named set of packages. - Version tracking across the whole catalogue on a schedule. ## When not to use it - Linux distribution packages, npm, PyPI or any other registry. Homebrew only. - Deciding whether a particular machine needs an upgrade. You get the currently published `version`; the comparison against what is installed is yours to make. - Cask licences or cask on-request install counts. Neither is published, and the Actor returns `null` rather than guessing. - Third-party taps in index mode. Named lookups reach them; the bulk catalogue covers the two core taps only. ## Call it ```js import { ApifyClient } from 'apify-client'; const client = new ApifyClient({ token: process.env.APIFY_TOKEN }); const run = await client.actor('arman-bd/homebrew-formulae-scraper').call({ formulae: ['wget', 'ffmpeg', 'node'], resourceType: 'formula', includeAnalytics: true, includeDependencies: true, maxResults: 0, }); const { items } = await client.dataset(run.defaultDatasetId).listItems(); const { value: summary } = await client .keyValueStore(run.defaultKeyValueStoreId) .getRecord('RUN_SUMMARY'); ``` One-shot over HTTP, when you want the rows back in the same request: ```bash curl -X POST "https://api.apify.com/v2/acts/arman-bd~homebrew-formulae-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"formulae":["wget","ffmpeg","node"],"resourceType":"formula","includeAnalytics":true}' ``` The Actor is also exposed through Apify's MCP server as `arman-bd/homebrew-formulae-scraper`, so an MCP-capable agent can call it with no extra wiring. ## Input | Field | Type | Required | Default | Notes | |---|---|---|---|---| | `formulae` | string[] | no | `[]` | The names you type after `brew install`, for example `wget`, `python@3.14`, `firefox`. A package page URL works too: the Actor keeps the last path segment. Names are lower-cased and deduplicated. **An empty list means index mode**, the whole catalogue. | | `resourceType` | string | no | `"formula"` | `formula` for CLI packages, `cask` for macOS apps, `both`. With names, `both` tries each name as a formula and falls back to a cask. In index mode, `both` downloads both catalogues. Any other value aborts the run. | | `includeAnalytics` | boolean | no | `true` | Adds the six install-count fields. Off, they are absent from the record entirely, not `null`. | | `includeDependencies` | boolean | no | `true` | Adds `dependencies` and `buildDependencies`. Off, they are absent from the record entirely. | | `maxResults` | integer | no | `0` | Cap per resource type. `0` means no limit, roughly 8500 formulae and 7700 casks. | **Whether `formulae` is empty is the whole decision.** With names, each package is one small request, eight run in parallel, analytics arrive with the package, and 200 names finish in seconds. With no names, the Actor downloads the complete catalogue dump, which is tens of megabytes per type, and then fetches the install leaderboards separately and joins them by name. Index mode is the only way to ask "what is popular"; named mode is the only way to reach a third-party tap, and the only sensible mode when you already know what you are looking for. ## Output One record per package. Whether `dependencies`, `buildDependencies` and the six install fields appear at all depends on the two include flags. | Field | Type | Notes | |---|---|---| | `name` | string | What you type after `brew install`. The join key. | | `fullName` | string | Fully-qualified tap name. Equal to `name` for core packages. | | `resourceType` | string | `formula` or `cask`. In `both` mode this is how you tell which lookup won. | | `tap` | string \| null | `homebrew/core`, `homebrew/cask`, or a third-party tap. | | `description` | string \| null | One line. | | `homepage` | string \| null | Upstream project site. | | `version` | string \| null | Current published stable version. | | `license` | string \| null | SPDX expression. Always `null` for casks. | | `caveats` | string \| null | The post-install message, where the package has one. | | `deprecated` | boolean | | | `deprecationReason` | string \| null | | | `disabled` | boolean | Stronger than deprecated: the package no longer installs. | | `disableReason` | string \| null | | | `sourceUrl` | string \| null | The tarball, git URL or disk image the package installs from. | | `pageUrl` | string | The package's page. Built from the name, so always present. | | `dependencies` | string[] | Runtime dependencies. For casks, a flattened list such as `cask:xquartz` or `macos:>= :big_sur`. Only when `includeDependencies` is on. | | `buildDependencies` | string[] | Build-time only. Always empty for casks. Only when `includeDependencies` is on. | | `installs30d` | number \| null | Total installs in the trailing 30 days, including installs pulled in as someone else's dependency. Only when `includeAnalytics` is on. | | `installs90d` | number \| null | As above, 90 days. This is the field index mode sorts on. | | `installs365d` | number \| null | As above, 365 days. | | `installsOnRequest30d` | number \| null | Installs where a user named this package. Always `null` for casks. | | `installsOnRequest90d` | number \| null | As above, 90 days. | | `installsOnRequest365d` | number \| null | As above, 365 days. | | `scrapedAt` | string | Run timestamp, ISO 8601. | A real record with both flags on: ```json { "name": "wget", "fullName": "wget", "resourceType": "formula", "tap": "homebrew/core", "description": "Internet file retriever", "homepage": "https://www.gnu.org/software/wget/", "version": "1.25.0", "license": "GPL-3.0-or-later", "caveats": null, "deprecated": false, "deprecationReason": null, "disabled": false, "disableReason": null, "sourceUrl": "https://ftpmirror.gnu.org/gnu/wget/wget-1.25.0.tar.gz", "pageUrl": "https://formulae.brew.sh/formula/wget", "dependencies": ["libidn2", "libpsl", "openssl@3", "gettext", "libunistring"], "buildDependencies": ["pkgconf"], "installs30d": 17456, "installs90d": 59779, "installs365d": 337567, "installsOnRequest30d": 17178, "installsOnRequest90d": 58821, "installsOnRequest365d": 333559, "scrapedAt": "2026-08-06T11:41:20.844Z" } ``` ## RUN_SUMMARY Written to the run's key-value store under the key `RUN_SUMMARY`. **Read it.** It is where you find out which mode actually ran and what did not resolve. ```json { "mode": "named", "resourceType": "both", "requested": 4, "failed": 1, "failures": [{ "name": "nope-not-real", "error": "cask \"nope-not-real\" not found (404)" }], "packagesSaved": 3, "filters": { "resourceType": "both", "maxResults": 0, "includeAnalytics": true, "includeDependencies": true }, "finishedAt": "2026-08-06T11:41:20.900Z" } ``` `mode` is `named` or `index`. In index mode `requested` is `null`, because nothing was requested by name. `packagesSaved` short of `requested` is explained entirely by `failures`, which carries `{ name, error }` for a package that could not be resolved and `{ source, error }` when a catalogue or leaderboard fetch failed. A leaderboard failure is the case to watch: the run still saves every package, but the install fields come back `null` throughout, and only `failures` says why. ## Behaviour to plan around - **A cap in index mode means "the top N", but only with analytics.** The catalogue arrives alphabetically. When `includeAnalytics` is on and the leaderboards loaded, records are sorted by `installs90d` before the cap is applied. If analytics are off, or the leaderboards failed, the same cap silently gives you the first N alphabetically instead, starting somewhere around `a2ps`. - **`installs` and `installsOnRequest` measure different things.** Total installs include everything dragged in as a dependency. A library can show 1.4 million 90-day installs against 180000 on request, meaning its real user base is a tenth of the headline. Rank end-user tools on the on-request figure, or you will overstate infrastructure libraries by an order of magnitude. - **Casks return `null` for all three on-request fields, and `null` for `license`.** Neither is published for casks. This is not a scraping gap and it will not fill in. - **Named and index runs are directly comparable.** Per-package figures are broken out by variant command upstream and the Actor sums them, so an `installs90d` from a named run and one from an index run mean the same quantity and can be diffed. - **In `both` mode a failed name reports the cask error, not the formula one.** Each name is tried as a formula first and then as a cask, and `failures` keeps the last error. A typo in a formula name comes back as a cask lookup failure. Read it as "neither worked". - **The include flags remove fields rather than nulling them.** With `includeDependencies: false` there is no `dependencies` key at all. Use `'dependencies' in record` rather than a truthiness check if it matters. - **One missing package never aborts the run.** An unknown name is recorded in `failures` and the rest continue. The Actor only throws when nothing at all was saved. Transient errors get three attempts with linear backoff; a not-found is final and is not retried. - **Install windows are trailing and aggregated daily.** A package released last week has small numbers regardless of how good it is, and today's spike will not be visible for a day. Compare like-aged packages only. - **Index mode is heavy.** Both catalogues together are tens of megabytes before any analytics join. Turn `includeDependencies` off for a whole-catalogue version snapshot; it is the single biggest saving in the record. ## Recipes **Rank macOS developer tools by real adoption.** Index mode, capped, so the sort by 90-day installs decides what you get. ```json { "formulae": [], "resourceType": "both", "maxResults": 250, "includeAnalytics": true, "includeDependencies": false } ``` Rank on `installsOnRequest90d` for formulae and on `installs90d` for casks, since casks have no on-request figure. Re-run monthly and diff on `name`. **Audit an installed fleet for deprecation.** Paste the output of `brew list` in. ```json { "formulae": ["wget", "openssl@1.1", "python@3.8", "node", "ffmpeg"], "resourceType": "both", "includeAnalytics": false } ``` Filter on `disabled` first, those already fail to install, then `deprecated`, and report `deprecationReason` alongside each. **Dependency graph for a toolchain.** ```json { "formulae": ["ffmpeg", "imagemagick", "pandoc", "node", "python@3.14"], "resourceType": "formula", "includeDependencies": true, "includeAnalytics": false } ``` Union `dependencies` across records for the runtime closure, and keep `buildDependencies` separate: they are not needed at run time and will inflate a shipped-artefact analysis. **Whole-catalogue version snapshot for release tracking.** ```json { "formulae": [], "resourceType": "formula", "maxResults": 0, "includeAnalytics": false, "includeDependencies": false } ``` Store `name` plus `version` and diff against the previous run. Anything with a changed `version` shipped a release since you last looked.