๐ŸŽ“ LinkedIn Profile โ€” Courses & Recommendations Signal avatar

๐ŸŽ“ LinkedIn Profile โ€” Courses & Recommendations Signal

Pricing

$24.99/month + usage

Go to Apify Store
๐ŸŽ“ LinkedIn Profile โ€” Courses & Recommendations Signal

๐ŸŽ“ LinkedIn Profile โ€” Courses & Recommendations Signal

Extract detailed LinkedIn profiles with the LinkedIn Profile Scraper. Retrieve names, job titles, company info, education, skills, and experience. Ideal for lead generation, recruitment, and professional research. Fast, accurate, and scalable for single or bulk profiles.

Pricing

$24.99/month + usage

Rating

0.0

(0)

Developer

Scrapier

Scrapier

Maintained by Community

Actor stats

0

Bookmarked

154

Total users

9

Monthly active users

39 days

Issues response

6 days ago

Last modified

Share

LinkedIn Profile Scraper โ€” Courses & Recommendation Signals as JSON

LinkedIn Profile โ€” Courses & Recommendations Signal scrapes public LinkedIn profiles โ€” supplied as direct URLs, or discovered from a company's employee list โ€” and returns one structured JSON record per profile: identity fields, current employer, location, plus two derived signal blocks most profile scrapers skip entirely, course_signal from the Recommended-courses module and recommendation_signal from received Recommendations text. Every response is typed JSON, ready to pass straight into an LLM prompt, a vector store, or a CRM enrichment job. Run it again on the same profile list later and the signal blocks become a trackable read on what a person is learning and who is vouching for them.

What is LinkedIn Profile โ€” Courses & Recommendations Signal?

LinkedIn Profile โ€” Courses & Recommendations Signal is an Apify Actor that scrapes public, logged-out LinkedIn profile pages and layers two derived signal blocks on top of the same base fields a standard profile scraper returns. No LinkedIn account, password, or session cookie is required as input โ€” the Actor reads the public HTML and Open Graph metadata LinkedIn serves to a logged-out visitor, and only optionally uses Playwright to pick up guest cookies that make that fetch more reliable. What makes it different from a generic profile scraper is what happens after the fetch: the scraped Recommended courses module and received Recommendations text are run through a keyword-theme classifier that only assigns a theme when a keyword genuinely begins a word in the source text, with the matched keyword(s) shipped alongside every theme as provenance.

  • Scrape profiles directly from a list of LinkedIn URLs or bare vanity slugs (mode: "profiles")
  • Discover and scrape a company's employees from its public company page plus a best-effort Google search fallback (mode: "company")
  • Derive course_signal โ€” course count, total scraped learning minutes, course titles, and keyword themes from the profile's Recommended-courses module
  • Derive recommendation_signal โ€” recommendation count, recommender names, and keyword themes from received Recommendation text
  • Optionally upgrade both signal blocks into a seniority / skills / lead-quality classification with an LLM (aiEnhancement)
  • Switch to rawData mode to get the plain base profile record with no derived signals at all
  • Route every request through Apify Proxy, with Residential recommended for LinkedIn's anti-bot wall

What data can you get with LinkedIn Profile โ€” Courses & Recommendations Signal?

Every run returns one JSON row per input profile (or per discovered employee, in company mode), built from five distinct pieces of data.

Result TypeExtracted FieldsPrimary Use Case
Base profilepublic_identifier, fullname, first_name, last_name, headline, profile_picture_url, location (full, city, country, country_code), current_company, companies_detected[], personal_website, profile_url, created_timestampContact identification, lead records, CRM enrichment
Course signalcourse_signal.course_count, .total_learning_minutes, .course_titles[], .themes[], .matched_keywords[]Skill and learning-interest inference from LinkedIn Learning activity
Recommendation signalrecommendation_signal.count, .recommenders[], .themes[], .matched_keywords[]Peer-endorsed strengths, social-proof signal for outreach
AI classification (opt-in)ai.seniority, .skills[], .lead_quality, .rationaleLLM-upgraded lead qualification from the same scraped text
Run-level signal summary (KV store)signal_summary.profiles_enriched, .total_courses, .total_learning_minutes, .total_recommendations, .course_theme_frequency, .recommendation_theme_frequencyBatch-level rollup for reporting or alerting

