Skip to content
Back to skills

Cf Crawl

BSecurity

Use when crawling a website with Cloudflare Browser Rendering /crawl to ingest web content.

  • 44 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 5, 2026
developmentpythongobashapi

Works with

  • cursor
  • cli
  • api

Security analysis

B80/100
  • criticalSends environment variables or credentials to an external URL
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned September 30, 2026

npx -y skills add Charles5277/nuxt-supabase-starter --skill cf-crawl --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Cf Crawl?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Cf Crawl
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/charles5277-cf-crawl/badge)](https://www.skillsdirectory.com/skills/charles5277-cf-crawl)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: cf-crawl
description: "Use when crawling a website with Cloudflare Browser Rendering /crawl to ingest web content."
---

<!-- clade-skill-scope: project -->

# Cloudflare Website Crawler

Crawl a site through Cloudflare Browser Rendering's `/crawl` endpoint via the `cf` CLI and save pages as local markdown.

## Arguments (`/cf-crawl <url> …`)

- First positional: URL (ask if missing)
- `--limit N` / `-l N`: max pages (default 20; the API's own default is 10)
- `--depth N` / `-d N`: max depth (default 100000)
- `--include "p1,p2"` / `--exclude "p1,p2"`: URL patterns (`*` excludes `/`, `**` includes it; exclude wins)
- `--no-render`: static HTML fetch, faster and cheaper, misses JS-rendered content
- `--merge`: combine output into one markdown file
- `--output DIR` / `-o DIR`: output dir (default `.crawl-output`)
- `--source sitemaps|links|all`: page discovery (default all)
- `--since DATE`: only pages modified since DATE (ISO date or Unix seconds) → API `modifiedSince` (`date -d "2026-03-10" +%s` on Linux, `date -j -f "%Y-%m-%d" "2026-03-10" +%s` on macOS)

## Step 1: Credentials and `cf`

Starting and polling go through the `cf` CLI (`cf-cli` skill); Step 4's paging loop is an in-script client and stays on the HTTP API (`cloudflare-workers.md` § 8). `command -v cf` must succeed; otherwise ask the user to install it (see `cf-cli` skill — via mise, never `npm i -g`) and stop.

Need `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_API_TOKEN` (token permission "Browser Rendering - Edit"); `cf` reads both from the environment. Check env first, then `.env`, `.env.local`, `~/.env`:

```bash
if [ -z "$CLOUDFLARE_ACCOUNT_ID" ] || [ -z "$CLOUDFLARE_API_TOKEN" ]; then
  for envfile in .env .env.local "$HOME/.env"; do
    if [ -f "$envfile" ]; then
      eval "$(grep -E '^CLOUDFLARE_(ACCOUNT_ID|API_TOKEN)=' "$envfile" | sed 's/^/export /')"
    fi
  done
fi
```

Still missing or empty → ask the user to add both to the project `.env` and stop.

## Step 2: Start the crawl

```bash
cf browser-run crawl create --body '{
  "url": "<TARGET_URL>",
  "limit": <NUMBER_OF_PAGES>,
  "formats": ["markdown"]
}'
```

Fill the body from the parsed arguments, adding only what the user passed: `--depth` → `depth`, `--source` → `source`, `--no-render` → `"render": false`, `--include`/`--exclude` → `"options": { "includePatterns": [...], "excludePatterns": [...] }`, `--since` → `"modifiedSince": <UNIX_TIMESTAMP>` (see Core Parameters). The printed result is the job ID.

## Step 3: Poll every 5 seconds

```bash
cf browser-run crawl get <JOB_ID> --limit 1 | jq -r '"Status: \(.status) | Finished: \(.finished)/\(.total)"'
```

Statuses: `running`, `completed`, `cancelled_due_to_timeout` (7-day limit), `cancelled_due_to_limits`, `errored`.

## Step 4: Retrieve and save

Records page with `?status=completed&limit=50&cursor=<CURSOR>`; with `modifiedSince`, `?status=skipped` lists unchanged pages, and robots-blocked URLs show `"status": "disallowed"`.

```bash
# Create output directory
mkdir -p .crawl-output

# Fetch and save all pages
python3 -c "
import json, os, re, sys, urllib.request

account_id = os.environ['CLOUDFLARE_ACCOUNT_ID']
api_token = os.environ['CLOUDFLARE_API_TOKEN']
job_id = '<JOB_ID>'
base = f'https://api.cloudflare.com/client/v4/accounts/{account_id}/browser-rendering/crawl/{job_id}'
outdir = '.crawl-output'
os.makedirs(outdir, exist_ok=True)

cursor = None
total_saved = 0

while True:
    url = f'{base}?status=completed&limit=50'
    if cursor:
        url += f'&cursor={cursor}'

    req = urllib.request.Request(url, headers={
        'Authorization': f'Bearer {api_token}'
    })
    with urllib.request.urlopen(req) as resp:
        data = json.load(resp)

    records = data.get('result', {}).get('records', [])
    if not records:
        break

    for rec in records:
        page_url = rec.get('url', '')
        md = rec.get('markdown', '')
        if not md:
            continue
        # Convert URL to filename
        name = re.sub(r'https?://', '', page_url)
        name = re.sub(r'[^a-zA-Z0-9]', '_', name).strip('_')[:120]
        filepath = os.path.join(outdir, f'{name}.md')
        with open(filepath, 'w') as f:
            f.write(f'<!-- Source: {page_url} -->\n\n')
            f.write(md)
        total_saved += 1

    cursor = data.get('result', {}).get('cursor')
    if cursor is None:
        break

print(f'Saved {total_saved} pages to {outdir}/')
"
```

## Parameter Reference

### Core Parameters

| Parameter       | Type    | Default    | Description                                               |
| --------------- | ------- | ---------- | --------------------------------------------------------- |
| `url`           | string  | (required) | Starting URL to crawl                                     |
| `limit`         | number  | 10         | Max pages to crawl (up to 100,000)                        |
| `depth`         | number  | 100,000    | Max link depth from starting URL                          |
| `formats`       | array   | ["html"]   | Output formats: `html`, `markdown`, `json`                |
| `render`        | boolean | true       | `true` = headless browser, `false` = fast HTML fetch      |
| `source`        | string  | "all"      | Page discovery: `all`, `sitemaps`, `links`                |
| `maxAge`        | number  | 86400      | Cache validity in seconds (max 604800)                    |
| `modifiedSince` | number  | -          | Unix timestamp; only crawl pages modified after this time |

### Options Object

| Parameter              | Type    | Default | Description                                    |
| ---------------------- | ------- | ------- | ---------------------------------------------- |
| `includePatterns`      | array   | []      | Wildcard patterns to include (`*` and `**`)    |
| `excludePatterns`      | array   | []      | Wildcard patterns to exclude (higher priority) |
| `includeSubdomains`    | boolean | false   | Follow links to subdomains                     |
| `includeExternalLinks` | boolean | false   | Follow external links                          |

### Advanced Parameters

| Parameter             | Type   | Description                                                |
| --------------------- | ------ | ---------------------------------------------------------- |
| `jsonOptions`         | object | AI-powered structured extraction (prompt, response_format) |
| `authenticate`        | object | HTTP basic auth (username, password)                       |
| `setExtraHTTPHeaders` | object | Custom headers for requests                                |
| `rejectResourceTypes` | array  | Skip: image, media, font, stylesheet                       |
| `userAgent`           | string | Custom user agent string                                   |
| `cookies`             | array  | Custom cookies for requests                                |

## Notes

- Respects robots.txt including crawl-delay
- Free plan: 10 minutes of browser time per day; results kept 14 days; max 10 MB per page
- Use `render: false` for static sites to save browser time

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.