Website Contact Scraper β Emails, Phones & Socials
Pricing
from $2.00 / 1,000 domains
Website Contact Scraper β Emails, Phones & Socials
Extract emails, phone numbers, WhatsApp numbers and social media profiles from any list of websites. Get one clean row per website: contact pages are crawled first, junk is filtered out and duplicates are merged. Export to Excel, CSV or JSON, or connect via API.
Pricing
from $2.00 / 1,000 domains
Rating
5.0
(1)
Developer
Emiliano Mastragostino
Maintained by CommunityActor stats
6
Bookmarked
1.1K
Total users
66
Monthly active users
8 days ago
Last modified
Categories
Features
Share
π Website contact scraper for emails, phone numbers and social profiles
Website Contact Scraper finds public email addresses, phone numbers, WhatsApp numbers and social media profiles on company websites. Submit one website or a list of thousands and get one clean row per website, ready for your CRM, lead list or spreadsheet.
- β One consolidated result for each website
- β Predictable costs
- β Contact pages crawled first
- β Filtered and deduplicated contact data
- β Progressive output suitable for bulk runs
- β Validated phone numbers, no look-alikes
- β Automation-ready status and diagnostics
What you get
A simplified view of the output, one row per website:
| Start URL | Status | Emails | Phones | Socials |
|---|---|---|---|---|
https://www.bakery.example/ | completed | info@bakery.example | +390212345678 | https://instagram.com/bakery https://facebook.com/bakery |
https://lawfirm.example/ | completed | office@lawfirm.example privacy@lawfirm.example | +442079460958 +442079460959 | https://linkedin.com/company/lawfirm |
https://studio.example/ | completed | β | β | β |
https://offline.example/ | failed | β | β | β |
studio.examplewas crawled successfully but publishes no contacts.offline.examplecould not be reached; its record includes the error and whether a retry makes sense.
Export the results to Excel, CSV, JSON, XML or HTML, or read them through the Apify API. The full output example shows every field.
No configuration is needed: add your website URLs and run. Scraping websites from a single country? Set Default phone country (for example US, GB or IT) to get more phone numbers, all in international format.
π What contact data does the Actor extract?
| Data | Output |
|---|---|
| Emails | Filtered, normalized, and deduplicated public email addresses |
| Phone numbers | Validated numbers, in E.164 when the country is known |
| WhatsApp numbers | International numbers (digits only) extracted from WhatsApp links |
| Social profiles | Canonicalized LinkedIn, Facebook, Instagram, X, YouTube, TikTok, GitHub, Pinterest, Telegram |
| Website metadata | Canonical start URL and redirected start-page URL |
| Crawl diagnostics | Status, page counters, timing, termination reason, and sanitized errors |
β¨ Why use Website Contact Scraper?
One website, one consolidated result
Generic website scrapers usually return one record per visited page.
Website Contact Scraper combines contacts found across the crawled pages into one structured record. Each unique valid start URL normally produces one result.
This makes it easier to:
- join contacts back to an existing lead or company record;
- avoid merging page-level results manually;
- import data into a CRM or database;
- build predictable automation workflows.
It also keeps costs predictable: you pay for one result per unique website, while duplicate and invalid URLs are free (see Pricing below).
Contact pages crawled first
The Actor visits the pages most likely to list contacts first, in this order:
- contact pages;
- legal notices, such as an Impressum or mentions lΓ©gales;
- privacy policies, which usually name a contact for data requests;
- support and help pages;
- about and company pages;
- other legal pages, such as terms and cookie policies.
It recognizes these pages in English, German, Spanish, French, and Italian, for example kontakt, contacto, contatti, nous-contacter, and impressum.
Within the page budget, pages are always chosen in this order, so repeated runs on the same website visit the same pages. If one of them fails, the next candidate takes its place.
You control the page budget, crawl depth, URL scope, concurrency, proxy use, and optional early stopping.
Built for bulk automation
Results are written as soon as each website finishes, so faster websites do not wait for the slowest one in the run.
Every record includes machine-readable information for routing results, identifying retryable failures, and monitoring crawl coverage.
What gets filtered out
Websites are full of strings that look like contacts but are not. A typical scraper returns them; this Actor filters or fixes them:
| Found on the page | Typical scraper returns | Website Contact Scraper returns |
|---|---|---|
500043-012 (a product code) | a phone number | nothing |
VAT No. GB 314 1592 65 (a UK VAT number) | 314 1592 65 as phone | nothing |
24/07/2025 (a date) | a phone number | nothing |
\u003ehelp@acme.com (from embedded page code) | u003ehelp@acme.com | help@acme.com |
yourname@gmail.com (a form placeholder) | an email | nothing |
youtube.com/embed/2VY4BPACEww (a video) | a social profile | nothing |
twitter.com/Acme and x.com/acme | 2 profiles | https://twitter.com/acme |
βοΈ How it works
For every valid submitted start URL, the Actor:
- opens the website;
- identifies and prioritizes likely contact pages;
- crawls within the configured URL scope, depth, and page budget;
- extracts email, phone, WhatsApp, and social-profile candidates;
- validates, normalizes, and deduplicates the contacts;
- writes one consolidated result when the website finishes.
π How to scrape website contacts
- Add one representative URL per website, preferably the homepage.
- Keep the defaults or adjust the crawl settings.
- Run the Actor.
- Review the consolidated records.
- Export the data or consume it through the Apify API, SDKs, webhooks, or schedules.
Basic input example
{"startUrls": [{"url": "https://apify.com"}]}
Full input example
{"startUrls": [{"url": "https://example.com"},{"url": "https://another-example.com"}],"maxPagesPerStartUrl": 12,"maxDepth": 2,"defaultPhoneCountry": "US","enqueueStrategy": "same-domain","allowedSubdomains": ["www", "support"],"earlyStopMode": "balanced","maxRequestsPerCrawl": 500,"maxConcurrency": 10,"proxyConfigurationOptions": {"useApifyProxy": true}}
This example shows the full available configuration. For most runs, you can start with the defaults and change only the fields you actually need.
Output example
Each result contains the consolidated contacts together with crawl status and diagnostics:
{"schemaVersion": 1,"status": "completed","hasContacts": true,"startUrl": "https://acme.example/","finalUrl": "https://www.acme.example/privacy-policy","contacts": {"emails": ["info@acme.example", "privacy@acme.example"],"phones": ["+14155552671"],"socials": [{"type": "linkedin","value": "https://linkedin.com/company/acme"}],"whatsapps": ["14155552671"]},"crawl": {"pagesVisited": 4,"pagesAttempted": 4,"pagesSucceeded": 4,"pagesFailed": 0,"visitedSample": ["https://www.acme.example/","https://www.acme.example/contact","https://www.acme.example/impressum","https://www.acme.example/privacy-policy"],"stoppedReason": "crawler-finished","terminationReason": "crawl_completed","startPageFinalUrl": "https://www.acme.example/","startPageHttpStatusCode": 200,"startedAt": "2026-01-01T12:00:00.000Z","finishedAt": "2026-01-01T12:00:04.200Z","durationMs": 4200,"error": null,"updatedAt": "2026-01-01T12:00:04.200Z"}}
π― Common use cases
Enrich company and lead lists
Append public emails, phone numbers, WhatsApp numbers, and social profiles to existing website records.
Prepare CRM-ready records
Produce one consolidated result per submitted website and join it to your source data through startUrl (see Start URLs for how URLs are canonicalized).
Enrich directory and marketplace data
Use the Actor after collecting company websites from directories, maps, marketplaces, or other scrapers.
Build and audit business datasets
Add contact channels to supplier, agency, dealership, local-business, property, ecommerce, or service-provider lists.
You can also distinguish websites that:
- expose supported contacts;
- were processed successfully but contain no supported contacts;
- failed because of blocking, DNS, TLS, or network errors.
π³ Pricing
You pay per website result: each unique website you submit produces one row, and you are charged only for rows written to the dataset.
- Duplicates are free: URLs that repeat in your input are merged and billed once.
- Invalid URLs are free: malformed and non-HTTP(S) URLs are skipped and never billed.
- Interrupted runs: if a run is aborted or times out, websites that did not finish produce no row and no charge.
The cost of a run is therefore predictable:
100 unique website URLsβNormally 100 rowsβNormally 100 billable results
In a completed run, every valid website URL gets a row, including websites that cannot be crawled, for example because they are offline or block automated visits. Those rows are billed too: they tell you which websites failed and whether a retry makes sense (crawl.error.retryable), instead of leaving them silently missing from your results.
Check the Actor's pricing page for the current price per result and available pricing tiers.
π οΈ Input configuration
Input reference
| Field | Required | Default | Description |
|---|---|---|---|
startUrls | Yes | β | Website URLs processed independently |
maxPagesPerStartUrl | No | 8 | Maximum pages per start URL |
maxDepth | No | 1 | Maximum link depth |
defaultPhoneCountry | No | Empty | Optional fallback country for ambiguous local numbers |
enqueueStrategy | No | same-hostname | Controls which links may be followed |
allowedSubdomains | No | β | Optional subdomain allowlist for same-domain |
earlyStopMode | No | off | Controls adaptive early stopping |
maxRequestsPerCrawl | No | β | Optional global request limit |
maxConcurrency | No | Autoscaled | Maximum concurrent requests |
proxyConfigurationOptions | No | Proxy disabled | Apify proxy configuration |
Start URLs
Submit one representative URL per website, preferably the homepage.
Each URL is validated and canonicalized before crawling:
- malformed and non-HTTP(S) URLs are skipped: they produce no result and are not billed;
- the
#fragmentand tracking parameters are removed:utm_*and other campaign parameters ending in_source,_medium,_campaign,_contentor_term, ad click IDs such asgclid,fbclidandmsclkid, andref; - a bare hostname gets a trailing slash (
https://acme.exampleβhttps://acme.example/), and trailing slashes on deeper paths are removed; - URLs that are identical after these steps are merged into one.
The canonical URL is returned in startUrl. When joining results back to your source data, apply the same normalization or match on hostname.
Page budget and depth
maxPagesPerStartUrl controls the page budget for each start URL.
maxDepth controls link traversal:
0β only the submitted page;1β also follow eligible links from the submitted page;2β also follow eligible links discovered at depth one.
The page budget applies regardless of depth.
Crawl scope
| Strategy | Behavior |
|---|---|
same-hostname | Same hostname only |
same-domain | Registrable domain and subdomains |
same-origin | Same protocol, hostname, and port |
all | External links that pass the remaining filters |
Use same-hostname for most runs.
Use same-domain when relevant content may exist on subdomains. Combine it with allowedSubdomains to restrict which subdomains may be visited.
Use all with care: external pages count against the same page budget, and contacts found on other websites are merged into the record of the submitted URL.
Scope is evaluated against the start page after redirects, so https://acme.example/ redirecting to https://www.acme.example/ crawls www.acme.example under same-hostname.
Early stopping
off(default) β use the full page budget and depth;balancedβ after at least 3 pages, stop once 2 emails, phone numbers or WhatsApp numbers are found, or when the last 3β4 pages added nothing new;aggressiveβ after at least 2 pages, stop at the first email, phone number or WhatsApp number, or when the last 2 pages added nothing new.
Social profiles do not count toward these thresholds. Because contact pages are visited first, early stopping usually ends the crawl after the pages that matter most.
Phone country fallback
defaultPhoneCountry has no default value. Set it only when one fallback country is appropriate for ambiguous local numbers.
For example:
{"defaultPhoneCountry": "US"}
Country precedence is:
- explicit international number, such as
+44 20 7946 0958; - reliable website country context, such as a
.itdomain or a country in the page's structured address data; defaultPhoneCountry;- no country: a number written in the page text is kept only when a phone label such as "Tel:" sits next to it, and it stays in local form.
For mixed international lists, leave this field empty.
Proxy and advanced controls
A proxy may improve access to websites that restrict direct requests, but it does not guarantee access or render JavaScript-only content.
Advanced controls include:
maxRequestsPerCrawlβ global request cap;maxConcurrencyβ concurrent request limit (autoscaled when unset).
Suggested configurations
| Goal | Pages | Depth | Early stop |
|---|---|---|---|
| Faster bulk run | 4 | 1 | balanced |
| Standard enrichment | 8 | 1 | off |
| Broader exploration | 20 | 2 | off |
These are starting points. Performance depends on website structure, response time, blocking, proxies, and Actor resources.
π¦ Output semantics and result delivery
Core fields
| Field | Meaning |
|---|---|
schemaVersion | Output contract version |
status | Website-level execution outcome |
hasContacts | Whether at least one supported contact was found |
startUrl | Canonical form of the submitted URL (see Start URLs) |
finalUrl | Legacy field containing the last successfully processed page |
contacts | Consolidated contact data |
crawl | Counters, timing, redirects, termination reason, and errors |
Use crawl.startPageFinalUrl for the redirected final URL of the original start page. finalUrl is retained for backward compatibility.
Status values
| Status | Meaning |
|---|---|
completed | At least one page succeeded and the crawl reached a normal configured end |
partial | Some pages succeeded, but execution ended before normal completion |
failed | No page was successfully processed |
A failed record can also come from a run that timed out or was aborted before any page of that website succeeded. Check crawl.terminationReason to tell these apart from website-side failures.
Contact presence does not determine status.
{"status": "completed","hasContacts": false}
This means the website was processed successfully, but no supported contact was found.
Automation example
// The run, not the website, cut these crawls short; a re-run may find more.const INTERRUPTED = new Set(['run_timed_out', 'user_aborted', 'global_request_limit_reached']);const interrupted = INTERRUPTED.has(result.crawl.terminationReason);if (result.status === 'completed' || result.status === 'partial') {await saveResult(result);if (result.hasContacts) {await sendToCrm(result);}if (result.status === 'partial' && interrupted) {await queueForRetry(result.startUrl);}} else if (interrupted || result.crawl.error?.retryable) {await queueForRetry(result.startUrl);} else {await queueForManualReview(result);}
partial records still contain the contacts found before the interruption, so save them instead of discarding them.
Termination reasons and errors
crawl.terminationReason may contain:
crawl_completedearly_stoppage_limit_reachedglobal_request_limit_reachedstart_url_unreachablestart_url_blockedrequest_failuresrun_timed_outuser_abortedinternal_error
A failure may include:
{"code": "DNS_ERROR","message": "The URL hostname could not be resolved.","retryable": true,"url": "https://does-not-resolve.invalid/"}
Supported error codes include:
DNS_ERRORCONNECTION_ERRORTLS_ERRORHTTP_BLOCKEDHTTP_ERRORREQUEST_TIMEOUTMAX_RETRIES_EXCEEDEDRUN_TIMEOUTINTERNAL_ERROR
Error messages are sanitized before being written to the dataset.
Page counters
pagesVisited = pagesSucceededpagesAttempted = pagesSucceeded + pagesFailed
Retries and redirect hops do not inflate these counters.
Ordering, interruptions, and duplicates
Dataset rows follow completion order, not input order. Always match records through startUrl, not row position.
A normal completed run produces one result per unique valid start URL.
Results already written remain available if a run is aborted or times out. Websites still incomplete may not produce a result.
Each start URL is emitted at most once within a single Actor process attempt. In rare cases, a platform restart or migration may produce a duplicate. Deduplicate by startUrl when downstream uniqueness is required.
Compatibility
The current output contract uses schemaVersion: 1.
crawl.stoppedReason is retained for backward compatibility. New integrations should use crawl.terminationReason.
Field names never change within a schema version, but how values are formatted can improve between releases, for example phone numbers in E.164 or social handles in lowercase. Each such change is listed in the Actor's changelog with upgrade notes. If you match or deduplicate on exact contact strings, check it before upgrading.
β Contact extraction and validation
Emails
The Actor extracts emails from the page HTML and from mailto: links, including links with query parameters or multiple recipients.
Candidates are syntax-checked, normalized, deduplicated, and filtered for malformed values, unknown domain endings, placeholder addresses (such as yourname@ or email@gmail.com), asset filenames, and other common false positives. Leftovers from page code or formatting (such as u003e or ** in front of an address) are removed.
The Actor does not perform SMTP verification, mailbox existence checks, deliverability verification, catch-all detection, or contact-owner identification.
Phone numbers
Phone numbers are found in tel: links, structured data (schema.org telephone), and page text, and validated with libphonenumber-js.
- When the country is known (see Phone country fallback), valid numbers are returned in E.164, for example
+14155552671. - North American numbers written with the country code but without
+, such as1-800-555-0199, are also returned in E.164. - Without a country, a number from the page text is kept only when a phone label such as "Tel:" sits next to it, and it stays in local form. Numbers from
tel:links and structured data are kept even without a label. - Dates, US ZIP+4 codes, year/number references such as
2016/679, VAT, tax and company-register numbers next to their label (such asVAT No.,P.IVA,USt-IdNr.,EIN,Tax IDorCompany No.), and placeholder numbers such as9876543210are rejected. On websites without a known country, product codes and other unlabeled digit runs are rejected too. - Equivalent local and international formats of the same number are deduplicated.
WhatsApp numbers
The Actor detects wa.me and api.whatsapp.com links and returns the associated numbers in international format as digits only, without a leading +:
{"whatsapps": ["14155552671"]}
Prepend + if you need E.164.
Social profiles
Supported platforms include LinkedIn, Facebook, Instagram, Twitter/X, YouTube, TikTok, GitHub, Pinterest, and Telegram.
Each link is reduced to the profile it belongs to: https is enforced, www., mobile and country subdomains and query parameters are removed, alias hosts are folded (for example x.com β twitter.com), and handles are lowercased. Links to a post or sub-page count as the account (linkedin.com/company/acme/about β linkedin.com/company/acme). Videos, embeds, share buttons, login and legal pages, and tracking URLs are excluded. The same profile linked in different ways therefore appears only once.
β οΈ Limitations and FAQ
Does the Actor execute client-side JavaScript?
No. It uses lightweight HTML parsing with Cheerio.
This keeps bulk runs efficient, but contacts rendered only through client-side JavaScript may be missed.
Why did a website fail with HTTP_BLOCKED?
The website refused automated visits, usually through bot protection such as Cloudflare. The Actor already retries each page several times with a fresh session before giving up.
- Some websites block every automated visit from a given IP address; others block only part of them, so the same website can succeed in one run and fail in the next.
- These records have
crawl.error.retryable: true. Retrying them in a later run, ideally with Apify Proxy enabled (residential proxies work best against bot protection), often succeeds. - A proxy does not guarantee access, and websites that require a JavaScript challenge cannot be crawled by this HTTP-based Actor.
Why did a completed website return no contacts?
status: "completed" with hasContacts: false means the website was processed successfully, but no supported contact was found.
Try increasing the page budget or depth, and check whether the contacts require JavaScript rendering.
Why is one of my URLs missing from the output?
Usually for one of these reasons:
- the URL was malformed or not HTTP(S), so it was skipped (see the run log);
- it duplicated another URL after canonicalization, so both share one record;
- the run was aborted or timed out before that website finished.
Why does startUrl differ from the URL I submitted?
startUrl is the canonical form of your URL: tracking parameters and fragments are removed and trailing slashes are normalized. See Start URLs.
Are failed records billed?
Yes, when the failed record is written to the dataset. A website that produces no dataset record does not create a result charge.
Can the Actor process large website lists?
Yes. Websites are processed in parallel, and each result is written as soon as its website finishes, so you can start working with the first rows while the run continues.
Throughput depends on crawl settings, website speed, blocking, proxies, and Actor resources. Test a representative sample before a very large run.
Can I use the Actor through an API?
Yes. Results are available through:
- Apify API;
- JavaScript and Python SDKs;
- webhooks and schedules;
- JSON, CSV, Excel, XML, and HTML exports.
π Support
This Actor is actively maintained. Need help or want to customize it?
- π¬ Contact the developer
- π Report a bug (Apify login required)
- β¨ Request a feature