Course & recommendation signal blocks

This is the part a plain profile scraper doesn't do. course_signal counts the courses listed in a profile's "Recommended courses" module, sums their parsed durations into total_learning_minutes, and classifies the course titles into up to 12 themes (ai_ml, data, security, software_dev, leadership, marketing, sales, design, finance, product, communication, career). recommendation_signal does the same over the text of received Recommendations, against 8 themes (leadership, technical, collaboration, delivery, communication, mentorship, reliability, creativity). A theme is only assigned when one of its keywords matches at a word start (\bkeyword) inside the scraped text โ€” so ship never matches leadership and api never matches capital โ€” and every assigned theme carries its matched_keywords so you can see exactly which words triggered it, not just trust a label:

{
"course_signal": {
"course_count": 1,
"total_learning_minutes": 72,
"course_titles": ["Product Management Foundations"],
"themes": ["product"],
"matched_keywords": ["product management"]
},
"recommendation_signal": {
"count": 1,
"recommenders": ["Jane Smith"],
"themes": ["leadership"],
"matched_keywords": ["leader"]
}
}

Note the field-name asymmetry: course_signal.course_count shares its name with the flattened dataset column course_count, while recommendation_signal.count is flattened to a differently-named column, recommendation_count โ€” both are documented in the Input/Output section below exactly as the source names them.

AI classification (opt-in)

With aiEnhancement on and rawData off, each non-error profile is additionally sent to an LLM โ€” headline, recommendation texts, and course titles only โ€” which returns a structured ai block: seniority (junior|mid|senior|lead|executive|unknown), up to 8 skills, a lead_quality verdict (low|medium|high), and a one-sentence rationale. The provider is auto-detected from the aiModel name you pick (Anthropic, OpenAI, Google, xAI, DeepSeek, Perplexity, or Mistral), and classification silently returns null on any provider error or missing API key rather than guessing a value.

Why not build this yourself?

LinkedIn does not expose a public, self-serve API for searching or bulk-fetching profile data โ€” the official LinkedIn APIs are partner-gated and built for things like "Sign In with LinkedIn" or approved marketing integrations, not for pulling course and recommendation content off arbitrary public profiles. Getting there yourself means reverse-engineering the public profile page's HTML and JSON-LD structure, handling the authwall/login-redirect LinkedIn serves to suspicious traffic, rotating and paying for proxies, and rebuilding your parsing selectors every time LinkedIn changes its markup โ€” all before you've written a single line of theme-extraction logic.

LinkedIn Profile โ€” Courses & Recommendations Signal already does this work: it detects authwall/challenge redirects and retries with a fresh guest-cookie fetch and a longer backoff before giving up and reporting an honest {profile_url, error} row; it resolves the proxy from your proxyConfiguration input at runtime rather than hardcoding one; and its theme classifier is a deliberately narrow, auditable word-boundary match rather than an opaque score. Maintaining those selectors, the retry/backoff ladder, and the keyword-theme tables as LinkedIn's markup shifts is the ongoing work this Actor takes off your plate.

What's the difference between a LinkedIn Learning catalog scraper and a profile course-signal actor?

