Field note / go

Why I rewrote Firecrawl in Go

The architectural choices behind Purify: a Go service, an HTTP-first path, browser fallback, and one system for scrape, crawl, map, and extract.

goweb-scrapingarchitecturefirecrawl

Publication record: capabilities, examples, and pricing reflect the date above. See the current product and pricing pages for the live offering.

Purify began while I was building an agent that needed to read public web pages. The hosted tools I tried were useful, but I wanted a smaller operational surface for self-hosting and a response contract designed around clean content for search and agents.

This article describes that architecture. It is not a current benchmark against Firecrawl, and it should not be read as a claim about Firecrawl's present packaging or performance. Check every project's current documentation before comparing deployments.

Why Go

The main reason was distribution. A Go build can produce one executable that carries the API and extraction service without requiring the operator to assemble an application runtime first.

That does not mean “no dependencies” or “no operations.” Browser rendering still requires a browser environment, and production use still needs logging, capacity planning, network policy, updates, and incident handling. The single binary simply gives those concerns a smaller application boundary.

Go also provides a straightforward concurrency model for a network-heavy service. The important design goal is bounded work: timeouts, cancellation, and explicit limits matter more than slogans about unlimited concurrency.

HTTP first, browser when needed

Many editorial, documentation, and reference pages include their meaningful content in the initial HTML. Starting with a direct HTTP path avoids launching a browser when rendering is unnecessary.

Purify's conceptual flow is:

Request
  ├─ HTTP retrieval produces useful content → clean and return
  └─ Page requires rendering → use the browser path, then clean and return

This architecture also has a failure mode. Some pages return enough initial markup to look valid while inserting important content later. Detection therefore needs conservative fallbacks, and callers must be able to identify incomplete or inaccessible results.

The API exposes timing as separate fields so a client can inspect the path instead of relying on an opaque speed claim:

  • timing.navigation_ms;
  • timing.cleaning_ms;
  • timing.total_ms.

Measure those fields on your own sources and deployment region.

Cleaning is a policy decision

Purify tries to isolate useful content and preserve semantic structure. More aggressive cleaning can reduce model input, but it can also remove a sidebar, embedded post, visualization, or related link that mattered to the task.

That is why the response includes token diagnostics for the individual request:

  • tokens.original_estimate;
  • tokens.cleaned_estimate;
  • tokens.savings_percent.

These fields describe the response, not a universal reduction guarantee.

Earlier versions of this article contained exact latency and token comparisons without a published reproducibility manifest. Those tables have been removed. Historical figures retained in the dedicated token article are labeled recorded reference until the fixtures, versions, raw artifacts, and runner are published together.

One system, several operations

Purify is no longer a single-page-only experiment. The current product surface is organized around:

  • scrape for one page;
  • batch scrape for a set of pages;
  • crawl for following pages within a defined scope;
  • map for URL discovery;
  • extract for schema-shaped output using a supplied model key.

The same operations are exposed through REST and the purify-mcp connector. Crawl and map still require careful scoping, deduplication, rate policy, and error handling; their presence is not a promise that every site can or should be crawled.

MCP is a binary, not an npm shortcut

The supported configuration points an MCP client to the purify-mcp executable:

{
  "mcpServers": {
    "purify": {
      "command": "/path/to/purify-mcp",
      "env": {
        "PURIFY_API_URL": "https://purify.verifly.pro",
        "PURIFY_API_KEY": "YOUR_API_KEY"
      }
    }
  }
}

Replace the command with the absolute path to the binary. The connector currently provides scrape, batch, crawl, map, and structured-extract tools. See the MCP setup guide for the current configuration.

Current scrape request

The hosted route accepts POST JSON rather than the legacy GET-with-query example:

curl -X POST "https://purify.verifly.pro/api/v1/scrape" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/article"}'

Read cleaned output from content; use the nested tokens and timing objects described above for request diagnostics.

Trade-offs worth keeping visible

  • Dynamic pages: browser rendering cannot guarantee access to authenticated, blocked, or highly interactive content.
  • Extraction loss: cleaning heuristics may remove useful context, so important workloads need regression fixtures.
  • Crawl responsibility: discovery must respect scope, access policy, and resource limits.
  • Self-hosting work: owning the binary also means owning availability, updates, observability, and abuse controls.
  • Young ecosystem: a smaller project has fewer integrations and fewer accumulated edge-case reports than an established platform.

When Purify fits

Purify is a sensible candidate when you want clean content and structured data for search or agents, a compact self-hosted core, or an MCP/REST interface covering scrape, crawl, map, and extract.

It may not fit when you need an authenticated browser session tailored to one site, a managed workflow feature Purify does not currently expose, or a mature ecosystem more than you need deployment control.

The source is available under Apache 2.0 at github.com/Easonliuliang/purify. Evaluate it with your own URLs, keep the failures, and treat those fixtures as part of the product.

Related reading

Put clean web data into your next system.

Start on the hosted service or inspect the open-source core first.

Try Purify →