Skip to content

Anonymous scraping

Not every task needs a managed account. Public pages — news, docs, pricing tables, public registries — don't need login, don't deserve a persistent profile, and shouldn't touch your signed-in sessions. Ghostvault's ephemeral sessions are built for exactly this: a throwaway anti-detect browser that opens, scrapes, and disappears.

Why this matters

A managed account is expensive state: a locked fingerprint, real session cookies, a profile directory you have to protect. Spinning one up to read a public Hacker News thread is wasteful and risky — you'd be exposing a sensitive session to an arbitrary third-party site for no reason.

Ephemeral sessions flip every default:

  • No account, no profile, no DB row — nothing persists after close.
  • Random identity per session — a fresh Camoufox fingerprint each time.
  • Tuned for scraping — headless, images blocked, short idle timeout.

Once an ephemeral session is open, every browser tool works on it unchanged. There is no separate "scraping API" — gv_get_page_content, gv_get_page_links, gv_click_element, gv_scroll, gv_screenshot all just work, because the ephemeral session becomes the active target automatically.

The lifecycle

Two tools, always used as a pair:

Tool Role
gv_open_ephemeral Launch a throwaway Camoufox + navigate to a URL. Returns a session_id. The session becomes the active target.
gv_close_ephemeral Close the session, kill the browser, wipe the tempdir. Frees the slot.
# Open → do stuff → close. Always close.
await gv.open_ephemeral(url="https://news.ycombinator.com")

content = await gv.get_page_content(include_screenshot=True)
links  = await gv.get_page_links(limit=20)
await gv.scroll(direction="down", amount=3)

await gv.close_ephemeral()   # frees the browser + removes the tempdir

Always close your ephemeral sessions

Each session is a full Firefox process. If you forget gv_close_ephemeral, the session lingers until the 5-minute idle timeout (see defaults below) or until you hit the concurrency cap. In a long-running script, close explicitly — don't rely on the idle timer.

Defaults — tuned for scraping

Ephemeral sessions have different defaults than managed accounts. Managed accounts are headful (so you can complete 2FA) and load images; ephemeral sessions are the opposite.

Setting Default Why
headless true Scraping doesn't need a visible window
block_images true Faster text scraping; smaller memory footprint
humanize false Read-only scraping doesn't need cursor movement
auto_close_minutes 5 Abandoned sessions clean themselves up
max_concurrent 3 Cap on simultaneous ephemeral sessions (each is a full Firefox process)

Override any of these per call:

# Visible browser + keep images for screenshot-based scraping
await gv.open_ephemeral(
    url="https://example.com/dashboard",
    headless=False,
    block_images=False,
)

Or change the defaults globally via env vars (see Advanced → Environment variables):

GHOSTVAULT_EPHEMERAL_HEADLESS=false
GHOSTVAULT_EPHEMERAL_BLOCK_IMAGES=false
GHOSTVAULT_EPHEMERAL_AUTO_CLOSE_MINUTES=15
GHOSTVAULT_EPHEMERAL_MAX_CONCURRENT=5

All browser tools work unchanged

This is the key design point. There is no gv_get_page_content_ephemeral. Once gv_open_ephemeral returns, the session is the active target and every browser tool operates on it exactly as it would on a managed account:

await gv.open_ephemeral(url="https://example.com")

await gv.open_url("https://example.com/about")         # navigate
await gv.get_page_content(include_screenshot=True)     # read text + see it
await gv.get_page_links(limit=50)                      # collect links
await gv.click_element("Read more")                    # follow a link
await gv.fill_input("Search", "pricing")               # fill a public form
await gv.press_key("Enter")                            # submit
await gv.scroll(direction="down", amount=2)            # scroll
await gv.screenshot(full_page=True)                    # capture

await gv.close_ephemeral()

A complete one-shot scrape

This is the pattern from examples/scripts/scrape_public_page.py — the simplest realistic scrape:

import asyncio, json
from ghostvault_sdk import GhostvaultClient