A LinkedIn Learning catalog scraper returns course-catalog data with no person attached to it; a profile course-signal actor ties courses to the specific profile that has them listed and turns that into a per-person signal. The distinction matters because the two answer different questions: a catalog scraper tells you what courses exist on LinkedIn Learning, while a profile course-signal actor tells you what a specific lead, candidate, or contact has been learning. As observed on the Apify Store on 2026-07-26, parseforge's LinkedIn Learning Catalog Scraper documents its output fields as id, scrapedAt, sourceUrl, and title โ€” course records with no owner. LinkedIn Profile โ€” Courses & Recommendations Signal instead nests the same kind of course data (title, duration, url) under a specific profile's contact_elements.learning_content.course_details, and rolls it up into that profile's own course_signal.themes, so the output answers "what has this person been studying" rather than "what's in the catalog."

How to scrape LinkedIn profiles with LinkedIn Profile โ€” Courses & Recommendations Signal?

  1. Open LinkedIn Profile โ€” Courses & Recommendations Signal on the Apify Store and click Try for free, or find it under your Actors in Apify Console if you've run it before.
  2. Choose mode: profiles to scrape a known list of URLs (the reliable path), or company to discover a company's employees first.
  3. Fill in the matching field โ€” profileUrls (one URL or bare slug per line) for profiles mode, or companies (slugs or company URLs) plus maxEmployees for company mode.
  4. Leave rawData off to get the full signal layer, or turn it on for plain base records only. Turn on aiEnhancement and supply aiModel/aiApiKey if you want the LLM classification block.
  5. Click Start, then open the Dataset tab when the run finishes and export as JSON, CSV, Excel, or the other formats Apify supports, or pull results via the Apify API.

A minimal real request in profiles mode:

{
"mode": "profiles",
"profileUrls": ["https://www.linkedin.com/in/timbakke/", "satyanadella"]
}

How to run multiple profiles or companies in one job

Batching is the array itself โ€” profileUrls accepts one URL or slug per line and every one is deduplicated and scraped in the same run; companies works the same way for company mode, with maxEmployees (default 20, max 200) capping how many discovered employees are pulled per company. Profiles and companies are processed one at a time in a sequential loop with a jittered delay between requests, rather than in parallel โ€” there is no concurrency parameter exposed in the input schema, so throughput scales with how many profiles you queue, not with a tunable worker count.

โฌ‡๏ธ Input

Every field below is optional โ€” there is no required input, though mode: profiles needs profileUrls and mode: company needs companies to actually do anything.

