reader

vakra-devApache-2.0活跃维护

面向 AI 智能体的网页抓取与爬取基础设施,能把网页转成干净的 Markdown,并管理浏览器会话,绕过反爬限制,直接喂给下游模型。

Open source web infrastructure for AI. Scrape, crawl, and automate the web, clean markdown, browser sessions, ready for your agents.

v0.3.2发布于 2026-07-01 22:37 UTC562star41fork同步于 2026-11-07 07:34 UTC

怎么上手

由 README 提炼,执行前请确认
npm install @vakra-dev/reader
npx playwright install chromium
硬件要求按 README 说明

README 未说明硬件要求;需 Node.js >= 18,首次运行需安装 Playwright 自带 Chromium。

支持 Apple Silicon
下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

基于 Playwright 的开源网页抓取引擎,内置反爬与代理,为 AI 代理提供干净 Markdown

面向 AI 智能体的网页抓取与爬取基础设施,能把网页转成干净的 Markdown,并管理浏览器会话,绕过反爬限制,直接喂给下游模型。

核心能力

  • 用 scrape 批量抓取 URL 并输出干净 Markdown/HTML
  • 用 crawl 按 BFS 发现链接并限制深度与页数
  • browser 返回 CDP 端点,可接 Playwright/Puppeteer 控制隐身 Chrome
  • 内置 TLS 指纹伪装、navigator 伪造、WebRTC 掩蔽与 webdriver=false
  • 浏览器池自动回收、健康监控与分级代理池

容易踩的坑

