LinkedIn Phone Number Scraper: Lead Scoring
Pricing
$19.99/month + usage
LinkedIn Phone Number Scraper: Lead Scoring
LinkedIn Lead & Contact Finder (Google SERP) extracts publicly listed phone numbers from LinkedIn profiles and linked pages. Build targeted contact lists by role, industry, or company. Ideal for sales teams running outbound campaigns.
Pricing
$19.99/month + usage
Rating
0.0
(0)
Developer
Scraper Engine
Maintained by CommunityActor stats
1
Bookmarked
81
Total users
14
Monthly active users
12 days ago
Last modified
Categories
Share
LinkedIn Phone Number Scraper — Lead Scores, Grades and E.164 Numbers
LinkedIn Phone Number Scraper: Lead Scoring finds publicly indexed phone numbers on linkedin.com and returns each one as a scored lead: phone_number normalised to E.164, the url it came from, the result title and description, plus an Actor-computed leadScore (0-100), a leadGrade (A–F), a derived seniorityLevel and a best-effort company. Thirteen flat JSON keys per row, no parsing required. Give it keywords and a country, press Start, and scored rows land in the dataset as each results page is parsed.
⚠️ This Actor reads Google's index, not LinkedIn pages. Despite the name it never sends a request to LinkedIn. It runs site:linkedin.com "<dial code>" "<keyword>" queries through Apify's GOOGLE_SERP proxy and pulls phone numbers out of the result titles and snippets Google has indexed. That means no login and no cookie — but also no LinkedIn profile fields: no structured job title, no employer record, no connection count. The seniority and company values are inferred from the snippet text, not read from LinkedIn.
What is LinkedIn Phone Number Scraper: Lead Scoring?
LinkedIn Phone Number Scraper: Lead Scoring is an Apify Actor that turns a keyword and a country into a ranked list of publicly indexed linkedin.com pages that show a phone number in visible text. For each keyword it pages through Google search results, keeps only links to linkedin.com, scans each result block for a phone-shaped string, normalises it against your country's dial code, then scores the result as an outreach lead from its own title, snippet and phone validity.
No LinkedIn account, login, session cookie or API token is required, because LinkedIn is never contacted — the only credential involved is your Apify token. The Actor selects Apify's GOOGLE_SERP proxy for you.
It is built for sales and lead-generation teams that want a prioritised call list rather than a raw dump, recruiters sourcing by role, and developers piping scored contact records into a CRM or an AI pipeline.
What LinkedIn contact data is publicly available to scrape?
A Google result row for a linkedin.com page carries a title, a URL and a snippet — nothing more. Everything else about the person or company behind that URL sits on the LinkedIn page itself, which this Actor never opens.
| Data Category | On the Google result (returned here) | Needs a LinkedIn page fetch or login |
|---|---|---|
| Phone number written in indexed page text | ✅ Returned as phone_number, normalised to E.164 | — |
The linkedin.com profile, company or post URL | ✅ Returned as url | — |
Google's result title, usually Name - Role - Company | LinkedIn | ✅ Returned as title | — |
| Snippet text surrounding the number | ✅ Returned as description | — |
| Role seniority | ✅ Inferred as seniorityLevel from title and snippet wording | LinkedIn's own structured job title — not returned |
| Employer | ✅ Best-effort company parsed out of the snippet | LinkedIn's structured employer record — not returned |
| Email addresses as their own field | ❌ | Not parsed — only phone numbers are extracted |
| Contact-info panel, connections, InMail | ❌ | Login and, in most cases, a connection |
| Numbers on LinkedIn pages Google has not indexed | ❌ | Out of reach — coverage is Google's index |
LinkedIn Phone Number Scraper: Lead Scoring only returns publicly visible data — what any visitor sees on a Google results page. Nothing behind a login wall.
What data can I extract with LinkedIn Phone Number Scraper: Lead Scoring?
Every row carries the same thirteen keys: eight describing the search result and the number found in it, and five computed by the Actor from those eight.
| Field Name | Description |
|---|---|
platform | Platform label the search ran against. With the schema's only allowed value this is always Linkedin.com |
keyword | The exact entry from your keywords list that produced this row |
title | Text of the Google result's <h3> heading, whitespace-normalised |
description | The Google snippet for that result, or "" when no snippet element matched |
url | The linkedin.com URL, unwrapped from Google's /url?q= redirect when present |
phone_number | The number normalised to + followed by digits only, e.g. +447585467400 |
country | Country name with the dial code stripped off, e.g. United Kingdom |
dial_code | Dial code used to build the query, normalise the number and filter it, e.g. +44 |
leadScore | Actor-computed outreach score from 0 to 100 — see the scoring section below |
leadGrade | Letter band derived from leadScore: A, B, C, D or F |
seniorityLevel | Seniority tier matched in the title and snippet: Founder/Owner, C-Suite, VP, Director, Manager, Senior IC or Unknown |
company | Best-effort company name parsed from the title or snippet, truncated to 80 characters, or null when nothing company-like is present |
titleMatch | true when any of your titleKeywords appears in the title or snippet, otherwise false |
Result identity and source fields
platform, keyword, title, description and url describe where the number came from. keyword is the partition key you will use most — one run covers many keywords and rows are not otherwise separated. url is the link a human needs to verify a lead before it is worked, and it points at whatever LinkedIn page Google indexed, which may be a personal profile (/in/…), a company page (/company/…) or a post.
description is the highest-information text field in the row: it is the snippet Google showed, so it usually contains the number in its original written form along with the surrounding words. It falls back to "" rather than null when the snippet element does not match.
phone_number, country and dial_code are the normalised half of the source data. country and dial_code come from splitting your single country input — the schema stores it as one combined string like United Kingdom (+44), which is split into United Kingdom as the name and +44 as the dial code.
The dial code does real work in three places: it becomes a quoted term in the Google query, it is prepended to numbers written in national trunk form, and a candidate is discarded unless the normalised result starts with it. Normalisation is deliberately conservative — text is NFKC-normalised, non-breaking spaces converted and zero-width characters (U+200B, U+200C, U+200D, U+FEFF) stripped; a leading 00 becomes +; a duplicated country code (4444…) is collapsed; a trunk zero written straight after the country code, as in +44 (0)7585 467400, is dropped; and anything whose digit count falls outside 10–14 is rejected outright, which is what keeps profile ids and concatenated junk out of the column.
Lead scoring fields
leadScore, leadGrade, seniorityLevel, company and titleMatch are computed by this Actor, not values LinkedIn or Google publish. They are derived entirely from the same row's title, description, phone_number and dial_code, plus your titleKeywords list. Change the fetched text and the score changes.
company is the only key in the row that can be null. The extractor tries three patterns in order: an at <Capitalised phrase> phrase in the title or snippet; the last non-LinkedIn segment of a dash-separated LinkedIn title when the title has at least three segments; and finally any capitalised phrase ending in a company token such as Ltd, Limited, Inc, LLC, GmbH, PLC, Group, Agency, Solutions, Technologies, Consulting, Ventures, Studio, Media, Marketing, Labs, Partners, Corp, Company or Enterprises. When none of the three matches, the field is null rather than a guess. Because pattern one takes a run of capitalised words, a snippet that continues into another capitalised word after the company name can over-capture — always treat company as a hint to verify, not a verified employer record.
🤖 Add-on: Need additional LinkedIn data?
A scored number and a URL is a lead, not a profile. LinkedIn Profile & Company Enrichment Scraper takes the profile behind a url and returns the profile and employer detail you need to qualify a high-grade lead before anyone calls it. LinkedIn Company URL - Mass Profile Finder With Firmographics works the company side when company comes back populated and you want firmographics behind it. And LinkedIn Profile Phone Number Scraper By Seniority & Title Filter is the sibling to reach for when you would rather filter results by seniority up front than score and sort them afterwards.
How the lead score is computed
leadScore is the sum of four signals, plus one optional boost, clamped to the range 0–100. Every input is read from the row itself.
| Signal | Points | Exact condition |
|---|---|---|
| Valid phone | 30 | phone_number starts with +, has 10–14 digits, and its digits start with the country's dial digits |
| Seniority tier | 0–35 | Highest matching tier in the combined title and description text — see the tier table |
| Company present | 20 | company is non-empty after extraction |
| Description richness | 0–15 | Character length of description: ≥200 → 15, ≥120 → 12, ≥60 → 8, ≥20 → 4, otherwise 0 |
| Title-keyword boost | +10 | Any entry in your titleKeywords appears in the title or description; also sets titleMatch to true |
Written out: leadScore = min(100, phonePoints + seniorityPoints + companyPoints + descriptionPoints + (10 if titleMatch else 0)).
Seniority tiers are checked in this order and the first match wins. Matching is whole-word and case-insensitive over the title and description joined together.
seniorityLevel | Points | Matched terms |
|---|---|---|
| Founder/Owner | 35 | founder, co-founder, cofounder, co founder, owner, co-owner, proprietor |
| C-Suite | 35 | ceo, cfo, coo, cto, cmo, cio, ciso, chief, president |
| VP | 30 | vp, vice president, svp, evp |
| Director | 26 | director, head of, head |
| Manager | 18 | manager, principal, lead |
| Senior IC | 10 | senior, specialist, consultant, expert |
| Unknown | 0 | nothing above matched |
leadGrade is a straight band on the final score: A ≥ 85, B ≥ 70, C ≥ 55, D ≥ 40, otherwise F.
Three things to know before you sort on it.
The score is absolute, not relative to the run. No run-level average, median or percentile enters the formula — the same title, snippet, number and titleKeywords list always produce the same score. That makes leadScore comparable across runs, keywords and countries, so datasets from separate runs merge and sort together. The one thing that shifts a score between runs is your own titleKeywords list, since the +10 boost depends on it; keep the list identical if you want strictly comparable numbers.
The four base signals can already reach 100. A valid phone, a founder or C-suite match, a named company and a snippet of 200 characters or more sum to exactly 100, so the +10 boost only moves rows that are not already maxed out. titleMatch is still worth reading as a flag in its own right.
Matching is text matching, and text lies. The tiers key on words in Google's snippet, so head inside "head of department" and lead inside "operations lead" or "lead generation" both register — the second as the Manager tier. chief and president behave the same way. titleKeywords matching is looser still: it is a plain lowercase substring test, so lead matches leader and leads. A seniorityLevel is a signal from indexed text, not a verified job title, and no accuracy figure is claimed for it.
Limits you should know before your first run
Four behaviours are worth knowing up front rather than discovering mid-run.
maxPhoneNumbers is per keyword, not per run. Ten keywords at maxPhoneNumbers: 200 is a 2,000-row target. Each keyword pages through Google ten results at a time until its own limit is met or it hits three consecutive pages that yield nothing.
minLeadScore drops rows before they are pushed. A row scoring below the threshold is never written to the dataset and never charged; the run logs the dropped count. Dropped rows also do not count toward maxPhoneNumbers, so raising the threshold tightens quality without shrinking the row target — but it does mean more Google pages fetched for the same number of rows.
Eleven country options collapse to +1. The dial-code parser keeps only the country-code portion before the hyphen, so Jamaica (+1-876), Bahamas (+1-242), Barbados (+1-246), Dominica (+1-767), Dominican Republic (+1-809), Grenada (+1-473), Antigua And Barbuda (+1-268), Saint Kitts And Nevis (+1-869), Saint Lucia (+1-758), Saint Vincent And The Grenadines (+1-784) and Trinidad And Tobago (+1-868) all query and filter on +1 — the whole North American Numbering Plan, not that island. Vatican City (+39-06) behaves the same way and filters on +39. The country field on the row still records the country you picked, so filter the results yourself if you need one specific area code. Countries that genuinely share a code behave the same by nature: Kazakhstan and Russia both filter on +7.
Search localisation is a hint, not a guarantee. The country you pick is mapped to an ISO code and passed to Google as gl and to the proxy as its exit country, which biases the index Google searches. It does not restrict results to that country — the dial-code filter on the number does that. One mapping is off: Congo (Democratic Republic) (+243) resolves to the CG exit country rather than CD, so its SERP is localised to the neighbouring Republic of the Congo. The dial code, the filter and the country value on the row are unaffected.
Why not build this yourself?
LinkedIn's own APIs are partner-gated and expose no contact lookup, so there is no official endpoint to compare against — the alternative to this Actor is writing the Google-SERP plumbing yourself, and that plumbing is where the time goes.
The proxy is the whole ballgame. Apify's GOOGLE_SERP group intercepts the plain HTTP request, runs the search itself and hands back results HTML. The residential group instead serves a JavaScript-gated shell with no results in it, which parses as "zero phone numbers found" rather than as an error — a silent, expensive failure mode.
Block detection is a trap, not a substring match. The obvious implementation greps the body for sorry or captcha. A normal Google results page legitimately contains both words in footer links and reCAPTCHA references, so that heuristic flags every healthy page as blocked and returns nothing on every run. This Actor keys instead on four phrases that appear only on the interstitial, on the HTTP status, and on whether Google's own result-wrapper markup is present at all.
Then there is the long tail. Rotating user-agent and accept-language pools, jittered delays, a retry budget, /url?q= and /safety/go redirect unwrapping, a phone regex whose output has to survive zero-width padding, 00 prefixes, trunk zeros and duplicated country codes, and a scoring layer that has to degrade gracefully when Google returns a title but no snippet. All of it is maintained here.
How to use LinkedIn Phone Number Scraper: Lead Scoring
The Actor runs on Apify. Start it from the Apify Console or call it through the Apify API — nothing else to sign up for.
- Open LinkedIn Phone Number Scraper: Lead Scoring on Apify and click Try for free
- Add one or more entries to Keywords / Usernames / URLs (
keywords) — a job title, industry or business type works best, because the term has to appear in Google's indexed text - Pick a Country (
country) from the dropdown — this sets the quoted dial code in the query, the filter applied to every number found, and the search locale - Optionally set Minimum Lead Score (
minLeadScore) to drop weak rows, and Title Keywords (boost) (titleKeywords) to promote the roles you actually want - Click Start, then sort the dataset by
leadScoreand export as JSON, CSV or Excel, or read it through the Apify API
keywords and country are the two required inputs, and they fail differently. An empty or missing keywords list logs No keywords provided. Please fill the 'keywords' array with at least one value. and the run finishes immediately — no rows, nothing charged. A missing country does not stop the run: the code falls back to United Kingdom (+44), so an API caller who omits the field quietly gets UK numbers under a UK search locale. The Console blocks that case before the run starts; the fallback only bites callers building the input JSON themselves.
How to scale to bulk lead extraction
keywords is a list, so bulk is the normal mode — add as many terms as you like and each is searched in turn within one run. Keywords are processed sequentially, not in parallel, and rows are pushed as they are found rather than at the end.
An entry can also be a LinkedIn profile or company URL. When it matches linkedin.com/in/…, /pub/… or /company/…, the Actor pulls the slug, turns it into a name — jane-doe-9057562b becomes jane doe, with the trailing vanity id stripped — and tries up to three progressively broader queries, stopping at the first one that yields a row.
One run covers one country, because country is a single string. To cover several regions, run the Actor once per country with the same keywords list. Because country, dial_code and an absolute leadScore are stamped on every row, the datasets merge into one sortable multi-region table.
What can you do with LinkedIn phone lead data?
- 📞 A sales development rep building a call list runs five job-title keywords with
minLeadScoreat 70, then works the dataset sorted byleadScoredescending, openingurlto verify each lead before the number is ever dialled. - 🎯 A demand-gen manager targeting decision-makers sets
titleKeywordsto["founder", "vp sales"]and filters ontitleMatchandseniorityLevelto separate genuine buying-authority rows fromManagerandSenior ICnoise. - 🧹 A RevOps engineer enriching a CRM matches incoming
phone_numbervalues against existing records, routes bydial_code, and writesleadScoreandleadGradeonto the record as a prioritisation field withurlkept as the provenance link. - 🔍 A market researcher measuring how a profession publishes contact details compares row counts and the
leadGradedistribution for the samekeywordsacross differentcountryvalues, readingdescriptionto see whether numbers are switchboards or direct lines. - 🤖 An AI engineer building a lead-qualification agent indexes
description,titleandcompanyinto a vector store withleadScore,seniorityLevelandkeywordas metadata filters, so the agent can answer "which indexed LinkedIn pages in this niche publish a UK number for a founder" against live rows rather than a stale export.
Every one of these is callable from an agent framework over the Apify API, since the Actor is a standard HTTP-triggered run.
How does LinkedIn Phone Number Scraper: Lead Scoring handle rate limits and blocking?
Every request goes out through Apify's GOOGLE_SERP proxy group, which is what performs the search — you never create a proxy account or rotate an IP yourself. The proxy is asked for your chosen country as its exit location.
Each results page gets up to three attempts. Before every attempt the Actor sleeps a random 1–2 seconds and rebuilds its headers, drawing a user-agent from a pool of four current desktop browser strings and an accept-language from three. After a failed or blocked attempt it waits a further random 3–6 seconds before retrying. Requests use a 60-second total timeout, with 30 seconds each for connect and socket read.
A response counts as blocked on any status other than 200, on four literal interstitial phrases such as our systems have detected unusual traffic, or on a body that contains Google's JavaScript-gate marker while showing none of Google's result markup.
There is no CAPTCHA solving in this Actor, and none is claimed. When all three attempts fail, that page counts as one empty page for the keyword; after three consecutive empty or failed pages the Actor stops that keyword, keeps the rows already collected, and moves on. A run never dies because one keyword got blocked. If the GOOGLE_SERP proxy cannot be initialised at all, or you turn useApifyProxy off, the Actor logs a warning and continues without a proxy — requests in that state are likely to be blocked by Google.
⬇️ Input
Two inputs are required: keywords and country. Everything else has a default.
| Parameter | Required | Type | Description | Example Value |
|---|---|---|---|---|
keywords | Yes | array | A list of keywords, Linkedin usernames, or profile URLs to search for. Each entry is searched separately. Prefilled with ["marketing"]. | ["founder", "marketing director"] |
country | Yes | string | Select a country to scrape related phone numbers. The dial code is used to normalize and filter phone numbers. 194 values in the dropdown. Default "United Kingdom (+44)". | "United Kingdom (+44)" |
platform | No | string | Select platform. For now this actor targets Linkedin via google.com SERP. Enum has one value: Linkedin. Default "Linkedin". | "Linkedin" |
maxPhoneNumbers | No | integer | Maximum number of phone numbers to collect per keyword. The scraper will stop once this limit is reached. Minimum 1, maximum 10000. Default 20. | 50 |
useApifyProxy | No | boolean | Route Google search requests through Apify Proxy (recommended). Turn this off only if you intend to run without a proxy. Default true. | true |
minLeadScore | No | integer | Only keep leads whose computed leadScore (0-100) is at least this value. Rows below the threshold are dropped and never pushed; the dropped count is logged. Set 0 to keep everything. Minimum 0, maximum 100. Default 0. | 70 |
titleKeywords | No | array | Optional role/keyword list. A result whose title or description contains any of these gets a +10 leadScore boost and titleMatch=true. Leave empty to disable boosting. Default []. | ["founder", "marketing director"] |
Four honest notes on how these behave:
platformis effectively fixed. The schema exposes one value, and two things in the code pin the Actor to LinkedIn regardless: the search domain is built aslinkedin.comfrom that value, and any result whose URL does not containlinkedin.comis dropped before a row is built. The value does change one visible thing — theplatformfield in the output, which comes back asLinkedin.comrather thanLinkedin.- The proxy input is a boolean here, not an object. This Actor reads
useApifyProxyas a plaintrue/false. If you are copying input JSON from a sibling contact Actor that takes aproxyConfigurationobject, that key is simply ignored and the proxy stays on. The group is alwaysGOOGLE_SERP; only the exit country varies, and it follows yourcountrychoice. minLeadScoreis clamped in code. Values outside 0–100 are pulled back into range and a non-numeric value falls back to0, so an out-of-range API call cannot silently filter everything out.maxPhoneNumbersis bounded by the schema, not by the code. The Console enforces 1–10000. The Actor reads the value as-is, so an API caller passing0or a negative number gets a run that completes with zero rows instead of an error.
Example input
{"keywords": ["founder", "marketing director", "operations lead"],"country": "United Kingdom (+44)","platform": "Linkedin","maxPhoneNumbers": 50,"useApifyProxy": true,"minLeadScore": 70,"titleKeywords": ["founder", "marketing director"]}
⬆️ Output
Typed, normalized JSON with a consistent shape across runs — the same thirteen keys on every row, never omitted. Only company can be null. Rows are pushed live as each results page is parsed, so the dataset fills while the run is still going. Export as JSON, CSV or Excel, or read the dataset through the Apify API. The default dataset view shows all thirteen fields with leadScore first, so the table is ready to sort on arrival.
Every row in the dataset is a result row, and every result row is charged as one row_result event. This Actor writes no header rows, no diagnostic rows, no accounting rows and no error rows — there is no marker field such as errorReason, status or isError to check, and no filter to apply. A keyword that finds nothing contributes no rows at all; a run that finds nothing anywhere finishes successfully with an empty dataset and a log line saying so. Rows dropped by minLeadScore are never pushed, so they are never charged. Failures live in the log, not in the data.
Duplicates are removed across the whole run. The seen-set is keyed on the (url, phone_number) pair and is created once per run, then shared across every keyword and every results page, so the same number from the same URL is pushed — and charged — exactly once even when several keywords surface the same page. What can legitimately repeat is one URL carrying two different numbers, or one number appearing on two different LinkedIn URLs; both are genuinely distinct rows. Deduplicate on phone_number alone if you want one row per contact:
seen, unique = set(), []for row in items:if row["phone_number"] not in seen:seen.add(row["phone_number"])unique.append(row)
Example output
{"platform": "Linkedin.com","keyword": "founder","title": "Jane Doe - Founder & CEO at BrightLabs Ltd | LinkedIn","description": "Founder & CEO at BrightLabs Ltd, a Manchester-based B2B growth agency. WhatsApp or call the studio on +44 7585 467400 for new business enquiries and partnerships.","url": "https://www.linkedin.com/in/jane-doe/","phone_number": "+447585467400","country": "United Kingdom","dial_code": "+44","leadScore": 100,"leadGrade": "A","seniorityLevel": "Founder/Owner","company": "BrightLabs Ltd","titleMatch": true}
That row scores 30 for a valid +44 number, 35 for the Founder/Owner tier, 20 for a named company and 12 for a 162-character snippet — 97 — then takes the +10 boost for matching titleKeywords and is capped at 100.
A second row, from a different keyword in the same run, showing a Director-tier lead that lands one band lower:
{"platform": "Linkedin.com","keyword": "marketing director","title": "Priya Raman - Marketing Director - Northwind Media Ltd | LinkedIn","description": "Media planning and buying for independent UK retail brands. Direct line 020 7946 0958 or email via the agency website.","url": "https://www.linkedin.com/in/priya-raman/","phone_number": "+442079460958","country": "United Kingdom","dial_code": "+44","leadScore": 94,"leadGrade": "A","seniorityLevel": "Director","company": "Northwind Media Ltd","titleMatch": true}
Note the number: the snippet wrote it in national trunk form as 020 7946 0958, and normalisation swapped the leading 0 for +44. Without the titleKeywords boost this row would score 84 and grade B.
And a lower-graded row, to show what the tiers do with an ambiguous title — Lead in "Operations Lead" matches the Manager tier:
{"platform": "Linkedin.com","keyword": "operations lead","title": "Alex Whitmore - Operations Lead - Harborline Logistics | LinkedIn","description": "Freight forwarding and 3PL across the north west. Office: 0161 496 0123.","url": "https://www.linkedin.com/in/alex-whitmore/","phone_number": "+441614960123","country": "United Kingdom","dial_code": "+44","leadScore": 76,"leadGrade": "B","seniorityLevel": "Manager","company": "Harborline Logistics","titleMatch": false}
How does it work?
For each keyword the Actor builds one Google query — site:linkedin.com "+44" "marketing director" — and requests it through Apify's GOOGLE_SERP proxy over plain HTTP, ten results at a time, with English interface language (hl=en) and a gl country hint derived from your country choice. The proxy runs the search and returns the results HTML.
That HTML is parsed for result blocks using Google's result-wrapper class names, with a second known wrapper as fallback. Each block's link is unwrapped from Google's /url?q= redirect, non-LinkedIn links are discarded, and the block's visible text is scanned for a phone-shaped string that survives normalisation and starts with your country's dial code. Only then is a row built — and only at that point is the lead score computed, from the same title, snippet and number that go into the row.
Only publicly visible data is collected: this is Google's public index, with no LinkedIn account, cookie or session involved anywhere. And because the thirteen output keys and the scoring formula are defined by the Actor rather than by Google's markup, a Google layout change can affect coverage but never your field names, types or score scale.
Integrations
LinkedIn Phone Number Scraper: Lead Scoring is an Apify Actor, so it works with anything that can call the Apify API or consume a dataset.
Calling LinkedIn Phone Number Scraper: Lead Scoring programmatically
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("<YOUR_USERNAME>/linkedin-phone-lead-scorer").call(run_input={"keywords": ["founder", "marketing director"],"country": "United Kingdom (+44)","maxPhoneNumbers": 50,"minLeadScore": 70,"titleKeywords": ["founder", "marketing director"],})for row in client.dataset(run["defaultDatasetId"]).iterate_items():print(row["leadGrade"], row["leadScore"], row["phone_number"], row["company"], row["url"])
Works in Go, Ruby, Node.js, cURL — any language that can make an HTTP request. Every row has the same thirteen keys, so no branching or presence checks are needed beyond a null guard on company.
No-code tools (n8n, Make, LangChain)
In n8n, use the Apify node — or an HTTP Request node pointed at the Apify run endpoint with your token — and pass the same JSON input shown above; a Filter node on leadScore gives you a second threshold without re-running the Actor. In Make, the Apify module supports run-and-wait, so a weekly keyword sweep can feed a Google Sheets, Airtable or CRM step directly, sorted by grade. In LangChain, wrap the run endpoint as a tool and hand the rows straight to the model, since they are already flat JSON with numeric scores. Apify schedules and webhooks cover recurrence and completion triggers.
⚠️ Is it legal to scrape LinkedIn phone numbers?
Collecting publicly indexed contact details is generally treated as permissible where no login is bypassed, and this Actor reads only Google's public index. But the output is personal contact data — the most sensitive category of scraped data — and that changes your obligations rather than removing them.
A phone number tied to an identifiable person is personal data under every major regime, and a LinkedIn URL, name and job description alongside it makes the record more identifying, not less. The moment you store a row, you are a data controller. Under GDPR and UK GDPR you need a lawful basis before storing or reusing it; if you rely on legitimate interest, that requires a documented balancing test, not an assumption. Because the data was not collected from the individual, the Article 14 duty to inform them applies, along with access and erasure rights they can exercise against you.
Using a number is regulated separately, and more strictly, than collecting it. Direct marketing in the EU and UK falls under ePrivacy and PECR; calling and texting in the United States is governed by its own federal and state rules, including do-not-call registries. "The number was public" is not a lawful basis for calling or texting it.
Risk is also not uniform: a company switchboard published on a business page is a materially different proposition from an individual's mobile posted in a personal profile, and this Actor does not tell the two apart. Screen every list against suppression and do-not-call registers before any outreach, and take advice from qualified counsel for your jurisdiction. This Actor gives you data and a score; it does not give you permission to contact anyone.
❓ Frequently asked questions
What LinkedIn fields does LinkedIn Phone Number Scraper: Lead Scoring return?
Thirteen, on every row: leadScore, leadGrade, seniorityLevel, company, titleMatch, platform, keyword, title, description, url, phone_number, country and dial_code. The five you will use most are phone_number, leadScore, url, seniorityLevel and company. See the data fields table above for what each one contains.
Does LinkedIn Phone Number Scraper: Lead Scoring require a LinkedIn account or login?
No — and it cannot use one. The Actor never sends a request to LinkedIn; it queries Google's index of linkedin.com pages through Apify's GOOGLE_SERP proxy. There is no cookie, no session and no LinkedIn account to get flagged. The only credential involved is your Apify token.
Is leadScore a LinkedIn metric?
No. It is computed by this Actor from the row's own title, description, phone_number and dial_code, plus any titleKeywords you supply. Neither LinkedIn nor Google publishes it. The full formula and every weight are in the scoring section above, and no accuracy figure is claimed for it — it is a ranking aid, not a verdict on a person.
Can I compare leadScore across two different runs?
Yes, provided both runs used the same titleKeywords list. Nothing in the formula depends on the other rows in the run — no average, no median, no percentile — so a given result scores identically whenever it is found. That is what makes datasets from separate keyword or country runs safe to merge and sort together. Only the +10 boost varies with your input, so changing titleKeywords between runs shifts some scores by exactly 10.
How many phone numbers can I extract in one run?
maxPhoneNumbers accepts 1 to 10,000 and applies per keyword, so twenty keywords at 100 each is a 2,000-row target in one run. Whether you reach it depends on Google's index rather than on the Actor: a keyword stops early once three consecutive results pages return nothing usable. Rows dropped by minLeadScore do not consume the quota.
What happens if a keyword returns no results?
The run continues and finishes successfully. There is no error row and no diagnostic row — a keyword that finds nothing simply contributes no rows, the Actor logs • No more phone numbers found for this keyword. and moves to the next keyword. If nothing is found for any keyword, the dataset is empty and the log ends with a line explaining that public phone numbers for those keywords were not present in Google's indexed snippets. In code, treat an empty result set as "no indexed match", not as a failure: if not items: ....
Four causes account for almost all empty results. Google may have no indexed linkedin.com page containing both your keyword and the dial-code digits in visible text. The keyword may be too narrow or too brand-specific to appear in indexed text at all — a single profile URL is the hardest case, which is why the Actor falls back to broader queries for those. minLeadScore may be set high enough to drop everything that was found; the log reports the dropped count, so check that line before blaming coverage. Or Google may have blocked the pages, which the log records per attempt.
Can I scrape multiple keywords or countries at once?
Multiple keywords, yes — keywords is a list and each entry is searched in turn within one run, with the originating term stamped on every row as keyword. Countries, no: country is a single string, so one run covers one dial code. Run the Actor once per country with the same keyword list; because country, dial_code and an absolute leadScore are on every row, the datasets merge into one sortable multi-region table.
Does minLeadScore change what I am charged?
Yes, downward. Charging is per pushed row as a row_result event, and rows below the threshold are never pushed. The trade-off is that a high threshold on a broad keyword means more Google pages fetched for the same number of billed rows, since dropped rows do not count toward maxPhoneNumbers. Set it to 0 to keep everything and filter afterwards.
Does LinkedIn Phone Number Scraper: Lead Scoring work with Claude, ChatGPT and other AI agent tools?
Yes. It is callable as a standard HTTP-triggered run through the Apify API, so LangChain, CrewAI, n8n or a hand-written tool definition can invoke it and receive typed JSON with no parsing step. Because the row shape is fixed at thirteen keys with numeric leadScore, an agent tool schema for it is trivial to write and the results are directly rankable.
How does it compare to other LinkedIn phone number scrapers?
Checked on the Apify Store on 25 July 2026. scraper-mind/linkedin-phone-number-scraper documents three inputs on its listing — keyword as a single string with default "software engineer", platform, and country with default "United States (+1)" — and four output fields: title, url, description and phone_number. Its listing describes deduplication and E.164 formatting; no per-keyword result cap, no list input, no scoring or filtering fields are documented, and its example output shows numbers partially masked. williams_asante/scrape-haven-get-leads-emails-and-phone-numbers ranks for the same terms but its README documents a Google Maps business scraper, taking search_for and total and returning business name, address, phone, email, website, social links and review average — a different source and a different entity, useful when you want businesses rather than named professionals. Its listing documents pricing at $50 per month. A third listing, weighty_marshmallow/phone-number-enrichment-from-linkedin-profile-url-80-coverage, takes a profile URL according to its listing name; its README could not be retrieved when checked on that date, so nothing further about it is documented here.
Against all three, the observable differences here are the list-valued keywords input, the per-keyword maxPhoneNumbers cap, run-wide deduplication on (url, phone_number), and the five computed scoring keys with a published formula.
Does it return data in a format LLMs can use directly?
Yes. Every row is typed, normalized JSON with the same thirteen field names across runs. No HTML parsing, no selectors, no optional keys to guard against beyond a null on company. Pass a row straight into an LLM context window, index it into a vector store, or hand it to an agent tool.
What happens when Google changes its layout or anti-bot system?
The scraper is maintained, and the output schema is defined by the Actor rather than by Google's markup, so your thirteen field names, types and the 0–100 score scale stay put regardless. Layout changes affect coverage, not shape — the parser tries Google's result-wrapper class name and falls back to a second known wrapper, and block detection leans on HTTP status and interstitial-only phrases rather than on brittle body-text matching against words that appear on healthy pages.
Can I use it without managing proxies or browser infrastructure?
Yes. The Actor selects Apify's GOOGLE_SERP proxy group itself, sets the exit country from your country choice, and rotates user-agents and accept-language headers per attempt. You never create a proxy account, pick a group or rotate an IP, and no browser is involved — the requests are plain HTTP. It does not solve CAPTCHAs, and it makes no claim to: a page that stays blocked after three attempts is counted as an empty page and the run carries on.
Which fields work best for AI training data and RAG indexing?
For RAG indexing, description carries by far the most information per record — it is the natural-language snippet the number appeared in — with title as a shorter second text field and company as a useful entity anchor. For metadata filters, leadScore, leadGrade, seniorityLevel, titleMatch and keyword are the ready-made facets, and all of them are typed primitives: an integer, and four strings or a boolean. For training data or feature extraction, phone_number, dial_code, country and platform are the most structurally consistent, since all four are normalised rather than passed through. Keep the personal-data obligations in the legal section in mind before pushing contact numbers into a training set or a shared vector store.
🔗 Related scrapers
| Scraper Name | What it extracts |
|---|---|
| LinkedIn Profile Phone Number Scraper By Seniority & Title Filter | The same Google-index method, filtering results by seniority and job function instead of scoring them |
| LinkedIn Profile & Company Enrichment Scraper | Profile and employer detail for a lead you found here, to qualify it before outreach |
| LinkedIn Company URL - Mass Profile Finder With Firmographics | Company pages and firmographics behind a company value |
| LinkedIn Jobs Scraper With Salary Range Filters | Job listings with salary ranges, for hiring-signal targeting |
| Instagram Phone Number Scraper | The same phone-number method applied to instagram.com |
| Tiktok Video Scraper (Creator Profile & Contact) | Creator contact details attached to TikTok videos |
💬 Your feedback
Found a bug, or a keyword and country pair that returns nothing when you expect results? Open an issue on the Actor's Issues tab. Reports that include the exact input JSON — the keyword list, the country string and your minLeadScore — are the fastest to reproduce and fix, and score disputes are easiest to settle when you paste the row's title and description alongside the grade.