# Homebrew Scraper: Formulae, Casks & Install Analytics (`arman-bd/homebrew-formulae-scraper`) Actor

Scrape Homebrew formulae and casks: version, dependencies, licence, caveats, deprecation status and official 30/90/365-day install analytics. Rare public adoption data.

- **URL**: https://apify.com/arman-bd/homebrew-formulae-scraper.md
- **Developed by:** [Arman Hossain](https://apify.com/arman-bd) (community)
- **Categories:** Developer tools, Automation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.37 / 1,000 package scrapeds

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

## Homebrew Scraper: Formulae, Casks & Install Analytics

![Homebrew Scraper: Version, dependencies, licence and Homebrew's own 30/90/365-day install counts, for any formula or cask](https://api.apify.com/v2/key-value-stores/ZQOcNAOHrIgTacAmy/records/homebrew-formulae-scraper.jpg)

**Homebrew Scraper** exports Homebrew package data, version, dependencies, licence, caveats, deprecation status, together with Homebrew's **official 30-, 90- and 365-day install counts**. Almost no other package registry publishes real adoption numbers; Homebrew does, and this Actor puts them in a table.

**Agent skill: [SKILL.md](https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/homebrew-formulae-scraper.md)**

```
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/homebrew-formulae-scraper.md
```

### What you get

| Output field | Meaning |
|---|---|
| `name`, `fullName` | The name you type after `brew install`, and its fully-qualified tap name |
| `resourceType` | `formula` (CLI package) or `cask` (macOS app) |
| `tap` | Which tap it comes from, `homebrew/core`, `homebrew/cask`, or a third-party tap |
| `description` | One-line description |
| `homepage` | Upstream project homepage |
| `version` | Current stable version |
| `license` | SPDX licence expression. Always `null` for casks, Homebrew does not record licences for them |
| `dependencies` | Runtime dependencies. For casks, a flattened `depends_on` list such as `cask:xquartz` or `macos:>= :big_sur` |
| `buildDependencies` | Build-time-only dependencies. Always empty for casks |
| `caveats` | The post-install message `brew` prints, if any |
| `deprecated`, `deprecationReason` | Whether the package is deprecated and why |
| `disabled`, `disableReason` | Whether it has been disabled outright and why |
| `installs30d`, `installs90d`, `installs365d` | Official install counts over each window |
| `installsOnRequest30d/90d/365d` | Installs where the user asked for this package **by name**, excludes installs pulled in as someone else's dependency. `null` for casks |
| `sourceUrl` | The tarball, git URL or `.dmg` the package installs from |
| `pageUrl` | Its page on formulae.brew.sh |
| `scrapedAt` | Run timestamp |

A `RUN_SUMMARY` record in the key-value store holds per-run counts, the filters used, and any package that failed.

### Why the analytics matter

`installs` counts every install, including the ones dragged in as a dependency. `installsOnRequest` counts only the ones a human typed. The gap between them is the interesting signal:

| Package | 90-day installs | 90-day on request | Read |
|---|---|---|---|
| `gh` | 837,346 | 836,498 | Practically everyone installs it deliberately, a genuine end-user tool |
| `openssl@3` | 1,440,419 | 182,675 | Mostly a transitive dependency; the real user base is ~13% of the headline number |

Ranking developer tools on the headline number alone overstates infrastructure libraries by an order of magnitude.

### Common use cases

**Measure real developer-tool adoption on macOS.** Pull the top of the index by install count and diff it month over month.

```json
{
 "formulae": [],
 "resourceType": "both",
 "maxResults": 250,
 "includeAnalytics": true,
 "includeDependencies": false
}
```

**Track dependency chains for CLI tools.** Fetch a named set and read `dependencies` / `buildDependencies` to build the graph.

```json
{
 "formulae": ["ffmpeg", "imagemagick", "pandoc", "node", "python@3.14"],
 "resourceType": "formula",
 "includeDependencies": true
}
```

**Spot deprecated packages in a fleet.** Feed the output of `brew list` straight in and filter the dataset on `deprecated` or `disabled`.

```json
{
 "formulae": ["wget", "openssl@1.1", "python@3.8", "node", "ffmpeg"],
 "resourceType": "both",
 "includeAnalytics": false
}
```

### Quick start

Three packages, everything about them:

```json
{
 "formulae": ["wget", "ffmpeg", "node"]
}
```

The 100 most-installed macOS apps:

```json
{
 "formulae": [],
 "resourceType": "cask",
 "maxResults": 100
}
```

The complete Homebrew index, about 8,500 formulae:

```json
{
 "formulae": [],
 "resourceType": "formula",
 "maxResults": 0
}
```

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `formulae` | array | `[]` | Package names, or full formulae.brew.sh URLs. **Empty means index mode**, the whole catalogue. |
| `resourceType` | string | `"formula"` | `formula`, `cask` or `both`. |
| `includeAnalytics` | boolean | `true` | Adds the six install-count fields. |
| `includeDependencies` | boolean | `true` | Adds `dependencies` and `buildDependencies`. |
| `maxResults` | integer | `0` | Cap per resource type. `0` = no limit. |

Combinations that make sense together:

- **`formulae` filled + `resourceType: "both"`**: each name is looked up as a formula first, then as a cask. This is the right setting when your list came from `brew list` and mixes the two.
- **`formulae: []` + `maxResults` + `includeAnalytics: true`**: index mode sorts by 90-day installs before capping, so this is how you get "the top N", not "the first N alphabetically".
- **`formulae: []` + `includeDependencies: false`**: the fastest way to snapshot the whole catalogue for version tracking.
- **`resourceType: "both"` in index mode** downloads both dumps (~48 MB combined) and saves up to `maxResults` of each.

### Output example

```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` looks like this:

```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"
}
```

### Limits and behaviour

- **Two very different modes.** With names, each package is one small request and analytics arrive inline. Without names, the Actor downloads the complete `formula.json` (~31 MB) or `cask.json` (~17 MB) dump. That dump is only fetched in index mode, naming packages never pulls it.
- **The bulk dumps carry no analytics.** Homebrew publishes install counts only on the per-package endpoints and on separate leaderboard files. In index mode the Actor fetches those leaderboards (`analytics/install/{30d,90d,365d}.json` and `analytics/install-on-request/…`) and joins them to the dump by name, so `installs90d` is populated either way.
- **Install figures are totalled across variant commands.** Homebrew records variant install commands (`wget --HEAD`) as their own rows on the per-package endpoint, while the leaderboards used by index mode publish one already-summed row per package. The Actor sums the per-package rows to match, so an `installs90d` from a named run and one from an index run mean the same thing and can be diffed against each other.
- **Casks have no `installsOnRequest`.** Homebrew does not publish an on-request leaderboard for casks, so those three fields are `null` rather than guessed.
- **A missing package never aborts the run.** An unknown name returns HTTP 404, is recorded in `RUN_SUMMARY.failures`, and the run continues. The Actor only throws if nothing at all was saved.
- **Transient errors are retried.** 429 and 5xx get three attempts with linear backoff; a 404 is fatal and not retried.
- **Named lookups run 8 at a time**, so a list of 200 packages completes in a few seconds.
- **Analytics lag by design.** Homebrew aggregates daily and the windows are trailing, so a package released last week has small numbers regardless of its popularity.

### Finding a package name

It is exactly what you type after `brew install`. If you are not sure, the name is the last path segment of its page:

| Page you see | Name |
|---|---|
| `formulae.brew.sh/formula/wget` | `wget` |
| `formulae.brew.sh/cask/firefox` | `firefox` |
| `formulae.brew.sh/formula/python@3.14` | `python@3.14` |

You can paste the whole URL, the Actor extracts the name itself. On your own machine, `brew list --formula` and `brew list --cask` print exactly the values this Actor expects.

### API example

```bash
curl -X POST "https://api.apify.com/v2/acts/arman-bd~homebrew-formulae-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
 -H "Content-Type: application/json" \
 -d '{
 "formulae": ["wget", "ffmpeg", "node"],
 "resourceType": "formula",
 "includeAnalytics": true
 }'
```

### JavaScript example

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/homebrew-formulae-scraper').call({
 formulae: [],
 resourceType: 'cask',
 maxResults: 50,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const p of items) console.log(`${p.name} ${p.version}, ${p.installs90d.toLocaleString()} installs/90d`);
```

**Defaults:** 1 GB memory, 15 min timeout.

### FAQ

**Do I need a proxy?** No. Proxy configuration is not required to run this Actor.

**Do I need a Homebrew account or token?** No. You supply no credentials.

**What happens if a package is unavailable?** A 404 is recorded in `RUN_SUMMARY.failures` and the run continues with the rest.

**Why is `license` null for my package?** It is a cask. Homebrew records licences for formulae only.

**Why is `installsOnRequest90d` null?** Same reason, Homebrew publishes on-request analytics for formulae, not casks.

**Can I get third-party taps?** Yes for named lookups, as long as the tap is indexed on formulae.brew.sh. Index mode covers `homebrew/core` and `homebrew/cask` only, because that is what the bulk dumps contain.

**Does it tell me if a package is outdated?** It gives you the current published `version`; compare that against what you have installed. The `outdated` flag in Homebrew's raw JSON refers to the local machine and is meaningless server-side, so it is not exported.

**Can I schedule it?** Yes, it is designed for scheduled runs. Diff on `name` plus `version` for release tracking, or on `installs90d` for adoption trends.

**Can I integrate it with something else?** Yes, Apify API, client libraries, webhooks, scheduled runs, dataset exports (JSON/CSV/Excel) or MCP. Output is structured JSON.

# Actor input Schema

## `formulae` (type: `array`):

The names you type after `brew install`. 'wget', 'ffmpeg', 'firefox'. This is the last path segment of the package's page on formulae.brew.sh, so a full URL like https://formulae.brew.sh/formula/wget also works. Leave the list empty to fetch Homebrew's complete index instead.

## `resourceType` (type: `string`):

Formulae are command-line packages (wget, ffmpeg). Casks are macOS apps installed as .app bundles (firefox, docker-desktop). Choose 'both' to look each name up as a formula first and fall back to a cask. or, in index mode, to download both indexes.

## `includeAnalytics` (type: `boolean`):

Add Homebrew's official 30-, 90- and 365-day install counts, plus the 'installs on request' figures that exclude installs pulled in as a dependency. This is the data almost no other package registry publishes.

## `includeDependencies` (type: `boolean`):

Add the runtime and build-time dependency lists. Turn off for a much smaller dataset when you only care about versions and install counts.

## `maxResults` (type: `integer`):

Cap how many packages are saved for each resource type. In index mode with analytics on, results are sorted by 90-day installs first, so a cap of 100 returns the 100 most-installed packages rather than the first 100 alphabetically. Set 0 for no limit (about 8,500 formulae and 7,700 casks).

## Actor input object example

```json
{
  "formulae": [
    "wget",
    "https://formulae.brew.sh/cask/firefox"
  ],
  "resourceType": "formula",
  "includeAnalytics": true,
  "includeDependencies": true,
  "maxResults": 100
}
```

# Actor output Schema

## `items` (type: `string`):

Every record the run produced.

## `runsummary` (type: `string`):

The RUN_SUMMARY record from the run's key-value store.

# 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 = {
    "formulae": [
        "wget",
        "ffmpeg",
        "node"
    ],
    "resourceType": "formula",
    "maxResults": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("arman-bd/homebrew-formulae-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 = {
    "formulae": [
        "wget",
        "ffmpeg",
        "node",
    ],
    "resourceType": "formula",
    "maxResults": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("arman-bd/homebrew-formulae-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 '{
  "formulae": [
    "wget",
    "ffmpeg",
    "node"
  ],
  "resourceType": "formula",
  "maxResults": 100
}' |
apify call arman-bd/homebrew-formulae-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,arman-bd/homebrew-formulae-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/PJd0JKOChgd3ywB9i/builds/BIob5FPIoVwlWAfbA/openapi.json