async def scrape(url: str) -> None:
    async with GhostvaultClient() as gv:
        # 1. Open a throwaway browser + navigate.
        await gv.open_ephemeral(url)

        # 2. Read text + screenshot (vision support).
        await gv.get_page_content(include_screenshot=True, max_chars=3000)

        # 3. Collect visible links.
        await gv.get_page_links(limit=10)

        # 4. Close — frees the browser, removes the tempdir.
        await gv.close_ephemeral()

asyncio.run(scrape("https://news.ycombinator.com"))

Nothing persisted. No account was created, no profile written, no DB row inserted. The session is gone.

Multi-page scraping on one session

You don't need to re-open for every page. Keep the session open, navigate with gv_open_url or gv_click_element, and only close at the end. This is the pattern in examples/scripts/scrape_with_pagination.py:

async def scrape_paginated(start_url: str, pages: int) -> None:
    async with GhostvaultClient() as gv:
        await gv.open_ephemeral(url=start_url)

        for i in range(pages):
            await gv.get_page_content(max_chars=5000)     # read current page
            await gv.scroll(direction="down", amount=2)   # engage
            # Click the "Next" button — same session, new page
            await gv.click_element("Next")

        await gv.close_ephemeral()   # one close at the end

This keeps you under the max_concurrent cap and avoids relaunching Firefox for every page.

Mixing ephemeral + managed accounts

The two modes coexist cleanly. Use ephemeral for public research, then switch to a managed account when you need authenticated access — Gmail, Drive, any login-gated site.

# 1. Scrape a public page anonymously (no account touched)
await gv.open_ephemeral(url="https://example.com/public-pricing")
data = await gv.get_page_content()
await gv.close_ephemeral()

# 2. Now switch to a managed account for authenticated work
await gv.open_account("acc_work123")
await gv.open_url("https://mail.google.com")   # signed-in inbox

The active target tracks whichever you opened last. If you have both an ephemeral session and a managed account open, calls go to whichever was opened most recently — or you can make it explicit with gv_switch_account / by re-opening the target you want.

Don't mix sensitive accounts with arbitrary scrapes

If you're about to navigate to an untrusted URL, close any open managed account first (or use an ephemeral session instead). There's no reason to drag your real session cookies onto a random site.

When to use which

Scenario Use
Scrape a public page (news, docs, pricing) gv_open_ephemeral
Fill a public form (contact, signup-free download) gv_open_ephemeral
Multi-page crawl of a public site gv_open_ephemeral + loop
Captcha-protected public scrape target gv_open_ephemeral with humanize=True (see Human-like typing & clicks)
Login-required site (Gmail, Drive, SSO app) Managed account — see Reading Gmail
Read Gmail / access Google services Managed account
Anything where you need to stay signed in Managed account

Captcha-protected scrape targets

Some public pages run reCAPTCHA v3 or similar behavioral checks. The defaults (humanize=False) are fine for plain text scraping, but for protected targets, enable humanization for the session and follow the warm-up pattern from Human-like typing & clicks → reCAPTCHA v3 warm-up:

await gv.open_ephemeral(
    url="https://protected-site.com",
    humanize=True,           # enable cursor humanization for this session
    block_images=False,      # some captcha scripts key off image loads
)
await gv.scroll(direction="down", amount=2, humanize=True)
# ...then scrape as usual

Troubleshooting

Symptom Fix
gv_open_ephemeral errors with "max concurrent reached" You have 3 sessions open (default cap). Close one with gv_close_ephemeral, or raise GHOSTVAULT_EPHEMERAL_MAX_CONCURRENT.
Session disappeared mid-scrape The 5-minute idle timer fired. For long scrapes, raise GHOSTVAULT_EPHEMERAL_AUTO_CLOSE_MINUTES, or call a browser tool periodically to reset the idle clock.
Screenshots look broken / missing images block_images=True is the default. Pass block_images=False if you need accurate screenshots.
Site loads but text is empty Heavy JS-rendered page. Add await gv.scroll(...) and a short asyncio.sleep to let it render, then re-read.
gv_close_ephemeral says "no active session" It was already closed (by you, or by the idle timer). Safe to ignore.