go-stare: Screenshot 10,000 URLs Without Melting Your Server
Hook
Taking screenshots of 10,000 URLs with Puppeteer will consume 20GB of RAM and take hours. go-stare does it with 500MB and finishes before your coffee gets cold.
Context
Anyone who's tried to screenshot hundreds or thousands of URLs knows the pain: Puppeteer spawns Chrome instances that devour memory, Selenium tests that run overnight, and servers that groan under the weight of headless browsers. Security researchers running reconnaissance on large attack surfaces, QA teams validating visual regressions across hundreds of pages, and bug bounty hunters processing subdomain lists all face the same problem: existing screenshot tools are heavyweight beasts designed for interactive automation, not bulk capture.
The culprit isn't Chrome itself—it's the abstraction layers we pile on top. Tools like Puppeteer, Playwright, and Selenium provide rich APIs for browser automation, but they carry significant overhead: Node.js runtimes, WebSocket management layers, complex state machines, and full process spawning for each browser context. For the simple task of "load page, wait for render, capture pixels," we're using a sledgehammer. go-stare takes a different approach: it talks directly to Chrome via the DevTools Protocol, skipping the middleware entirely. The result is a tool that's fast enough for real-time pipelines, light enough to run on minimal infrastructure, and simple enough to integrate anywhere.
Technical Insight
At its core, go-stare is a CDP client that establishes a WebSocket connection to a Chrome instance running with remote debugging enabled. Instead of spawning new browser processes, it reuses a single Chrome instance and creates isolated target pages for each URL. This architectural choice is what gives go-stare its speed advantage—no process creation overhead, no cold starts, just direct protocol communication.
To use go-stare, you first launch Chrome with remote debugging enabled on a specific port:
# Start Chrome with remote debugging
chrome --headless --remote-debugging-port=9222
# In another terminal, screenshot a single URL
go-stare -u https://example.com -o screenshot.png
# Or process multiple URLs from stdin
cat urls.txt | go-stare -c 10 -d ./screenshots/
The -c flag controls concurrency, allowing you to balance speed against resource usage. With -c 10, go-stare maintains 10 concurrent page captures, reusing the same Chrome instance across all operations. This is drastically different from Puppeteer's typical pattern of spawning browser contexts or pages with isolated processes.
Under the hood, go-stare uses the Page.navigate and Page.captureScreenshot CDP commands. Here's what the protocol flow looks like when capturing a screenshot:
// Simplified example of CDP communication
// 1. Create a new target (tab)
target := cdp.CreateTarget(url)
// 2. Navigate and wait for load
cdp.Send("Page.navigate", map[string]interface{}{
"url": url,
})
cdp.WaitForEvent("Page.loadEventFired")
// 3. Capture screenshot
result := cdp.Send("Page.captureScreenshot", map[string]interface{}{
"format": "png",
"quality": 90,
"captureBeyondViewport": true,
})
// 4. Decode base64 and save
imageData := base64.Decode(result["data"])
ioutil.WriteFile("output.png", imageData, 0644)
// 5. Close target to free resources
cdp.CloseTarget(target.ID)
The real power emerges when you integrate go-stare into reconnaissance pipelines. Security researchers often chain together multiple tools: subfinder for subdomain enumeration, httpx to probe for live hosts, and then screenshot tools to visually identify interesting targets. go-stare's stdin support makes this seamless:
# Full recon pipeline
subfinder -d example.com -silent | \
httpx -silent -status-code -title | \
awk '{print $1}' | \
go-stare -c 20 -d ./screenshots/ -t 15
This pipeline can process thousands of URLs with minimal memory overhead. The -t 15 flag sets a 15-second timeout per page, ensuring that slow-loading or broken sites don't stall the entire pipeline. Each screenshot is saved with a sanitized filename based on the URL, making organization straightforward.
The concurrency model deserves special attention. go-stare uses Go's goroutines with a semaphore pattern to limit concurrent CDP connections. This prevents overwhelming the Chrome instance while maximizing throughput. Unlike Puppeteer's page pool implementations that require careful tuning and can deadlock under load, go-stare's approach is simpler and more predictable—it's just controlled goroutine spawning with a channel-based semaphore.
One underappreciated aspect is the capture quality. Because go-stare uses the same rendering engine as interactive Chrome, you get pixel-perfect screenshots with full CSS, JavaScript execution, and modern web features like CSS Grid and Web Components. There's no "phantom rendering" quirks or outdated WebKit issues—just Chrome's actual rendering output. The captureBeyondViewport option even allows full-page screenshots without the viewport stitching that can introduce artifacts in other tools.
Gotcha
The biggest gotcha is the external Chrome dependency. Unlike self-contained tools, go-stare requires you to manage a Chrome instance separately. This means additional setup steps, potential port conflicts, and the need to monitor Chrome's health. If Chrome crashes or becomes unresponsive, go-stare can't auto-recover—you'll need external process management (systemd, supervisord, Docker health checks) to ensure reliability. For one-off scripts, this is acceptable friction. For production systems, it requires thoughtful architecture.
The documentation and feature set also reflect go-stare's early stage. Advanced CDP capabilities like cookie injection, custom headers, authentication handling, or viewport customization aren't exposed through command-line flags. You can't easily screenshot authenticated pages or set custom user agents without modifying the Chrome launch parameters. Similarly, there's no built-in retry logic for network failures or timeout handling beyond the simple -t flag. If you need to screenshot SPAs that require specific wait conditions (like waiting for an API call to complete), you're limited to fixed timeouts rather than smarter waiting strategies. For many reconnaissance workflows this is fine—you're capturing whatever loads within the timeout window—but for QA or monitoring use cases, you'll likely need something more sophisticated.
Verdict
Use if: You're processing hundreds or thousands of URLs in security reconnaissance, bug bounty programs, or automated testing pipelines where speed and resource efficiency matter more than feature completeness. go-stare excels when you need to integrate screenshots into existing command-line workflows, care about memory consumption, and want something that actually finishes before lunch. It's perfect for disposable jobs on minimal infrastructure—think Docker containers with 512MB RAM capturing screenshots at scale. Skip if: You need comprehensive browser automation with authentication flows, JavaScript manipulation, or complex interaction sequences. If you're building a visual regression testing suite that requires precise wait conditions, element selection, or screenshot comparison, reach for Puppeteer or Playwright instead. Similarly, if the idea of managing an external Chrome instance feels like unnecessary complexity for your use case, self-contained tools like gowitness offer better operational simplicity at the cost of some performance.