99.99% Uptimeby Solari Status ↗

Lead Enrichment With Browser Agents (2026 Guide)

·

5 min read

·

Summarize with AI
ON THIS PAGE

The webinar export reached the ops channel on Friday with a name and a work email in every row, and almost nothing else. The routing rule needed industry, company size and region before a rep could pick a lead up, and the data provider had come back blank for most of the smaller companies.

Lead enrichment fills those empty columns, and a browser agent does it by reading each company's own website the way a researcher would, saving every value next to the URL it came from. Solari runs that agent on one account, with a cloud browser per lead, stealth and proxies for sites that block automation, and a sandbox to clean the rows before they reach the CRM. This guide walks through the pipeline from a half-empty spreadsheet to rows a rep can trust.

Decide the Fields Before the Agent Opens a Page

The schema comes first, because it decides which pages each session opens. A useful enrichment schema is short and checkable: industry, a headcount band, headquarters country, whether the company publishes pricing, and whether it is hiring. Each field maps to the page most likely to state it, such as the careers page for hiring, so the agent never crawls the whole site.

Every value also carries the source URL and the quoted line the agent read. A rep who doubts "fintech" can open the page and see the sentence, and a value with no quote stays blank rather than guessed.

Most of these pages render with JavaScript, so a plain HTTP fetch often returns an empty shell and the agent needs a real browser. Find content with Playwright locators such as getByRole("main") rather than long CSS chains, which break when a site redesigns. With the schema fixed, each lead becomes a small, separate job.

Give Each Lead Its Own Browser Session

Since the unit of work is a lead, each lead gets its own Solari session, a real browser driven with the standard Playwright API and closed in a finally block. A hung page on one company's site then stays inside that session and never stalls the rest of the list.

The plan sets what each browser hour costs, and the figure below shows the rate per plan from the Solari pricing docs.

Solari browser hour by plan

An enrichment visit is a few page loads and a close, so the hourly rate matters more than the plan fee. Professional runs 150 concurrent browsers, enough for a weekly signup list. The loop below sizes a worker pool to the plan and tries the default browser first.

const limit = pLimit(PLAN_CONCURRENCY)

async function visit(lead: Lead, stealth: boolean) {
  const browser = await client.launch(stealth ? { stealth: true, proxy: "us" } : {})
  try {
    const page = await browser.newPage()
    const res = await page.goto(lead.aboutUrl)
    if (res?.status() === 403) return { lead, blocked: true }
    return { lead, text: await page.getByRole("main").innerText() }
  } finally {
    await browser.close()
  }
}

const enrich = async (lead: Lead) => {
  const first = await visit(lead, false)
  return first.blocked ? visit(lead, true) : first
}

await Promise.all(leads.map((lead) => limit(() => enrich(lead))))
const limit = pLimit(PLAN_CONCURRENCY)

async function visit(lead: Lead, stealth: boolean) {
  const browser = await client.launch(stealth ? { stealth: true, proxy: "us" } : {})
  try {
    const page = await browser.newPage()
    const res = await page.goto(lead.aboutUrl)
    if (res?.status() === 403) return { lead, blocked: true }
    return { lead, text: await page.getByRole("main").innerText() }
  } finally {
    await browser.close()
  }
}

const enrich = async (lead: Lead) => {
  const first = await visit(lead, false)
  return first.blocked ? visit(lead, true) : first
}

await Promise.all(leads.map((lead) => limit(() => enrich(lead))))
const limit = pLimit(PLAN_CONCURRENCY)

async function visit(lead: Lead, stealth: boolean) {
  const browser = await client.launch(stealth ? { stealth: true, proxy: "us" } : {})
  try {
    const page = await browser.newPage()
    const res = await page.goto(lead.aboutUrl)
    if (res?.status() === 403) return { lead, blocked: true }
    return { lead, text: await page.getByRole("main").innerText() }
  } finally {
    await browser.close()
  }
}

const enrich = async (lead: Lead) => {
  const first = await visit(lead, false)
  return first.blocked ? visit(lead, true) : first
}

await Promise.all(leads.map((lead) => limit(() => enrich(lead))))

Pace each company's site as well: read its robots.txt, space out visits to the same host and stop after repeated refusals. Some sites still refuse the default browser, and that is where stealth comes in.

Switch to Stealth Only When a Site Blocks

The default Solari browser is the fastest and cheapest mode, so it should handle every lead it can. The stealth docs put the rule plainly: "start with the default and switch to stealth the moment you see blocks or challenge pages." The loop above retries only the blocked leads that way.

Stealth is also the switch that turns on a proxy. Passing proxy: "us" routes the session through a rotating residential IP, and the proxies docs bill that traffic at $4.00 per GB on Professional. Setting blockHeavy at session create keeps video and large images off the proxy, so an about page never costs more than the field it fills.

Stealth needs a paid plan, and stealth sessions carry their own cap on top of concurrent browsers, 50 on Professional, so the retry pool should be smaller than the main one. Once every lead has a page read, the raw text is ready to become rows.

Clean and Match the Rows in a Sandbox

Turning page text into fields is code work rather than browsing, so it moves to a Solari sandbox on the same account and API key. The agent writes the findings in as JSON with files.write and runs a Python script through runCode, which keeps variables between calls, so the model can fix a parsing rule without reloading the batch.

The script maps "US" and "USA" to a single country code, turns headcount phrases into bands and drops any value without its quote. It also handles record linkage, matching each row to the existing CRM record by domain, so a lead who signed up twice does not become two accounts. Only then does the batch write back, every field with its source attached.

Choosing the Right Setup

Lead enrichment with browser agents comes down to four decisions: a short schema where every value carries its quote, a session per lead sized to the plan, stealth and proxies only for sites that block, and a sandbox to clean and match rows before the CRM sees them. Done in that order, the export that arrived half empty goes back to sales with fields a rep can check in a click. The Solari introduction shows how browsers and sandboxes share one account.

Get Started With Solari

Start with last week's signups and the about page alone, then add careers and pricing pages once the first fields check out. Solari browsers are ready in 8 ms and Solari is about 2x cheaper than Browserbase per browser hour, so a list of short visits stays fast and cheap. Create a free Solari account and enrich a small batch before moving the full list to a paid plan.

Share this article

Give every agent
a place to work

Browsers, sandboxes and desktops from one API. Start free and scale when you need to.

Planet horizon at sunrise
opsra logo

The Fastest AI Infrastructure

Unsubscribe anytime. See our Privacy Policy.

Copyright © 2026 Solari

Engineered by Pinetree

opsra logo

The Fastest AI Infrastructure

Unsubscribe anytime. See our Privacy Policy.

Copyright © 2026 Solari

Engineered by Pinetree

opsra logo

The Fastest AI Infrastructure

Unsubscribe anytime. See our Privacy Policy.

Copyright © 2026 Solari

Engineered by Pinetree