# Recreation.gov Campground Scraper (Campsites & Amenities) (`steadfetch/recreation-gov-campground-extractor`) Actor

Extract public Recreation.gov campground records: name, geo, address, amenities, aggregate rating, published offer and campsite inventory. For camping market research, site databases and travel planning tools. HTML scraping only - no /api calls, Crawl-delay 10 respected.

- **URL**: https://apify.com/steadfetch/recreation-gov-campground-extractor.md
- **Developed by:** [Steadfetch](https://apify.com/steadfetch) (community)
- **Categories:** Travel, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 result delivereds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## steadfetch/recreation-gov-campground-extractor

Extract public campground records from [Recreation.gov](https://www.recreation.gov) HTML pages with
[Apify](https://apify.com) pay-per-event pricing and schema-validated output.

**Data Source: Recreation.gov**

### What it does

The Actor accepts numeric campground ids from Recreation.gov and reads each public
`/camping/campgrounds/{id}` HTML page. It parses the page's schema.org `Campground` JSON-LD block plus
the campsite inventory linked in the HTML, and saves one validated dataset item per campground:

- name, page title, description and canonical URL;
- aggregate rating and review count (no review text, no reviewer data);
- postal address, coordinates and parent gateway/forest;
- published amenities, offer price/currency/availability and time zone;
- campsite ids, labels and URLs.

Missing values are returned as `null` or `[]`. Nothing is inferred: if Recreation.gov does not
publish a value, the field is empty. The Actor does **not** promise a date-by-date availability
calendar; that data lives behind `/api/*`, which this Actor never calls.

#### Input

```json
{
  "campgroundIds": ["233462"],
  "maxResults": 500,
  "includeCampsites": true
}
```

- `campgroundIds` (required): numeric ids from sitemap URLs of the form
  `https://www.recreation.gov/camping/campgrounds/{id}`. Duplicate ids are fetched once and never
  billed twice.
- `maxResults`: 1 to 5000 campgrounds saved to the dataset (default 500).
- `includeCampsites`: include the campsite inventory published on the page (default `true`).

#### Example output

Real output for campground `233462` (campsites and amenities shortened for readability):

```json
{
  "url": "https://www.recreation.gov/camping/campgrounds/233462",
  "title": "Coeur D'ALENE, Melvern Lake - Recreation.gov",
  "scrapedAt": "2026-10-20T16:05:00.000Z",
  "campground_id": "233462",
  "name": "Coeur D'ALENE",
  "source_url": "https://www.recreation.gov/camping/campgrounds/233462",
  "description": "Coeur d'Alene Campground is situated on Melvern Lake on the eastern edge of the Flint Hills of Kansas.",
  "rating": 4.4653463,
  "review_count": 101,
  "timezone": "America/Chicago",
  "address": {
    "street": "31408 Coeur dAlene Parkway",
    "city": "Melvern",
    "region": "Kansas",
    "postal": "66510",
    "country": "United States"
  },
  "geo": { "latitude": 38.4976028, "longitude": -95.7179111 },
  "parent_place": { "name": "Melvern Lake", "url": "https://www.recreation.gov/gateways/269" },
  "amenities": ["Beach Access", "Boat Ramp", "Dump Station", "Marina", "Picnic Tables"],
  "offer": {
    "price": 16,
    "currency": "USD",
    "availability": "https://schema.org/InStock",
    "sku": "233462"
  },
  "campsites": [
    {
      "campsite_id": "12686",
      "label": "Site 43, Loop CDEA, Type Standard Electric",
      "url": "https://www.recreation.gov/camping/campsites/12686"
    },
    {
      "campsite_id": "13695",
      "label": "Site 38, Loop CDEA, Type Standard Electric",
      "url": "https://www.recreation.gov/camping/campsites/13695"
    }
  ]
}
```

Every item is validated against `.actor/dataset_schema.json` before it reaches the dataset. If an
item does not validate, the run fails instead of delivering malformed data.

### Pricing

Pay-per-event, with the two synthetic Apify events declared in `.actor/pay_per_event.json`:

| Event | Charged when | Price |
| --- | --- | --- |
| `apify-actor-start` | once per run start (synthetic, charged automatically) | 0.00005 USD per run (Apify-recommended default) |
| `apify-default-dataset-item` | once per campground saved to the dataset (synthetic, charged automatically) | 0.004 USD per campground = 4 USD per 1,000 results |

There is no custom per-item event and no `Actor.charge()` call: the platform charge
`apify-default-dataset-item` already bills each result, so adding another event would bill users
twice. A run that delivers no campground is only charged the start event.

#### Cost per 1,000 results

Measured locally on 2026-10-20 with the committed fixtures and Node 24:

- Extraction CPU: **8.4 ms per campground** (1000 extractions in 8.40 s), about **0.00012 USD per
  1,000 campgrounds** at 0.20 USD/GB-hour and 256 MB.
- Wall time dominates: `robots.txt` requires `Crawl-delay: 10`, so 1,000 campgrounds take at least
  **2.78 hours** of wall time. At 256 MB that is **0.694 GB-hour = 0.139 USD per 1,000 results**.
- Expected infrastructure cost: **about 0.14 USD per 1,000 campgrounds**. Gross margin at 4.00 USD
  per 1,000 is about 96.5 percent.

The platform cost of a run that fetches fewer pages and emits fewer items is proportionally lower.
Local measurement spends no Apify credit.

### Legal and robots.txt compliance

`robots.txt` (checked 2026-10-20) applies `User-agent: *`, disallows `/api`, `/api/*`, `/account/*`
and `/cart*`, and declares `Crawl-delay: 10`. This Actor:

- fetches **only** `/camping/campgrounds/{id}` HTML pages and **never** `/api/*`;
- runs one request at a time and waits at least 10 seconds after each response, including retries;
- sends an honest `User-Agent: steadfetch-recreation-gov-campground-extractor/0.1
  (+https://apify.com/steadfetch; contact: https://apify.com/steadfetch)`;
- collects no personal data: aggregate ratings and facility/campsite metadata only. Review text,
  reviewer identities and the facility telephone number published in JSON-LD are deliberately not
  collected.

Recreation.gov's [Use and Share Our Data](https://www.recreation.gov/use-our-data) page states:
"The federal recreation data, and derivative information, used on Recreation.gov is available for
free to anyone who envisions a variety of uses." Attribution is included in the README and in this
Actor's Store listing as "Data Source: Recreation.gov". The full evidence, including literal quotes
and saved responses, is in `docs/legal-gate.md` in the
repository.

### FAQ

**Does the Actor need a login or paid subscription to Recreation.gov?**
No. It reads public HTML pages only and never bypasses logins, paywalls or CAPTCHAs.

**Does it return availability for specific dates?**
No. Date-by-date availability on Recreation.gov is served from `/api/*`, which `robots.txt`
disallows. This Actor returns the offer and campsite inventory published in the HTML, and does not
claim to be an availability calendar.

**Why is it so slow compared with other Actors?**
`robots.txt` requires a 10 second crawl delay. Reliability and compliance take priority over speed;
1,000 campgrounds need at least 2.78 hours.

**What happens if a page has no JSON-LD or no campground id?**
The page is skipped and logged. The Actor never invents a record from partial data.

**Is the output schema stable?**
Yes. New fields are added additively and required fields never change. Every item keeps the common
Steadfetch envelope (`url`, `scrapedAt`) plus `campground_id` and `source_url`.

**Can I request another source?**
Yes. Open an issue in the Steadfetch Actors repository describing the public page you want
extracted.

### Maintenance

This Actor is **tested daily**: automated runs keep the source reachable and the output schema
valid. Every push runs lint, typecheck, offline fixture tests, the Steadfetch publish gate and
`apify-cli validate-schema` in CI. Schema changes are announced through repository releases.

### Development

```bash
npm install
npm test                # lint + typecheck + offline fixture tests
npm run publish:check   # Steadfetch publish gate
npm run build           # tsc build to dist/
```

Fixtures are real pages saved from Recreation.gov on 2026-10-20. Refetch them (with the same 10 s
delay, no Apify credit, no token) with
`docs/evidence/fixtures/fetch-fixtures.sh`.

# Actor input Schema

## `campgroundIds` (type: `array`):

Numeric campground ids from recreation.gov sitemap URLs of the form https://www.recreation.gov/camping/campgrounds/{id}. Duplicates are ignored and not billed.

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

Maximum number of campgrounds saved to the dataset. One dataset item is one billable result.

## `includeCampsites` (type: `boolean`):

Include the campsite inventory published on each campground page. When disabled, campsites is an empty list.

## Actor input object example

```json
{
  "campgroundIds": [
    "233462"
  ],
  "maxResults": 10,
  "includeCampsites": true
}
```

# Actor output Schema

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

Dataset items validated against the campground item schema (fields).

# 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 = {
    "campgroundIds": [
        "233462"
    ],
    "maxResults": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadfetch/recreation-gov-campground-extractor").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 = {
    "campgroundIds": ["233462"],
    "maxResults": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("steadfetch/recreation-gov-campground-extractor").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 '{
  "campgroundIds": [
    "233462"
  ],
  "maxResults": 10
}' |
apify call steadfetch/recreation-gov-campground-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadfetch/recreation-gov-campground-extractor"
        }
    }
}
```

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/GxgoZOuSbLPT36wWn/builds/KhNRTyzG7ESb0FJVR/openapi.json