ParameterRequiredTypeDescriptionExample Value
modeNostring (enum)How to source profiles: profiles scrapes the exact URLs you provide (reliable); company discovers a company's employees via company pages + Google search, then scrapes them (Google discovery is brittle and usually blocked from datacenter IPs). Default "profiles"."profiles"
profileUrlsNoarray of stringsUsed when mode = profiles. Full profile links or bare slugs, one per line, e.g. https://www.linkedin.com/in/timbakke/ or timbakke.["https://www.linkedin.com/in/timbakke/"]
companiesNoarray of stringsUsed when mode = company. Company slugs or LinkedIn company URLs, e.g. microsoft or https://www.linkedin.com/company/microsoft. Each is expanded to up to maxEmployees profiles.["microsoft"]
maxEmployeesNointeger (min 1, max 200)Used when mode = company. Caps how many discovered employee profiles are scraped per company. Default 20.20
rawDataNobooleanEmit the plain base profile records only โ€” no course_signal, recommendation_signal, ai, or signal_summary. Turn off to get the full signal layer. Default false.false
aiEnhancementNobooleanAdds an ai block (seniority, skills, lead_quality, rationale) per profile. Ignored in raw-data mode. Default false.false
aiModelNostring (enum)AI provider/model for classification. Provider is auto-detected from the name. Default "claude-haiku-4-5"."claude-haiku-4-5"
aiApiKeyNostring (secret)API key for the selected provider. Leave blank to fall back to the provider's environment variable. Only used when aiEnhancement is on.(blank โ€” uses env var)
proxyConfigurationNoobject (proxy)Apify Proxy configuration. RESIDENTIAL strongly recommended for LinkedIn. Default {"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}.{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}

aiModel accepts one of the following 21 values, grouped by the provider the model name maps to:

ProviderAccepted aiModel values
Anthropicclaude-haiku-4-5, claude-sonnet-5, claude-opus-4-8, claude-fable-5
OpenAIgpt-4o-mini, gpt-4o, gpt-4.1-mini, gpt-4.1, o3-mini, o1
Googlegemini-2.0-flash-lite, gemini-2.0-flash, gemini-2.5-flash, gemini-2.5-pro
xAIgrok-3-mini, grok-3
DeepSeekdeepseek-chat, deepseek-reasoner
Perplexitysonar, sonar-pro
Mistralmistral-small-latest, mistral-large-latest

If aiApiKey is left blank, the Actor falls back to the matching environment variable for the detected provider: ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY (or GOOGLE_API_KEY), XAI_API_KEY (or GROK_API_KEY), DEEPSEEK_API_KEY, PERPLEXITY_API_KEY (or PPLX_API_KEY), MISTRAL_API_KEY.

Example JSON input, company mode with the AI layer on:

{
"mode": "company",
"companies": ["microsoft"],
"maxEmployees": 20,
"rawData": false,
"aiEnhancement": true,
"aiModel": "claude-haiku-4-5",
"aiApiKey": "",
"proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}

The most common input mistake is relying on mode: company as the primary path: employee discovery combines links found on the company's public pages with a site:linkedin.com/in/ "<company name>" Google search, and that Google step is usually blocked (HTTP 429 or a consent wall) from datacenter and even residential proxy IPs โ€” it reports honestly and returns whatever it collected rather than fabricating URLs, but that can mean far fewer employees than maxEmployees asks for. If you already know which profiles you want, mode: profiles with profileUrls is the reliable path.

โฌ†๏ธ Output

Each run pushes one typed, normalized JSON row per input profile (or discovered employee) to the Apify Dataset, exportable as JSON, CSV, Excel, XML, RSS, or an HTML table from Apify Console or the API. Every row carries a scrapedAt ISO-8601 timestamp regardless of mode. A profile that couldn't be scraped still gets a row โ€” {"profile_url": ..., "error": "<reason>", "created_timestamp": ...} โ€” rather than being silently dropped; observed error strings in the source are HTTP <status>, Empty response from LinkedIn, Login required or blocked (authwall), No data extracted, Max retries exceeded, or the raw exception text.

Scraped results (enrichment mode โ€” rawData: false)

[
{
"company_url": "https://www.linkedin.com/company/microsoft",
"profile_url": "https://www.linkedin.com/in/timbakke/",
"fullname": "Tim Bakke",
"first_name": "Tim",
"last_name": "Bakke",
"headline": "Product Manager at Microsoft",
"public_identifier": "timbakke",
"profile_picture_url": "https://media.licdn.com/dms/image/tim-bakke.jpg",
"location": { "country": "United States", "city": "Seattle", "full": "Seattle, Washington, United States", "country_code": "US" },
"is_creator": null,
"is_influencer": null,
"is_premium": null,
"created_timestamp": 1785000000,
"show_follower_count": null,
"current_company": "Microsoft",
"companies_detected": [ { "name": "Microsoft", "slug": "microsoft", "url": "https://www.linkedin.com/company/microsoft" } ],
"personal_website": "",
"recommendations_received": [ { "text": "Tim is a fantastic collaborator and always ships on time.", "author": "Jane Smith", "author_url": "https://www.linkedin.com/in/jane-smith/" } ],
"other_contact_details": { "course_links": ["https://www.linkedin.com/learning/product-management-foundations"] },
"contact_elements": {
"profile_identity": { "profile_identifier": "https://www.linkedin.com/in/timbakke/", "profile_picture": "https://media.licdn.com/dms/image/tim-bakke.jpg", "profile_name_display": "Tim Bakke" },
"auth_points": { "email_phone_input_present": false, "password_input_present": false, "login_form_action": "" },
"learning_content": {
"course_details": [ { "title": "Product Management Foundations", "duration": "1h 12m", "url": "https://www.linkedin.com/learning/product-management-foundations", "image": "https://media.licdn.com/dms/image/course.jpg" } ],
"course_titles": ["Product Management Foundations"],
"all_courses_link": "https://www.linkedin.com/learning/me"
},
"platform": { "corporate_logo_url": "", "copyright": "LinkedIn Corporation ยฉ 2026", "policy_links": [], "about_links": [], "privacy_links": [], "user_agreement_links": [] },
"language": { "selector_present": false, "locales": [] }
},
"course_signal": { "course_count": 1, "total_learning_minutes": 72, "course_titles": ["Product Management Foundations"], "themes": ["product"], "matched_keywords": ["product management"] },
"recommendation_signal": { "count": 1, "recommenders": ["Jane Smith"], "themes": ["leadership"], "matched_keywords": ["leader"] },
"scrapedAt": "2026-07-26T00:00:00Z",
"location_display": "Seattle, Washington, United States",
"course_count": 1,
"courseThemes": "product",
"recommendation_count": 1,
"recThemes": "leadership"
},
{
"company_url": "",
"profile_url": "https://www.linkedin.com/in/example-noSignals/",
"fullname": "Alex Rivera",
"first_name": "Alex",
"last_name": "Rivera",
"headline": "Software Engineer",
"public_identifier": "example-noSignals",
"profile_picture_url": "",
"location": { "country": "", "city": "", "full": "", "country_code": "" },
"is_creator": null,
"is_influencer": null,
"is_premium": null,
"created_timestamp": 1785000010,
"show_follower_count": null,
"current_company": "",
"companies_detected": [],
"personal_website": "",
"recommendations_received": [],
"other_contact_details": { "course_links": [] },
"contact_elements": {
"profile_identity": { "profile_identifier": "https://www.linkedin.com/in/example-noSignals/", "profile_picture": "", "profile_name_display": "Alex Rivera" },
"auth_points": { "email_phone_input_present": false, "password_input_present": false, "login_form_action": "" },
"learning_content": { "course_details": [], "course_titles": [], "all_courses_link": "" },
"platform": { "corporate_logo_url": "", "copyright": "", "policy_links": [], "about_links": [], "privacy_links": [], "user_agreement_links": [] },
"language": { "selector_present": false, "locales": [] }
},
"course_signal": { "course_count": 0, "total_learning_minutes": 0, "course_titles": [], "themes": [], "matched_keywords": [] },
"recommendation_signal": { "count": 0, "recommenders": [], "themes": [], "matched_keywords": [] },
"scrapedAt": "2026-07-26T00:00:00Z",
"location_display": "",
"course_count": 0,
"courseThemes": "",
"recommendation_count": 0,
"recThemes": ""
},
{
"profile_url": "https://www.linkedin.com/in/blocked-example/",
"error": "Login required or blocked (authwall)",
"created_timestamp": 1785000020,
"scrapedAt": "2026-07-26T00:00:00Z",
"location_display": "",
"course_count": null,
"courseThemes": "",
"recommendation_count": null,
"recThemes": ""
}
]

That third row is a real behavior worth calling out: an error record still runs through the same row-shaping step as a successful one, so it still picks up location_display, course_count, courseThemes, recommendation_count, and recThemes โ€” just as ""/null rather than omitted โ€” even though it never had a course_signal or recommendation_signal block to derive them from.

Raw data mode (rawData: true)

With rawData on, every field above under contact_elements, course_signal, recommendation_signal, and the flattened location_display/course_count/courseThemes/recommendation_count/recThemes columns is dropped. A row is just the base scraped record plus scrapedAt:

{
"company_url": "https://www.linkedin.com/company/microsoft",
"profile_url": "https://www.linkedin.com/in/timbakke/",
"fullname": "Tim Bakke",
"headline": "Product Manager at Microsoft",
"public_identifier": "timbakke",
"location": { "country": "United States", "city": "Seattle", "full": "Seattle, Washington, United States", "country_code": "US" },
"current_company": "Microsoft",
"recommendations_received": [ { "text": "Tim is a fantastic collaborator and always ships on time.", "author": "Jane Smith", "author_url": "https://www.linkedin.com/in/jane-smith/" } ],
"contact_elements": { "learning_content": { "course_details": [ { "title": "Product Management Foundations", "duration": "1h 12m", "url": "https://www.linkedin.com/learning/product-management-foundations" } ] } },
"scrapedAt": "2026-07-26T00:00:00Z"
}

Run-level signal summary (Key-Value store)

When rawData is off, the Actor also writes one aggregated object to the run's default Key-Value store under the key signal_summary, rolling up theme frequencies and totals across every profile in the run:

{
"variant": "course_recommendation_signals",
"mode": "profiles",
"count": 3,
"errors": 1,
"generated_at": "2026-07-26T00:00:00Z",
"signal_summary": {
"profiles_enriched": 2,
"total_courses": 1,
"total_learning_minutes": 72,
"total_recommendations": 1,
"course_theme_frequency": { "product": 1 },
"recommendation_theme_frequency": { "leadership": 1 }
}
}

Each dataset row is pushed with the pay-per-event charge label row_result (Actor.push_data(row, charged_event_name="row_result") in the source). That event only produces a charge once Pay-Per-Event pricing for row_result is configured on this Actor's Store listing โ€” otherwise a run is billed under Apify's standard consumption-based pricing for the compute and proxy usage it consumes. The Actor also checks event_charge_limit_reached after every push and stops the run early with Actor.exit(status_message="User spending limit reached") if your account's spending limit is hit mid-run.

How can I use the data extracted with LinkedIn Profile โ€” Courses & Recommendations Signal?

  • ๐ŸŽฏ Sales, recruiting, and BD teams: feed a target list of profileUrls, then use course_signal.themes and recommendation_signal.themes as a first-pass skills/strengths read on a lead or candidate before an outreach or screening call โ€” current_company and headline give the context, the signal blocks give the substance.
  • ๐Ÿค– AI engineers and LLM developers: because every field is typed JSON with stable names, an agent tool can call this Actor, drop a row straight into an LLM's context window, and let the model reason over course_signal/recommendation_signal instead of parsing raw profile HTML itself.
  • ๐Ÿ“ˆ People-analytics and L&D teams: course_theme_frequency and total_learning_minutes in the run-level signal_summary give a batch-level read on what a cohort of profiles is actually studying, without manually opening each profile's Recommended-courses module.
  • ๐Ÿ”ฌ Market and competitive researchers: run the same companies list on a schedule and compare recommendation_theme_frequency and headcount-adjacent signals like companies_detected across runs to track how a company's public-facing talent narrative shifts over time.

How do you monitor course and recommendation signals over time?

Course and recommendation signals aren't static โ€” a profile's Recommended-courses module and received Recommendations change as a person completes courses or gets endorsed, so the useful discipline here is diffing, not a single snapshot. Run the same profileUrls (or companies) list on a recurring schedule and compare the new run's course_signal.themes, course_signal.course_count, course_signal.total_learning_minutes, recommendation_signal.themes, and recommendation_signal.count against the previous run's values for the same public_identifier. A new theme appearing in course_signal.themes with a fresh matched_keywords entry is a concrete, checkable signal that someone has started building a specific skill; a jump in recommendation_signal.count is a concrete signal that someone new has publicly vouched for them. The run-level signal_summary.course_theme_frequency and .recommendation_theme_frequency do the same comparison at the batch level, so you can track whether a theme is trending up or down across an entire target list rather than one person at a time.

The Actor itself has no built-in cron or webhook delivery โ€” the real scheduling mechanism is the Apify platform's own Schedules feature (Apify Console โ†’ Schedules), which can trigger this Actor on a recurring interval with the same input, and Apify's dataset/run webhooks, which can push a notification or payload to your own endpoint when each scheduled run finishes.

Integrate LinkedIn Profile โ€” Courses & Recommendations Signal and automate your workflow

LinkedIn Profile โ€” Courses & Recommendations Signal works with any language or tool that can send an HTTP request, since it runs as a standard Apify Actor reachable through the Apify API.

REST API with Python

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("linkedin-profile-course-signals").call(
run_input={"mode": "profiles", "profileUrls": ["https://www.linkedin.com/in/timbakke/"]}
)
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
if "error" in row:
continue
print(row["fullname"], row["course_signal"]["themes"], row["recommendation_signal"]["themes"])

MCP for query-grounded AI agents

Any Apify Actor, including this one, is reachable from an MCP-compatible client through Apify's MCP Server at https://mcp.apify.com, or by running npx @apify/actors-mcp-server locally with ACTORS=linkedin-profile-course-signals set. An agent issues a natural-language question, the MCP tool call runs this Actor against the relevant profileUrls or companies, and the agent receives structured JSON back to ground its answer in โ€” rather than guessing at a lead's skills or background. Compatible with Claude, Cursor, and other MCP clients.

Scheduled monitoring and delivery

There is no webhook input field on this Actor, so real-time push delivery isn't a built-in feature โ€” the platform pattern is an Apify Schedule that reruns the Actor on an interval, combined with an Apify dataset or run webhook pointed at your own endpoint to receive each run's results as they complete.

Yes โ€” scraping publicly accessible web pages is generally legal, and LinkedIn Profile โ€” Courses & Recommendations Signal only accesses public, logged-out LinkedIn profile pages; it does not log in, use a stored session, or access anything behind LinkedIn's authwall. In hiQ Labs, Inc. v. LinkedIn Corp. (9th Cir. 2019), the court held that scraping data LinkedIn makes publicly accessible does not violate the Computer Fraud and Abuse Act. Because this Actor's output is built from a person's name, headline, employer, location, and recommendation text, it is personal data, and storing or using it should have a lawful basis under GDPR/CCPA if you operate in or target the EU/California โ€” this is a different framing from a product-catalog or listings scraper, which is governed primarily by terms-of-service and database-rights considerations rather than personal-data law. Scraping for one-off research monitoring and scraping to build a bulk-stored dataset for AI training or resale carry different risk profiles. Consult your legal team for commercial use cases involving bulk data storage.

โ“ Frequently asked questions

Which AI providers can I use for classification?

Seven: Anthropic, OpenAI, Google, xAI, DeepSeek, Perplexity, and Mistral, selected via the aiModel parameter โ€” the provider is auto-detected from the model name prefix (claude-* = Anthropic, gpt-*/o1/o3-* = OpenAI, gemini-* = Google, grok-* = xAI, deepseek-* = DeepSeek, sonar* = Perplexity, mistral-*/ministral-*/codestral-*/magistral-*/pixtral-* = Mistral). The default, claude-haiku-4-5, is one of the cheaper options; the input description recommends a cheap/mini/flash/haiku-tier model for this kind of classification task.

What's the difference between mode: profiles and mode: company?

mode: profiles scrapes the exact profileUrls you list โ€” it's the reliable path since every URL is fetched directly. mode: company instead takes companies (slugs or company URLs), finds up to maxEmployees employee profile URLs by combining links found on the company's public pages with a best-effort Google search, and then scrapes those discovered profiles the same way. The Google-discovery step is brittle and often blocked by HTTP 429s or a consent wall from cloud IPs, so company mode can return fewer employees than maxEmployees asks for.

How does LinkedIn Profile โ€” Courses & Recommendations Signal handle LinkedIn's anti-bot measures?

It retries a blocked or empty fetch up to 3 times per profile with a jittered delay between attempts that grows with each retry, and optionally refreshes guest cookies via a headless Playwright browser before the first attempt and again before the last. If the response redirects to a login/challenge/authwall page or is too short to be a real profile, the attempt is treated as a failure and retried; if every attempt fails, the profile is returned as an honest {profile_url, error} row rather than silently dropped or faked. Requests are also routed through Apify Proxy via the proxyConfiguration input, with a Residential proxy group recommended for LinkedIn specifically.

Does LinkedIn Profile โ€” Courses & Recommendations Signal extract course and recommendation signals?

Yes โ€” course_signal (from the scraped Recommended-courses module) and recommendation_signal (from received Recommendation text) are added to every non-error profile row when rawData is off. They are absent when rawData is on, and an error row (a profile that couldn't be scraped at all) never gets either block, though it still carries the flattened course_count/recommendation_count/courseThemes/recThemes columns as null/empty strings.

How many profiles does LinkedIn Profile โ€” Courses & Recommendations Signal return per run?

In profiles mode, exactly one row per unique URL/slug you provide in profileUrls (duplicates are normalized and deduplicated before scraping). In company mode, up to maxEmployees rows per company in companies (default 20, maximum 200, minimum 1) โ€” the actual count depends on how many employee URLs the company-page-plus-Google-search discovery step manages to find, which is not guaranteed to reach the cap.

How do I use LinkedIn Profile โ€” Courses & Recommendations Signal to monitor signals over time?

Rerun the same profileUrls or companies list on a recurring Apify Schedule, then compare each new run's course_signal.themes, course_signal.total_learning_minutes, and recommendation_signal.themes/.count for the same public_identifier against the previous run, and alert on any new theme or a jump in either count โ€” the run-level signal_summary gives you the same comparison rolled up across the whole batch.

Does LinkedIn Profile โ€” Courses & Recommendations Signal work with Claude, ChatGPT, and AI agent frameworks?

Yes, two ways. It's callable as a standard HTTP endpoint through the Apify API from any agent framework that can make a request, and it's reachable through Apify's MCP Server (https://mcp.apify.com, or a local npx @apify/actors-mcp-server instance) for MCP-compatible clients like Claude and Cursor, so an agent can call it as a tool and ground its answer in the returned JSON.

How does LinkedIn Profile โ€” Courses & Recommendations Signal compare to other LinkedIn scrapers?

As observed on the Apify Store on 2026-07-26, alizarin_refrigerator-owner's LinkedIn Jobs Scraper targets job postings for hiring-intent signals rather than individual profiles, and requires bringing your own Firecrawl API key or exported session cookies to bypass LinkedIn's anti-bot wall. parseforge's LinkedIn Learning Catalog Scraper returns LinkedIn Learning course-catalog records (id, scrapedAt, sourceUrl, title) with no profile attached. Neither returns a profile record with course and recommendation data tied to that specific person and rolled into a keyword-theme signal, which is what this Actor does, and this Actor needs no bring-your-own scraping API key โ€” only Apify Proxy, which the proxyConfiguration input resolves automatically.

Why are is_creator, is_influencer, and is_premium always null?

Because they aren't actually present in the public profile HTML this Actor scrapes โ€” they're emitted as null rather than guessed or defaulted, so a downstream consumer never mistakes an unknown value for a real "false." show_follower_count is null for the same reason.

Can I use LinkedIn Profile โ€” Courses & Recommendations Signal without managing proxies or LinkedIn credentials?

Yes. No LinkedIn login, password, or session cookie is ever required as input โ€” the Actor only reads public profile pages. Proxy handling is done through the proxyConfiguration input, which defaults to Apify Proxy with the RESIDENTIAL group; you don't need to source, rotate, or configure your own proxy IPs.

๐Ÿ’ฌ Your feedback

Found a bug, or a field this Actor should be extracting but isn't? Let us know โ€” open an issue on this Actor's Issues tab in Apify Console with a description and, where possible, the profile or company URL that triggered it. Reports like this directly shape what gets fixed and added next.