Why Scraping Contact Pages at Scale is Hard
Modern business websites are a contact extraction nightmare. The same company may protect its email behind Cloudflare's email obfuscation (data-cfemail), render its contact form dynamically via JavaScript, bury phone numbers in image-alt text, and list its LinkedIn company page only on a hidden /about-us subpage.
Cloudflare Email Obfuscation
Millions of sites encode mailto links with Cloudflare's data-cfemail attribute. Raw regex scrapers return garbage characters. Bolt Scraper includes a full CF email decoder and handles all obfuscation variants.
Contact Data on Subpages
Most businesses do not list their direct email on the homepage. It lives on /contact, /about, or /team. A homepage-only scraper misses 60–80% of available contact data.
Noise vs. Signal Emails
Raw scrapers return noreply@, support@, postmaster@, and image filenames masquerading as emails. Manual filtering adds hours to every export.
This is precisely why teams building B2B prospecting data at scale don't write custom scrapers for each site — they use a purpose-built API that handles all of these edge cases automatically. The Bolt Scraper Website Scraper API accepts up to 10,000 URLs per job and returns a structured JSON record for each domain, including filtered emails, phones, and extracted social profile links.
Sending Bulk URLs in a Single POST Request
The core endpoint accepts an array of URLs and immediately queues them for parallel cloud extraction. You do not need to loop through domains one by one — the entire list ships in one call, and the scraper handles concurrency for you.
POST https://boltscraper.com/api/v1/website/jobs Authorization: Bearer bs_YOUR_API_KEY Content-Type: application/json { "urls": [ "https://example-co.com", "https://demo-roofing.com", "https://sunsetshingles.net", // ... up to 10,000 URLs ] }
The API responds immediately with a job_id. Scraping runs asynchronously in the cloud. You can either poll for completion or — far more efficiently — register a webhook that fires as soon as your batch finishes.
{
"success": true,
"job_id": "job_a3f8c2...",
"job_name": "Q4 Outreach — Roofing Companies",
"total_urls": 2847,
"status": "scraping"
} URL Normalization
The API automatically normalizes your input: strips trailing slashes, adds https:// to bare domains, and deduplicates identical URLs before queuing. You can pass raw exports from Apollo or Crunchbase without pre-processing.
Deep Subpage Crawling — Finding /contact, /about, and /team
For every URL submitted, the scraper performs a multi-stage crawl. It starts on the root page and then automatically discovers and follows the contact, about, and team subpages for that domain. This is where 60–80% of direct sales contacts live.
Subpage Discovery
The crawler parses every outgoing link from the root page and classifies them using keyword pattern matching:
contactPageUrl— /contact, /contact-us, /reach-usaboutPageUrl— /about, /about-us, /our-storyteamPageUrl— /team, /people, /leadership, /staff
Multi-Stage Extraction
Once subpages are discovered, a second scrape pass fetches each one and merges the results. Emails found on /contact are ranked higher than emails regex-matched from body text — so the most intentionally-placed contacts always surface first in email_1.
The resulting record for each URL contains up to three deduplicated emails and two phone numbers, plus all discovered social links, already classified and ready to use:
{
"url": "https://example-co.com",
"page_title": "Example Co",
"meta_description": "Expert roofing services in Phoenix, AZ",
// Up to 3 deduplicated emails
"email_1": "[email protected]",
"email_2": "[email protected]",
"email_3": "",
// Up to 2 phone numbers
"phone_1": "+15550100192",
"phone_2": "",
// Social media — direct profile URLs
"linkedin_url": "https://linkedin.com/company/acme-roofers",
"facebook_url": "https://facebook.com/example-co",
"instagram_url": "https://instagram.com/example_co",
"twitter_url": "",
"youtube_url": "",
"status": "success",
"scraped_at": "2026-09-19T11:30:00.000Z"
} Smart Email Filtering — Direct Contacts Only
Raw scrapers return hundreds of junk email addresses per domain: server notification addresses, CMS system addresses, placeholder addresses from contact form plugins. The Bolt Scraper engine applies a multi-layer filtering stack automatically before returning any email.
| Email Pattern | Action | Reason |
|---|---|---|
noreply@, no-reply@, donotreply@ | ❌ Filtered | Auto-sending addresses that never receive replies |
postmaster@, mailer-daemon@ | ❌ Filtered | Server-level system addresses |
Dummy domains: example.com, test.com, yourdomain.com | ❌ Filtered | Template/CMS placeholder emails |
Files mistaken for emails: [email protected] | ❌ Filtered | CSS/JS filenames that match email regex |
[email protected], [email protected] | ✅ Kept | Real business contacts on company domains |
| Cloudflare-encoded emails (decoded) | ✅ Kept | CF obfuscation reversed before filtering |
This means every email in your results file is a real, deliverable business address that was deliberately placed on the site. Your cold outreach bounce rate drops to near zero before you even hit a verification service.
End-to-End Example: Enriching 500 Domains
Here's a complete Python example that submits 500 domains, polls for completion, and then saves the enriched contacts to a CSV:
import requests, time, csv API_KEY = "bs_YOUR_API_KEY" BASE_URL = "https://boltscraper.com/api/v1/website" HEADERS = {"Authorization": f"Bearer {API_KEY}"} # 1 — Submit the batch domains = ["example-co.com", "demo-roofing.com", ...] # your domain list r = requests.post(f"{BASE_URL}/jobs", headers=HEADERS, json={ "job_name": "Q4 Roofing Enrichment", "urls": domains }) job_id = r.json()["job_id"] print(f"Job created: {job_id}") # 2 — Poll until complete while True: status = requests.get(f"{BASE_URL}/jobs/{job_id}", headers=HEADERS).json() job = status["job"] print(f" {job['processed_count']}/{job['total_urls']} processed") if job["status"] == "COMPLETED": break time.sleep(15) # 3 — Fetch results and save to CSV results_csv = requests.get( f"{BASE_URL}/jobs/{job_id}/download", headers=HEADERS ) with open("enriched_contacts.csv", "wb") as f: f.write(results_csv.content) print(f"✅ Results saved to enriched_contacts.csv")
Prefer Webhooks Over Polling
For production workflows, replace the polling loop above with a webhook. Read the Website Scraper Webhooks guide to set up an automated webhook callback that delivers a signed CSV download URL when your job completes — no polling loop, no wasted compute.
Part of the Website Scraper Playbook
Explore all five guides in this topic cluster to build a complete contact enrichment and monitoring pipeline:
Start Extracting Business Contacts at Scale
Submit 10,000 domains in one API call. Get business emails, phones, LinkedIn, Instagram, and Facebook links — automatically filtered and ready for your outreach tool.
Instant cloud setup • CSV & JSON export • Test live website scraper for free & view docs
Frequently Asked Questions
data-email attributes, and obfuscated "name [at] domain.com" formats. For pure client-side-rendered SPAs that return empty HTML, results will be limited to what's available in the initial server response and metadata. info@, admin@, and hello@ are kept — these are legitimate business inboxes. Only non-deliverable system addresses (noreply@, postmaster@, mailer-daemon@) and placeholder domains (example.com, yourdomain.com) are filtered out automatically. website_job.completed webhook event on each. Credits are deducted per URL processed. GET /api/v1/website/jobs/:jobId/download to receive a CSV file with all result columns. Webhook payloads also include a signed download_csv_url that's valid for 24 hours and works from n8n, Make.com, or any automation platform.
What Social Platforms Are Extracted
Beyond emails, the scraper parses every outgoing link in the page HTML and classifies social profile URLs by platform. All 12 major platforms are detected and returned as separate fields in your result:
B2B Social Channels
Direct Messaging Channels