首次运行前必须执行 npx playwright install chromium 下载浏览器;需要 Node.js 18 及以上版本;代理需自备主机、端口与账号密码。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v0.3.2
  1. v0.3.2最新正式版2026-07-01 · 2026-07-01 22:37 UTC

    Fixes

    • Fix stale proxy connections causing scrapes to hang after hours of uptime
      • Root cause: Node 19+ changed http.globalAgent to keepAlive: true, causing proxy-chain to pool TCP connections to upstream proxies that go stale
      • Fix: custom proxy tunnel with http.Agent({ keepAlive: false }) for fresh connections per request
      • Fixed in both playwright-pool.ts and browser-session.ts
    • Reduce withPage hard timeout from 90s to 60s (matches reader-api timeout)
    • SSR fallback for framework hydration error pages (Next.js, Nuxt)
    • Production request lifecycle logging (daemon, browser pool, scraper)

    Tests

    • 25/25 test files, 346/346 tests pass
  2. v0.3.12026-06-29 · 2026-06-29 23:37 UTC
    查看更新说明

    Fixes

    • Fix Chrome tab slot leaks causing production outages
      • Hard 90s timeout on withPage() prevents slots held forever
      • Detect client disconnect, abort orphaned scrapes
      • Wire abortSignal from daemon through scraper to Playwright engine
      • Race page.goto against abort signal for faster cancellation
      • Timeout page.close() at 5s to prevent hung CDP blocking slot release
      • Timeout drainLimit() at 60s to prevent blocked recovery cycles

    Improvements

    • Concurrent BFS crawling with single-pass scrape (3x faster crawls)
    • Fix undici 300s bodyTimeout killing long crawl requests

    Tests

    • 25/25 test files, 346/346 tests pass
    • 11 new tests for tab timeout, abort signal, disconnect detection
  3. v0.3.02026-06-27 · 2026-06-27 22:54 UTC
    查看更新说明

    What's Changed

    Breaking Changes

    • Replaced Hero/Ulixee engine with Playwright + fingerprint-generator + proxy-chain
    • Proxy modes renamed: stealth -> premium, auto removed entirely
    • Removed connectionToCore, IBrowserPool, BrowserPool, HeroBrowserPool exports
    • Removed re2 native dependency (replaced with native RegExp)
    • playwright-core moved to runtime dependency

    New Features

    • Browser infrastructure overhaul: --headless=new, fingerprint-generator, proxy-chain (no MITM)
    • Screenshot format support (formats: ["screenshot"])
    • Error code serialization in daemon responses
    • redactProxyUrl exported from proxy utilities

    Improvements

    • Removed all Hero/Ulixee code and dependencies (~4000 lines deleted)
    • Removed dead devDeps: chromedriver, selenium-webdriver, puppeteer-core
    • Updated all 18 documentation .md files
    • Fixed Window cast TypeScript warning

    Tests

    • 24/24 test files, 335/335 tests pass
  4. v0.2.02026-05-08 · 2026-05-08 01:03 UTC
    查看更新说明
    • chore: release v0.2.0
    • Rewrite release script and add version verification to publish workflow
    • Rename client variable to reader in cloud quick start example
    • Add browser sessions, Hero-only engine, tiered proxy pools, and updated docs
    • fix(reader): add safety layers to supermarkdown conversion
    • chore: add CITATION.cff and update citation key
  5. v0.1.22026-02-02 · 2026-02-02 02:12 UTC
    查看更新说明

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    The Problem

    Building agents that need web access is frustrating. You piece together Puppeteer, add stealth plugins, fight Cloudflare, manage proxies and it still breaks in production.

    Because production grade web scraping isn't about rendering a page and converting HTML to markdown. It's about everything underneath:

    LayerWhat it actually takes
    Browser architectureManaging browser instances at scale, not one-off scripts
    Anti-bot bypassCloudflare, Turnstile, JS challenges, they all block naive scrapers
    TLS fingerprintingReal browsers have fingerprints. Puppeteer doesn't. Sites know.
    Proxy infrastructureStandard vs premium, rotation strategies, sticky sessions
    Resource managementBrowser pooling, memory limits, graceful recycling
    ReliabilityRate limiting, retries, timeouts, caching, graceful degradation

    I built Reader, a production-grade web scraping engine on top of Playwright, with stealth and anti-detection built in from the ground up.

    The Solution

    Three primitives. That's it.

    import { ReaderClient } from "@vakra-dev/reader";
    import { chromium } from "playwright-core";
    
    const reader = new ReaderClient();
    
    // 1. Scrape URLs → clean markdown
    const result = await reader.scrape({ urls: ["https://example.com"] });
    console.log(result.data[0].markdown);
    
    // 2. Crawl a site → discover + scrape pages
    const pages = await reader.crawl({
      url: "https://example.com",
      depth: 2,
      scrape: true,
    });
    console.log(`Found ${pages.urls.length} pages`);
    
    // 3. Browser session → full Playwright/Puppeteer control with stealth
    const session = await reader.browser();
    const browser = await chromium.connectOverCDP(session.wsEndpoint);
    const page = browser.contexts()[0].pages()[0];
    await page.goto("https://example.com");
    console.log(await page.title());
    await session.close();
    

    All the hard stuff (browser pooling, anti-bot bypass, proxy rotation, retries) happens under the hood. You get clean markdown. Your agents get the web. And when you need full browser control, browser() gives you a stealthed Chrome that Playwright or Puppeteer can drive.

    [!TIP] If Reader is useful to you, a star on GitHub helps others discover the project.

    Features

    • Browser Sessions - Launch stealthed Chrome, connect Playwright/Puppeteer via CDP
    • Anti-Bot Bypass - TLS fingerprinting, navigator spoofing, WebRTC masking, webdriver=false
    • Clean Output - Markdown and HTML with automatic main content extraction
    • Smart Content Cleaning - Removes nav, headers, footers, popups, cookie banners
    • CLI & API - Use from command line or programmatically
    • Browser Pool - Auto-recycling, health monitoring, tiered proxy pools
    • Concurrent Scraping - Parallel URL processing with progress tracking
    • Website Crawling - BFS link discovery with depth/page limits
    • Tiered Proxies - Standard and premium proxy pools with health tracking

    Installation

    npm install @vakra-dev/reader
    

    Requirements: Node.js >= 18

    First run: Playwright bundles Chromium for all platforms. Install it with:

    npx playwright install chromium
    

    Quick Start

    Basic Scrape

    import { ReaderClient } from "@vakra-dev/reader";
    
    const reader = new ReaderClient();
    
    const result = await reader.scrape({
      urls: ["https://example.com"],
      formats: ["markdown", "html"],
    });
    
    console.log(result.data[0].markdown);
    console.log(result.data[0].html);
    
    await reader.close();
    

    Batch Scraping with Concurrency

    import { ReaderClient } from "@vakra-dev/reader";
    
    const reader = new ReaderClient();
    
    const result = await reader.scrape({
      urls: ["https://example.com", "https://example.org", "https://example.net"],
      formats: ["markdown"],
      batchConcurrency: 3,
      onProgress: (progress) => {
        console.log(`${progress.completed}/${progress.total}: ${progress.currentUrl}`);
      },
    });
    
    console.log(`Scraped ${result.batchMetadata.successfulUrls} URLs`);
    
    await reader.close();
    

    Crawling

    import { ReaderClient } from "@vakra-dev/reader";
    
    const reader = new ReaderClient();
    
    const result = await reader.crawl({
      url: "https://example.com",
      depth: 2,
      maxPages: 20,
      scrape: true,
    });
    
    console.log(`Discovered ${result.urls.length} URLs`);
    console.log(`Scraped ${result.scraped?.batchMetadata.successfulUrls} pages`);
    
    await reader.close();
    

    Browser Session

    Launch a stealthed Chrome and control it with Playwright or Puppeteer. The browser has anti-bot stealth active (webdriver=false, navigator spoofing, WebRTC masking). Your existing scripts just work.

    import { ReaderClient } from "@vakra-dev/reader";
    import { chromium } from "playwright-core";
    
    const reader = new ReaderClient();
    
    // Create a browser session - returns a CDP WebSocket URL
    const session = await reader.browser();
    
    // Connect Playwright (one-line change from a local script)
    const browser = await chromium.connectOverCDP(session.wsEndpoint);
    const context = await browser.newContext();
    const page = await context.newPage();
    
    // Use Playwright normally - full stealth active
    await page.goto("https://news.ycombinator.com/");
    console.log(await page.title());
    
    await browser.close();
    await session.close();
    await reader.close();
    

    Also works with Puppeteer:

    import { connect } from "puppeteer-core";
    
    const browser = await connect({ browserWSEndpoint: session.wsEndpoint });
    

    With Proxy

    import { ReaderClient } from "@vakra-dev/reader";
    
    const reader = new ReaderClient();
    
    const result = await reader.scrape({
      urls: ["https://example.com"],
      formats: ["markdown"],
      proxy: {
        type: "premium",
        host: "proxy.example.com",
        port: 8080,
        username: "username",
        password: "password",
        country: "us",
      },
    });
    
    await reader.close();
    

    With Tiered Proxy Pools

    Configure standard (datacenter, fast) and premium (residential, anti-bot) proxy tiers:

    import { ReaderClient } from "@vakra-dev/reader";
    
    const reader = new ReaderClient({
      proxyPools: {
        standard: [
          { url: "http://user:pass@dc-proxy1:8080" },
          { url: "http://user:pass@dc-proxy2:8080" },
        ],
        premium: [{ url: "http://user:pass@res-proxy1:8080" }],
      },
    });
    
    const result = await reader.scrape({
      urls: ["https://example.com"],
      proxyMode: "standard", // or "premium" for anti-bot sites
    });
    
    await reader.close();
    

    Or via environment variables:

    PROXY_STANDARD=http://user:pass@dc1:8080,http://user:pass@dc2:8080
    PROXY_PREMIUM=http://user:pass@res1:8080
    

    With Browser Pool Configuration

    import { ReaderClient } from "@vakra-dev/reader";
    
    const reader = new ReaderClient({
      browserPool: {
        size: 5, // 5 browser instances
        retireAfterPages: 50, // Recycle after 50 pages
        retireAfterMinutes: 15, // Recycle after 15 minutes
      },
      verbose: true,
    });
    
    const result = await reader.scrape({
      urls: manyUrls,
      batchConcurrency: 5,
    });
    
    await reader.close();
    

    CLI Reference

    Daemon Mode

    For multiple requests, start a daemon to keep browser pool warm:

    # Start daemon with browser pool
    npx reader start --direct-pool-size 5
    
    # All subsequent commands auto-connect to daemon
    npx reader scrape https://example.com
    npx reader crawl https://example.com -d 2
    
    # Check daemon status
    npx reader status
    
    # Stop daemon
    npx reader stop
    
    # Force standalone mode (bypass daemon)
    npx reader scrape https://example.com --standalone
    

    reader scrape <urls...>

    Scrape one or more URLs.

    # Scrape a single URL
    npx reader scrape https://example.com
    
    # Scrape with multiple formats
    npx reader scrape https://example.com -f markdown,html
    
    # Scrape multiple URLs concurrently
    npx reader scrape https://example.com https://example.org -c 2
    
    # Save to file
    npx reader scrape https://example.com -o output.md
    
    OptionTypeDefaultDescription
    -f, --format <formats>string"markdown"Output formats (comma-separated: markdown,html)
    -o, --output <file>stringstdoutOutput file path
    -c, --concurrency <n>number1Parallel requests
    -t, --timeout <ms>number30000Request timeout in milliseconds
    --batch-timeout <ms>number300000Total timeout for entire batch operation
    --proxy <url>string-Proxy URL (e.g., http://user:pass@host:port)
    --user-agent <string>string-Custom user agent string
    --show-chromeflag-Show browser window for debugging
    --no-main-contentflag-Disable main content extraction (include full page)
    --include-tags <sel>string-CSS selectors for elements to include (comma-separated)
    --exclude-tags <sel>string-CSS selectors for elements to exclude (comma-separated)
    -v, --verboseflag-Enable verbose logging

    reader crawl <url>

    Crawl a website to discover pages.

    # Crawl with default settings
    npx reader crawl https://example.com
    
    # Crawl deeper with more pages
    npx reader crawl https://example.com -d 3 -m 50
    
    # Crawl and scrape content
    npx reader crawl https://example.com -d 2 --scrape
    
    # Filter URLs with patterns
    npx reader crawl https://example.com --include "blog/*" --exclude "admin/*"
    
    OptionTypeDefaultDescription
    -d, --depth <n>number1Maximum crawl depth
    -m, --max-pages <n>number20Maximum pages to discover
    -s, --scrapeflag-Also scrape content of discovered pages
    -f, --format <formats>string"markdown"Output formats when scraping (comma-separated)
    -o, --output <file>stringstdoutOutput file path
    --delay <ms>number1000Delay between requests in milliseconds
    -t, --timeout <ms>number-Total timeout for crawl operation
    --include <patterns>string-URL patterns to include (comma-separated regex)
    --exclude <patterns>string-URL patterns to exclude (comma-separated regex)
    --proxy <url>string-Proxy URL (e.g., http://user:pass@host:port)
    --user-agent <string>string-Custom user agent string
    --show-chromeflag-Show browser window for debugging
    -v, --verboseflag-Enable verbose logging

    reader browser

    Launch a browser session with a CDP WebSocket endpoint.

    # Create a session (prints wsEndpoint, blocks until Ctrl+C)
    npx reader browser create
    
    # Create with options
    npx reader browser create --timeout 60000 --show-chrome
    
    # List active sessions (daemon mode)
    npx reader browser list
    
    # Stop a session
    npx reader browser stop <sessionId>
    
    OptionTypeDefaultDescription
    --proxy <url>string-Proxy URL
    -t, --timeout <ms>number300000Session lifetime in milliseconds
    --show-chromeflag-Show browser window
    --standaloneflag-Force standalone mode
    -v, --verboseflag-Enable verbose logging

    API Reference

    ReaderClient

    The recommended way to use Reader. Manages the Playwright browser pool lifecycle automatically.

    import { ReaderClient } from "@vakra-dev/reader";
    
    const reader = new ReaderClient({ verbose: true });
    
    // Scrape
    const result = await reader.scrape({ urls: ["https://example.com"] });
    
    // Crawl
    const crawlResult = await reader.crawl({ url: "https://example.com", depth: 2 });
    
    // Browser session
    const session = await reader.browser();
    // → session.wsEndpoint for Playwright/Puppeteer
    
    // Close when done (optional - auto-closes on exit)
    await reader.close();
    
    Constructor Options
    OptionTypeDefaultDescription
    verbosebooleanfalseEnable verbose logging
    showChromebooleanfalseShow browser window for debugging
    browserPoolBrowserPoolConfigundefinedBrowser pool configuration (size, recycling)
    proxyPoolsProxyPoolConfigundefinedTiered proxy pools (standard + premium)
    proxiesProxyConfig[]undefinedArray of proxies for rotation (legacy)
    proxyRotationstring"round-robin"Rotation strategy: "round-robin" or "random"
    BrowserPoolConfig
    OptionTypeDefaultDescription
    sizenumber2Number of browser instances in pool
    retireAfterPagesnumber100Recycle browser after N page loads
    retireAfterMinutesnumber30Recycle browser after N minutes
    maxQueueSizenumber100Max pending requests in queue
    Methods
    MethodDescription
    scrape(options)Scrape one or more URLs
    crawl(options)Crawl a website to discover pages
    browser(options?)Launch a stealthed browser session (CDP WebSocket)
    start()Pre-initialize browser pool (optional)
    isReady()Check if client is initialized
    close()Close client and release resources

    scrape(options): Promise<ScrapeResult>

    Scrape one or more URLs. Can be used directly or via ReaderClient.

    OptionTypeRequiredDefaultDescription
    urlsstring[]Yes-Array of URLs to scrape
    formatsArray<"markdown" | "html">No["markdown"]Output formats
    onlyMainContentbooleanNotrueExtract only main content (removes nav/header/footer)
    includeTagsstring[]No[]CSS selectors for elements to keep
    excludeTagsstring[]No[]CSS selectors for elements to remove
    waitForSelectorstringNo-CSS selector to wait for before page is loaded
    timeoutMsnumberNo30000Request timeout in milliseconds
    batchConcurrencynumberNo1Number of URLs to process in parallel
    batchTimeoutMsnumberNo300000Total timeout for entire batch operation
    proxyProxyConfigNo-Proxy configuration object
    proxyTierProxyTierNo-Proxy tier: "standard" or "premium"
    onProgressfunctionNo-Progress callback: ({ completed, total, currentUrl }) => void
    verbosebooleanNofalseEnable verbose logging
    showChromebooleanNofalseShow Chrome window for debugging

    Returns: Promise<ScrapeResult>

    interface ScrapeResult {
      data: WebsiteScrapeResult[];
      batchMetadata: BatchMetadata;
    }
    
    interface WebsiteScrapeResult {
      markdown?: string;
      html?: string;
      metadata: {
        baseUrl: string;
        finalUrl?: string; // Present if URL redirected
        totalPages: number;
        scrapedAt: string;
        duration: number;
        website: WebsiteMetadata;
      };
    }
    
    interface BatchMetadata {
      totalUrls: number;
      successfulUrls: number;
      failedUrls: number;
      scrapedAt: string;
      totalDuration: number;
      errors?: Array<{ url: string; error: string }>;
    }
    

    crawl(options): Promise<CrawlResult>

    Crawl a website to discover pages.

    OptionTypeRequiredDefaultDescription
    urlstringYes-Single seed URL to start crawling from
    depthnumberNo1Maximum depth to crawl
    maxPagesnumberNo20Maximum pages to discover
    scrapebooleanNofalseAlso scrape full content of discovered pages
    delayMsnumberNo1000Delay between requests in milliseconds
    timeoutMsnumberNo-Total timeout for entire crawl operation
    includePatternsstring[]No-URL patterns to include (regex strings)
    excludePatternsstring[]No-URL patterns to exclude (regex strings)
    formatsArray<"markdown" | "html">No["markdown"]Output formats for scraped content
    scrapeConcurrencynumberNo2Number of URLs to scrape in parallel
    proxyProxyConfigNo-Proxy configuration object
    userAgentstringNo-Custom user agent string
    verbosebooleanNofalseEnable verbose logging
    showChromebooleanNofalseShow Chrome window for debugging

    Returns: Promise<CrawlResult>

    interface CrawlResult {
      urls: CrawlUrl[];
      scraped?: ScrapeResult;
      metadata: CrawlMetadata;
    }
    
    interface CrawlUrl {
      url: string;
      title: string;
      description: string | null;
    }
    
    interface CrawlMetadata {
      totalUrls: number;
      maxDepth: number;
      totalDuration: number;
      seedUrl: string;
    }
    

    browser(options?): Promise<BrowserSession>

    Launch a stealthed Chrome and return a CDP WebSocket URL for Playwright/Puppeteer.

    OptionTypeRequiredDefaultDescription
    proxyProxyConfigNo-Proxy configuration
    proxyTierProxyTierNo-Proxy tier: "standard" or "premium"
    showChromebooleanNofalseShow browser window
    timeoutMsnumberNo300000Session lifetime (auto-closes after)
    verbosebooleanNofalseEnable verbose logging

    Returns: Promise<BrowserSession>

    interface BrowserSession {
      sessionId: string; // Unique session identifier
      wsEndpoint: string; // CDP WebSocket URL for Playwright/Puppeteer
      createdAt: string; // ISO timestamp
      close(): Promise<void>; // Close session and release resources
    }
    

    Stealth features active on all sessions:

    • navigator.webdriver = false (via --disable-blink-features=AutomationControlled)
    • Proxy routing through authenticated proxy forwarder (if configured)
    • Isolated user profile per session (no cookie/state leaks)

    ProxyConfig

    OptionTypeRequiredDefaultDescription
    urlstringNo-Full proxy URL (takes precedence over other fields)
    type"standard" | "premium"No-Proxy type
    hoststringNo-Proxy host
    portnumberNo-Proxy port
    usernamestringNo-Proxy username
    passwordstringNo-Proxy password
    countrystringNo-Country code for residential proxies (e.g., 'us', 'uk')

    Daemon Mode (Production)

    For production servers, start the daemon once and all scrape/crawl/browser requests share the warm browser pool:

    import { ReaderClient } from "@vakra-dev/reader";
    
    // Create once at startup
    const reader = new ReaderClient({
      proxyPools: {
        standard: [{ url: "http://user:pass@dc-proxy:8080" }],
        premium: [{ url: "http://user:pass@res-proxy:8080" }],
      },
    });
    
    // Reuse for all requests
    const result = await reader.scrape({ urls: ["https://example.com"] });
    
    // Graceful shutdown
    process.on("SIGTERM", () => reader.close());
    

    How It Works

    Anti-Bot Bypass

    Reader uses Playwright with stealth enhancements for advanced anti-detection:

    1. Fingerprint Generation - Randomized browser fingerprints via fingerprint-generator
    2. Navigator Spoofing - webdriver=false, device memory, hardware concurrency
    3. Stealth Scripts - Injected scripts that patch automation indicators
    4. WebRTC IP Masking - Prevents IP leaks through WebRTC connections
    5. Proxy Chaining - Traffic routed through proxy-chain for IP management

    Browser Pool

    • Tiered Proxy Pools - Separate standard and premium proxy pools
    • Auto-Recycling - Browsers recycled after 100 requests or 30 minutes
    • Health Tracking - Auto-benches failed proxies for 5 minutes, revives on recovery
    • Per-Proxy Concurrency - Limits concurrent requests per proxy URL (default: 2)

    HTML to Markdown: supermarkdown

    Reader uses supermarkdown for HTML to Markdown conversion - a sister project we built from scratch specifically for web scraping and LLM pipelines.

    Why we built it:

    When you're scraping the web, you encounter messy, malformed HTML that breaks most converters. And when you're feeding content to LLMs, you need clean output without artifacts or noise. We needed a converter that handles real-world HTML reliably while producing high-quality markdown.

    What supermarkdown offers:

    FeatureBenefit
    Written in RustNative performance with Node.js bindings via napi-rs
    Full GFM supportTables, task lists, strikethrough, autolinks
    LLM-optimizedClean output designed for AI consumption
    Battle-testedHandles malformed HTML from real web pages
    CSS selectorsInclude/exclude elements during conversion

    supermarkdown is open source and available as both a Rust crate and npm package:

    # npm
    npm install @vakra-dev/supermarkdown
    
    # Rust
    cargo add supermarkdown
    

    Check out the supermarkdown repository for examples and documentation.

    Server Deployment

    Reader uses a real Chromium browser under the hood. On headless Linux servers (VPS, EC2, etc.), you need to install Chrome's system dependencies:

    # Debian/Ubuntu
    sudo apt-get install -y libnspr4 libnss3 libatk1.0-0 libatk-bridge2.0-0 \
      libcups2 libxcb1 libatspi2.0-0 libx11-6 libxcomposite1 libxdamage1 \
      libxext6 libxfixes3 libxrandr2 libgbm1 libcairo2 libpango-1.0-0 libasound2
    

    This is the same requirement that Puppeteer and Playwright have on headless Linux. macOS, Windows, and Linux desktops already have these libraries.

    For Docker and production deployment guides, see the deployment documentation.

    Documentation

    Full documentation is available at reader.dev/docs, including guides for scraping, crawling, proxy configuration, browser pool management, and deployment.

    Examples

    ExampleDescription
    Basic ScrapingSimple single-URL scraping
    Batch ScrapingConcurrent multi-URL scraping
    Crawl WebsiteCrawl and discover pages
    Browser Session (Playwright)Navigate, extract data, screenshot
    Browser Session (Actions)Click, type, search, wait for elements
    Browser Session (Puppeteer)Puppeteer via connect({ browserWSEndpoint })
    Browser Session (Raw CDP)Direct CDP WebSocket commands
    Browser Pool ConfigConfigure browser pool for high throughput
    Proxy PoolProxy rotation with multiple proxies
    Cloudflare BypassScrape Cloudflare-protected sites
    All FormatsOutput in markdown and html
    AI ToolsOpenAI, Anthropic, LangChain integrations

    Development

    # Install dependencies
    npm install
    
    # Run linting
    npm run lint
    
    # Format code
    npm run format
    
    # Type check
    npm run typecheck
    
    # Find TODOs
    npm run todo
    

    Contributing

    Contributions welcome! Please see CONTRIBUTING.md for guidelines.

    License

    Apache 2.0 - See LICENSE for details.

    Citation

    If you use Reader in your research or project, please cite it:

    @software{reader.dev,
      author = {Kaul, Nihal},
      title = {Reader: Open-source, production-grade web scraping engine built for LLMs},
      year = {2026},
      publisher = {GitHub},
      url = {https://github.com/vakra-dev/reader}
    }
    

    Support