LinkedIn Phone Number Scraper With Company Enrichment avatar

LinkedIn Phone Number Scraper With Company Enrichment

Pricing

from $5.99 / 1,000 results

Go to Apify Store
LinkedIn Phone Number Scraper With Company Enrichment

LinkedIn Phone Number Scraper With Company Enrichment

LinkedIn Phone Number Scraper gathers public phone contacts for agencies and B2B teams. Filter profiles by job title, industry, or location to streamline prospecting and outbound calling at scale.

Pricing

from $5.99 / 1,000 results

Rating

0.0

(0)

Developer

Scrapier

Scrapier

Maintained by Community

Actor stats

0

Bookmarked

18

Total users

2

Monthly active users

2 days ago

Last modified

Share

LinkedIn Phone Number Scraper With Company Enrichment

LinkedIn Phone Number Scraper With Company Enrichment searches Google's public index for LinkedIn results matching your keywords, pulls out any phone number Google has indexed in the snippet, and normalizes it to E.164. Every result is also enriched: the real LinkedIn title is parsed into a person's name, job title and company, and the run rolls those leads up into one summary row per employer. Every response is structured JSON — ready to pass directly to an LLM, index into a vector store, or feed a monitoring pipeline. It runs entirely on Apify, with no LinkedIn login and no phone number guessed or fabricated.

What is LinkedIn Phone Number Scraper With Company Enrichment?

LinkedIn Phone Number Scraper With Company Enrichment queries Google (not LinkedIn directly) with a site:linkedin.com search restricted to your keywords and country, then reads phone-like text out of each result's title and snippet. It returns two kinds of rows in one dataset: individual lead rows (one per phone number found) and company summary rows that aggregate the leads by employer. This is the company-enrichment layer — parsing personName, jobTitle and companyName straight out of the real result title, and emitting a leadCount / phoneCount / sampleRoles roll-up per company — that the base phone-number scrapers on the Store don't do.

No LinkedIn account, cookie or login is used at any point. The Actor never visits linkedin.com directly — it reads what Google has already indexed and shows to an anonymous searcher. The platform field is schema-restricted to "Linkedin" today, even though the underlying query-building logic derives its site: domain from whatever string is passed in — so the current release targets LinkedIn only, and that is the only value the input form will accept.

  • Extracts phone numbers from Google's indexed LinkedIn result snippets, normalized to E.164
  • Parses each result's real title ("Name - Role - Company | LinkedIn") into personName, jobTitle, companyName — never guessed, left null when the title doesn't actually contain that part
  • Reads profileType (in, company, pub, school, showcase) from the LinkedIn URL slug
  • Emits one company_summary row per distinct company, with leadCount, phoneCount and up to 5 sampleRoles
  • Optional companyKeywords filter keeps only lead rows whose parsed company name matches your target employers
  • Localizes both the phone-number normalization and the Google search itself to one of ~190 countries via a country + dial-code dropdown — the same country value resolves to both a dial code (for number matching) and an ISO country code passed to Google as the gl region parameter, so the search results and the accepted phone numbers are localized together, not independently

What data can you get with LinkedIn Phone Number Scraper With Company Enrichment?

Every run can produce two result types, distinguished by recordType — an individual lead, and a per-company roll-up built from the leads collected in that same run. profileType on a lead row tells you what kind of LinkedIn page the result came from: in for a personal profile, company or showcase for a business page, pub for a legacy public-profile URL, or school for an education page — each parsed from the URL path itself, not guessed.

Result TypeExtracted FieldsPrimary Use Case
Lead (recordType: "lead")recordType, platform, keyword, title, description, url, phone_number, country, dial_code, personName, jobTitle, companyName, profileTypeBuilding a contact list of names, roles, companies and phone numbers tied to one real LinkedIn result
Company summary (recordType: "company_summary")recordType, platform, country, dial_code, companyName, leadCount, phoneCount, sampleRolesSeeing, per employer, how many leads and distinct phone numbers a keyword search actually surfaced

Company enrichment

This is the part competing LinkedIn phone-number scrapers don't do: instead of handing back a flat title / url / phone_number row, the Actor parses LinkedIn's own title format. Person pages render as "Name - Role - Company | LinkedIn"; the parser splits on the real separator (a padded hyphen, en-dash, em-dash or pipe — not a plain -, so it doesn't break hyphenated names like "Jean-Pierre"). Three or more segments give you a name, a job title and a company; exactly two segments give a name and a job title but no company, because a two-part title is ambiguous and the Actor never assigns a company it isn't sure of. company, showcase and school pages skip name/role parsing entirely and put the cleaned page title straight into companyName — so a school result's institution name lands in the same field a business page's name would, since profileType already tells you which kind of page it came from. profileType comes from the URL slug (linkedin.com/in/, /company/, /pub/, /school/, /showcase/). Once every lead row has a parsed companyName, the Actor groups them and pushes one company_summary row per employer:

{
"recordType": "company_summary",
"platform": "Linkedin.com",
"companyName": "Acme Ltd",
"country": "United Kingdom",
"dial_code": "+44",
"leadCount": 3,
"phoneCount": 2,
"sampleRoles": ["Sales Director", "Account Manager"]
}

Phone number normalization

Every phone-like run of digits found in a result's title or snippet is normalized to E.164 using the country's dial code: international prefixes (+44…, 0044…) are recognized directly, a national trunk 0 is swapped for the dial code, a trunk 0 written right after the dial code (+44 (0)7585…) is stripped, an accidentally duplicated country code is collapsed, and the result is rejected if it doesn't land between 10 and 14 digits — which filters out truncated fragments and concatenated IDs that only look like a phone number. A digit run that doesn't start with an international prefix and doesn't match the selected country's dial code at all is discarded rather than normalized against the wrong country.

Why not build this yourself?

LinkedIn does not expose a public API that lets you search across profiles by keyword and get back phone numbers — its official APIs require partner approval and are scoped to your own connections or ad accounts, not open keyword search. A DIY version means owning three separate problems at once.

First, reliably reaching a real Google results page: Google returns a JS-gated shell to most residential and datacenter IPs, so you need a proxy pool that actually returns rendered SERP HTML, rotating headers and user agents, jittered request timing between attempts, and genuine block detection that doesn't false-positive on the words "sorry" or "captcha" that appear on every normal results page footer. You also need a retry and give-up policy — this Actor retries a page up to 3 times before treating it as failed, and abandons a keyword's search after 3 consecutive empty or blocked pages rather than looping indefinitely.

Second, phone normalization across roughly 190 countries — trunk-code handling, duplicated country codes, and rejecting ID-like digit runs all have to be encoded per dial code, and getting the digit-length bounds wrong either drops real numbers or lets junk through. Third, title parsing that doesn't invent data: telling a two-segment "Name - Role" title apart from a three-segment "Name - Role - Company" title, splitting only on the real LinkedIn separator so hyphenated names survive, and returning null instead of a guess when the company genuinely isn't in the title.

What is the difference between a LinkedIn phone number scraper and a LinkedIn company scraper?

A LinkedIn phone number scraper searches for individual people or profiles and returns lead-level contact data — a name, a role, and (when Google has indexed one) a phone number. A LinkedIn company scraper starts from a company page and returns firmographic data about the business itself — industry, headcount, follower count, address — with no phone numbers and no individual leads at all.

The two get confused because both work from public LinkedIn pages and both feed the same B2B pipelines. Checked on the Apify Store on 26 July 2026, automation-lab/linkedin-company-scraper is a company-profile actor: it returns fields like employeeCount, companySize and headquarters for a company URL or slug you supply, and documents no phone-number extraction.

LinkedIn Phone Number Scraper With Company Enrichment sits between the two: it is lead-first — every row starts from a person or business result matching your keyword — but it also produces the company_summary object a pure company scraper would, aggregated from the real leads found in the same run rather than from a separate company-page fetch. If you already have a list of company URLs and want firmographic fields, a company scraper is the right tool; if you're starting from a keyword and want phone numbers plus who they belong to, this Actor is.

How to scrape LinkedIn with LinkedIn Phone Number Scraper With Company Enrichment?

  1. Open LinkedIn Phone Number Scraper With Company Enrichment on the Apify Store and click Try for free
  2. Enter your search terms, LinkedIn usernames, or profile URLs into Keywords / Usernames / URLs (keywords) — this is the only required field besides country. All three formats are accepted in the same list: a plain term ("marketing director"), a bare LinkedIn slug, or a full profile/company URL (https://www.linkedin.com/in/jane-smith-123)
  3. Pick your target Country (country) — its dial code both localizes the Google search and filters/normalizes the phone numbers found
  4. Set Max Phone Numbers (maxPhoneNumbers) and, optionally, Company Keywords (companyKeywords) to keep only leads at specific employers
  5. Click Start, then download lead and company-summary rows as JSON, CSV or Excel from the Dataset tab
{
"keywords": ["marketing director"],
"country": "United States (+1)",
"maxPhoneNumbers": 20
}

How to run multiple queries in one job

keywords is a list — add as many search terms, usernames or profile URLs as you need, and each one runs in turn within the same job. Keywords are processed sequentially, not in parallel: the Actor finishes one keyword's pagination before starting the next. maxPhoneNumbers is a per-keyword ceiling, not a total across the whole list — ten keywords at the default of 20 can produce up to 200 lead rows in one run.

A plain keyword builds exactly one Google query. A recognized LinkedIn profile or company URL (e.g. linkedin.com/in/jane-smith-123) instead decodes a name from the slug and tries up to three progressively broader queries — site:linkedin.com "jane smith" "+1", then site:linkedin.com "jane smith", then site:linkedin.com jane-smith-123 — stopping at the first one that returns any result, so a real profile still surfaces even when the exact-slug match comes back empty.

⬇️ Input

ParameterRequiredTypeDescriptionExample Value
keywordsYesarray of stringsKeywords, LinkedIn usernames, or profile URLs to search for.["marketing", "founder"]
platformNostring (enum)Target platform. Only "Linkedin" is currently offered. Default "Linkedin"."Linkedin"
countryYesstring (enum)Country to scrape related phone numbers for, formatted "Country Name (+DialCode)". ~190 values in the dropdown, from "Afghanistan (+93)" to "Zimbabwe (+263)". The dial code normalizes and filters phone numbers and localizes the Google search region. Default "United Kingdom (+44)"."United States (+1)"
maxPhoneNumbersNointegerMaximum number of phone numbers to collect per keyword. Minimum 1, maximum 10000. The scraper stops that keyword once the limit is reached. Default 20.15
companyKeywordsNoarray of stringsOptional. Keep only lead rows whose parsed companyName (from the real LinkedIn result title, e.g. "Name - Role - Company") contains one of these terms, case-insensitive. Leave empty to keep every result.["google", "microsoft"]
useApifyProxyNobooleanRoute Google search requests through Apify Proxy (recommended). Default true.true

Example input

{
"keywords": ["sales director", "marketing"],
"platform": "Linkedin",
"country": "United Kingdom (+44)",
"maxPhoneNumbers": 15,
"companyKeywords": ["google", "microsoft"]
}

Common pitfall: country must match the dropdown text exactly, including the dial code in parentheses — the parser expects "Country Name (+Code)" and free-typed values without that format lose dial-code normalization. Separately, companyKeywords only ever matches a lead's parsed companyName — a two-segment title ("Name - Role", no company detectable) can never satisfy any companyKeywords filter and is dropped, even if the person genuinely works at a company on your list.

⬆️ Output

Typed JSON, one row per phone number found and one roll-up row per company. The dataset's default view already surfaces every key either row type writes — it is not a trimmed subset. A run-scoped (url, phone_number) pair is de-duplicated before it is pushed, so the same phone number attached to the same result URL is never written twice within one run, even if it appears on more than one paginated results page. Rows dropped by the companyKeywords filter are never pushed to the dataset at all. Export as JSON, CSV or Excel, or read the dataset through the Apify API.

Scraped results

[
{
"recordType": "lead",
"platform": "Linkedin.com",
"keyword": "sales director",
"title": "Jane Smith - Sales Director - Acme Ltd | LinkedIn",
"description": "Call us on +44 7700 900123 for enquiries.",
"url": "https://uk.linkedin.com/in/jane-smith-123",
"phone_number": "+447700900123",
"country": "United Kingdom",
"dial_code": "+44",
"personName": "Jane Smith",
"jobTitle": "Sales Director",
"companyName": "Acme Ltd",
"profileType": "in"
},
{
"recordType": "lead",
"platform": "Linkedin.com",
"keyword": "sales director",
"title": "Tom Reid - Regional Sales Director - Acme Ltd | LinkedIn",
"description": "Reach the Acme sales team on +44 20 7946 0958.",
"url": "https://uk.linkedin.com/in/tom-reid-88",
"phone_number": "+442079460958",
"country": "United Kingdom",
"dial_code": "+44",
"personName": "Tom Reid",
"jobTitle": "Regional Sales Director",
"companyName": "Acme Ltd",
"profileType": "in"
},
{
"recordType": "lead",
"platform": "Linkedin.com",
"keyword": "marketing",
"title": "Priya Nair - Head of Marketing | LinkedIn",
"description": "Priya Nair leads growth marketing. Contact: +44 7911 123456.",
"url": "https://uk.linkedin.com/in/priya-nair-mkt",
"phone_number": "+447911123456",
"country": "United Kingdom",
"dial_code": "+44",
"personName": "Priya Nair",
"jobTitle": "Head of Marketing",
"companyName": null,
"profileType": "in"
},
{
"recordType": "company_summary",
"platform": "Linkedin.com",
"keyword": null,
"title": null,
"description": null,
"url": null,
"phone_number": null,
"country": "United Kingdom",
"dial_code": "+44",
"personName": null,
"jobTitle": null,
"companyName": "Acme Ltd",
"profileType": null,
"leadCount": 2,
"phoneCount": 2,
"sampleRoles": ["Sales Director", "Regional Sales Director"]
}
]

Billing is pay-per-event: one row_result event is charged for every row written to the dataset — lead rows and company-summary rows alike. Rows dropped by the companyKeywords filter are never pushed, so they are never charged. Check the Pricing tab on the Actor's Store listing for current per-event rates.

How does it work?

For each keyword, the Actor builds a Google query rather than crawling LinkedIn directly. A plain keyword becomes site:linkedin.com "<dial code>" "<keyword>"; a recognized LinkedIn profile or company URL is decoded into a name from its slug and tried against several progressively broader query variants instead, so a real profile still surfaces if the exact-URL match returns nothing.

Each query is fetched from http://www.google.com/search through the Apify GOOGLE_SERP proxy group — the one Apify proxy group documented to reliably return a rendered results page over plain HTTP rather than the JS-gated shell that Residential and Datacenter groups get served for Google. Every request carries a randomized desktop user agent and Accept-Language header and a 1–2 second jittered delay before it fires. If a fetch fails or the response looks blocked, the Actor waits a longer 3–6 second jittered pause and retries the same page, up to 3 attempts total, before treating it as failed and moving to the next page; a keyword's pagination stops once 3 consecutive pages come back empty or blocked.

Each results page is parsed with BeautifulSoup, scoped to the result-block markup Google currently uses (div.MjjYud, falling back to the older div.tF2Cxc). For every block that links to a linkedin.com URL, the Actor pulls the visible title and description text, runs a phone-pattern regex over the flattened block text, and normalizes any match to E.164 using the country's dial code. In the same pass it parses the title into personName / jobTitle / companyName and reads profileType from the URL. A run-scoped (url, phone_number) set de-duplicates results across all pages and all queries for a keyword, so the same number is never pushed twice.

After every keyword has been processed, the Actor re-groups the pushed lead rows in memory by their parsed companyName and pushes one company_summary row per group — this step reads only the rows already collected in that run; it never re-fetches or re-searches. Summary rows are pushed in descending order of leadCount (most leads first, alphabetical on ties), so the busiest companies land at the top of the dataset without you having to sort it yourself. Lead rows with no parsed companyName — a two-segment title, for instance — are simply excluded from this grouping step; they still exist as lead rows, they just never contribute to a company_summary.

How can I use the data extracted with LinkedIn Phone Number Scraper With Company Enrichment?

  • Sales and business development teams: pull personName, jobTitle, companyName and phone_number straight into a CRM import, and use companyKeywords up front so the run only ever produces leads at your target accounts. Because keywords accepts a list, a single job runs an entire target-role list ("VP Sales", "Head of Procurement", "Sales Director") against the same account filter in one pass.
  • Recruiters: search a role keyword ("engineering manager"), then read company_summary rows to see which employers are yielding the most reachable candidates before working the individual lead rows. sampleRoles on each summary shows the spread of titles found at that company without opening every lead.
  • AI engineers and LLM developers: run the Actor as a tool call, receive typed JSON with no HTML to parse, and pass lead rows straight into an agent's context or a vector store for a grounded outreach or research assistant. recordType lets the consuming code branch on lead-vs-summary without extra logic.
  • Market researchers: compare leadCount and phoneCount across company_summary rows for a set of companyKeywords to gauge which named accounts have the most publicly indexed contact surface for a given keyword, without visiting a single company page directly.

How do you monitor company enrichment over time?

Because leadCount, phoneCount and sampleRoles are recomputed fresh from the leads found in each run, repeated runs on the same keywords and companyKeywords let you track how a target company's publicly indexed contact footprint changes — new hires showing up under a role keyword, or a previously silent account suddenly surfacing indexed phone numbers.

The fields to diff between runs are companyName (which employers appear at all), leadCount and phoneCount per company, and the personName values inside the matching lead rows (who is new since the last run). A simple workflow: run weekly with the same keywords and companyKeywords, keep the previous run's dataset, and compare company_summary rows by companyName — a jump in leadCount at a target account is the signal worth acting on.

The Actor itself has no built-in scheduler. Set up the repeated run with an Apify Schedule on the Actor, and either poll the new run's dataset through the Apify API or attach a webhook to fire when the run finishes.

Integrate LinkedIn Phone Number Scraper With Company Enrichment and automate your workflow

LinkedIn Phone Number Scraper With Company Enrichment works with any language or tool that can send an HTTP request, because it runs as a standard Apify Actor behind the Apify API.

REST API with Python

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("<YOUR_USERNAME>/linkedin-phone-company-enricher").call(run_input={
"keywords": ["marketing director"],
"country": "United States (+1)",
"maxPhoneNumbers": 20,
"companyKeywords": ["acme"],
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
if row["recordType"] == "lead":
print(row["personName"], row["companyName"], row["phone_number"])

Works the same way in Node.js, Go, Ruby or cURL — any client that can call the Apify API.

Scheduled monitoring and delivery

The Actor has no built-in scheduler or webhook of its own; both are Apify platform features you attach to it. Configure an Apify Schedule to re-run the same input on a cadence, and an Apify webhook to notify your endpoint — or trigger a Zapier/Make/n8n flow — as soon as each run's dataset is ready.

Scraping publicly accessible LinkedIn results is generally treated as lawful in the US — the 9th Circuit's ruling in hiQ Labs, Inc. v. LinkedIn Corp. (9th Cir., 2019) held that accessing data a website makes publicly viewable does not violate the Computer Fraud and Abuse Act. LinkedIn Phone Number Scraper With Company Enrichment returns only what Google has already indexed and shows to any anonymous searcher — it never logs in, and it never accesses anything behind LinkedIn's own login wall.

That said, the output here is personal data — names, job titles and phone numbers tied to identifiable individuals — so GDPR, CCPA and equivalent personal-data regimes apply to how you store, process and contact the people in your results, separately from whether the scraping itself was lawful. A single research run that stays on your own machine carries a very different risk profile from a bulk import into an outbound dialing or SMS campaign at scale, and the two should not be treated the same way operationally.

Consult your legal team before any commercial use case involving bulk data storage, resale, or direct outreach to the individuals in your results.

❓ Frequently asked questions

How do I get phone numbers for a specific country?

Set country to the matching dropdown value, e.g. "United States (+1)" or "Germany (+49)". The dial code both restricts which digit runs are accepted as valid phone numbers and localizes the underlying Google search region (gl parameter).

Can I filter results to specific companies?

Yes. Set companyKeywords to the employer names you care about, e.g. ["google", "microsoft"]. Matching is case-insensitive against the parsed companyName, and it's a substring match, so "acme" also matches "Acme Ltd".

How does LinkedIn Phone Number Scraper With Company Enrichment handle Google's anti-bot measures?

Requests route through Apify's GOOGLE_SERP proxy group (the group that reliably returns a rendered results page rather than a JS-gated shell), with a randomized user agent and Accept-Language header per request and a jittered delay before each fetch. A response is treated as blocked only when it matches a genuine interstitial phrase (Google's "unusual traffic" or captcha-redirect pages) or looks like a JS-gated shell with none of the normal result markers present — deliberately not on words like "sorry" or "captcha" that also appear in the footer of an ordinary results page. Up to 3 attempts per page with a backoff pause between them, and a query gives up after 3 consecutive empty or blocked pages.

Does LinkedIn Phone Number Scraper With Company Enrichment extract company enrichment?

Yes — personName, jobTitle and companyName are parsed from the real LinkedIn result title on every lead row, and a company_summary row with leadCount, phoneCount and sampleRoles is emitted per distinct company found in the run. Any part not actually present in the title comes back null rather than guessed, and a company can only appear in a summary row if at least one lead's title yielded a parseable company name.

How many phone numbers does it return per keyword?

Up to maxPhoneNumbers (default 20, minimum 1, maximum 10000) per keyword, not per run — that ceiling is a schema-defined stopping point for this Actor, not a limit Google itself imposes. The Actor stops paginating a keyword once that ceiling is hit, or after 3 consecutive pages produce no new matching results. Google results are paginated 10 at a time internally, so reaching a high maxPhoneNumbers on a keyword with few indexed phone numbers means paging deep into results that may not contain any more matches.

How do I use it to monitor new leads at a company over time?

Re-run the same keywords and companyKeywords on a schedule, keep the previous run's dataset, and diff company_summary rows by companyName — a rising leadCount or new personName values inside the matching lead rows are the signal that something changed. See "How do you monitor company enrichment over time?" above for the full workflow.

Does it work with Claude, ChatGPT, and AI agent frameworks?

It's callable as a standard HTTP endpoint through the Apify API, so any agent framework that can make a request — LangChain, CrewAI, a custom tool definition — can invoke it and receive typed JSON with no parsing step.

How does LinkedIn Phone Number Scraper With Company Enrichment compare to other LinkedIn scrapers?

Checked on the Apify Store on 26 July 2026, scraper-mind/linkedin-phone-number-scraper returns a flat row of title, url, description and phone_number with no name, role or company parsing and no per-company roll-up. automation-lab/linkedin-company-scraper is a different category entirely — it starts from a company URL and returns firmographic fields (employeeCount, companySize, headquarters), with no phone-number extraction at all. LinkedIn Phone Number Scraper With Company Enrichment is the one of the three that both extracts phone numbers from a keyword search and structures the result into personName / jobTitle / companyName plus a company_summary roll-up.

What happens if my keyword returns zero phone numbers?

No rows are pushed and nothing is charged for that keyword. Public phone numbers only show up when Google has actually indexed one in a result's title or snippet, which is uncommon for most profiles — the Actor logs that no numbers were found and moves on to the next keyword. Try a broader keyword (a job title or industry rather than one exact profile URL) if a run comes back empty.

Does the companyKeywords filter reduce how many phone numbers I get?

No — a row dropped by companyKeywords doesn't count against maxPhoneNumbers, so the Actor keeps paginating until it either fills the budget with matching companies or exhausts the query (3 consecutive empty/blocked pages). The trade-off is time, not yield: a narrow companyKeywords list against a broad keyword can mean paging further into Google's results than an unfiltered run would need to.

Can I use it without managing proxies or LinkedIn credentials?

Yes. No LinkedIn account, cookie or password is ever used — the Actor queries Google, not LinkedIn. Leave useApifyProxy on its default true and Apify's GOOGLE_SERP proxy group is handled for you; the only credential you supply is your own Apify API token if you're calling it programmatically.

Scraper NameWhat it extracts
LinkedIn Profile Phone Number Scraper (Number Validation)Validates and re-checks phone numbers against a single known LinkedIn profile
LinkedIn Company URL - Mass Profile Finder & Employee ListBulk-resolves company URLs into employee profile lists
LinkedIn Jobs ScraperJob listings with company and posting details
LinkedIn Post Comments Scraper & Post ReactorsCommenters and reactors on a given LinkedIn post
LinkedIn career history analyzerCareer history and role progression parsed from a profile

💬 Your feedback

Found a bug, or a LinkedIn title format the parser doesn't handle correctly? Open an issue on the Actor's Issues tab. Reports that include the exact keywords and country used are the fastest to reproduce and fix.