Duolingo Scraper - Profiles, Leaderboards, Courses & Vocabulary
Pricing
from $1.80 / 1,000 learner records
Duolingo Scraper - Profiles, Leaderboards, Courses & Vocabulary
Scrape public Duolingo data without login. Extract learner profiles with streaks, XP and achievements, weekly league standings, course catalogs with learner counts, and vocabulary words from lessons in clean structured data.
Pricing
from $1.80 / 1,000 learner records
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
1
Monthly active users
7 days ago
Last modified
Categories
Share
Duolingo Scraper
Scrape Duolingo: learner profiles by username (per-course XP and crown counts, streaks, achievements, motivation, roles and more), weekly league standings, and the full course catalog with learner counts. Every surface this actor reads works with no account at all.
Why This Scraper?
- Learner profiles by username: streak, total XP, per-course XP and crowns, join date, Super subscription flag, plus account attributes like motivation, roles and verification status. (Achievement badges are included when the source exposes them; the anonymous profile read often returns none.)
- Weekly league standings by username, with no account needed: current tier, highest tier reached, wins, first, second and top-three finishes, weeks at the current tier, and the date of the last win.
- Course catalog: all 300+ Duolingo courses with title, public course link, learning and source language, learner count, release phase and progress; filter by keyword, language pair or learner floor, sorted by learners or name. Optional Course Details mode also reads each returned course's page for its description line.
- Four modes: search by username, paste profile page links, browse the course catalog, or read a course's vocabulary.
- Incremental mode for scheduled monitoring: only NEW, UPDATED and REAPPEARED records on later runs, with EXPIRED detection on complete scans.
- Resume a large pull from a previous run or dataset ID without paying for duplicate rows.
Data You Get
Sample shape: values are illustrative placeholders, not from a live profile.
| Field | Example value |
|---|---|
| kind | profile |
| username | sample_learner |
| name | Sample Learner |
| userId | 100000001 |
| profileUrl | https://www.duolingo.com/profile/sample_learner |
| streak | 12 |
| totalXp | 50000 |
| learningLanguage | es |
| fromLanguage | en |
| hasPlus | true |
| location | US |
| achievementsDetail | [{"name": "challenger", "tier": 3, "count": 825}] |
| coursesCount | 2 |
| courses | [{"title": "Spanish", "xp": 42000, "crowns": 210}] |
| joinedAt | 1700000000 |
| tier (league) | 2 |
| numWins (league) | 7 |
| learningLanguageName (course) | Spanish |
| numLearners (course) | 1234567 |
| changeType | NEW |
How to Use
Read a set of public profiles (no account needed):
{"mode": "search","searchType": "profiles","usernames": ["sample_learner", "another_sample"]}
Read weekly league standings for a set of learners (no account needed):
{"mode": "search","searchType": "leaderboard","usernames": ["sample_learner", "another_sample"]}
Read profiles from pasted links (usernames or numeric IDs):
{"mode": "url","urls": ["https://www.duolingo.com/profile/sample_learner"]}
List the course catalog (Spanish courses only here, most learners first):
{"mode": "courses","courseQuery": "spanish","courseSort": "learners","maxItems": 50}
Find only mature courses with real learners (beta and in-development pairs excluded):
{"mode": "courses","courseMinLearners": 100000,"maxItems": 100}
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| mode | select | search | Search, or paste profile links (URL mode). |
| searchType | select | profiles | Profiles by username, or weekly league by username. Both work with no account. |
| usernames | array | duolingo | Usernames to read (Profiles and League search types). A name nobody matches is reported, not failed. |
| urls | array | one profile link | Profile page links: /profile/ |
| vocabSkill | string | empty | Only lessons whose skill label contains this keyword (Vocabulary mode). |
| courseQuery | string | empty | Course keyword filter (Course-catalog mode; matches language names and codes). |
| courseLearningLanguage | string | empty | Only courses for this learning language code (Course-catalog mode). |
| courseFromLanguage | string | empty | Only courses taught FROM this language code (Course-catalog mode). |
| courseMinLearners | integer | 0 | Only courses with at least this many learners (Course-catalog mode). |
| courseSort | select | learners | Course row order: most learners first, or language name. |
| learningLanguage | select | empty | Only keep profiles learning this language (profiles and URL mode; applied to results). |
| hasPlusOnly | boolean | false | Only keep profiles with a paid Super subscription. |
| minTotalXp | integer | 0 | Only keep profiles with at least this total XP. |
| minStreak | integer | 0 | Only keep profiles with at least this active streak. |
| fetchAchievements | boolean | true | Add each profile's full achievements detail (one extra read per profile). |
| maxItems | integer | 20 | Stop after this many records in total. 0 = no limit. |
| resumeFromRunId | string | empty | Continue one interrupted run from its ID or dataset ID. |
| incrementalMode | boolean | false | Recurring monitoring: later runs return only what changed. |
| stateKey | string | empty | Name a monitoring campaign to keep its incremental state stable. |
| emitUnchanged | boolean | false | Also return (and bill) unchanged rows in incremental mode. |
| emitExpired | boolean | false | Also return (and bill) rows no longer present, after a complete scan. |
| proxyConfiguration | proxy | default pool | Apify Proxy settings; the default works on every plan. |
Output Example
Sample shape: values are illustrative placeholders, not from a live profile.
{"kind": "profile","recordId": "100000001","username": "sample_learner","name": "Sample Learner","userId": 100000001,"profileUrl": "https://www.duolingo.com/profile/sample_learner","bio": "Sample bio text.","joinedAt": 1700000000,"streak": 12,"streakData": {"currentStreak": {"length": 412, "startDate": "2025-07-28", "endDate": "2026-09-19"}, "longestStreak": null},"totalXp": 50000,"learningLanguage": "es","fromLanguage": "en","currentCourseId": "DUOLINGO_ES_EN","hasPlus": true,"profileCountry": null,"location": "Sample City","courses": [{"id": "DUOLINGO_ES_EN", "title": "Spanish", "learningLanguage": "es", "fromLanguage": "en", "xp": 42000, "crowns": 210}],"coursesCount": 1,"achievements": [],"achievementsCount": 0,"achievementsDetail": [{"name": "sample_badge", "tier": 1, "count": 5}],"changeType": "NEW"}
League runs emit one kind: "league_summary" row per requested username, carrying that learner's league standing: tier, highestLeague, numWins, numberOneFinishes, numberTwoFinishes, topThreeFinishes, streakInTier and lastWinContestEnd. Course-catalog runs emit kind: "course" rows: composed title ("Spanish (from English)"), the public courseUrl link, language pair (codes and names), numLearners, release maturity (releaseStatus: released / beta / in development, mapped from the source's own phase), and fromLanguageCount: how many source languages teach that course. The legacy per-course page carries no richer body, and its meta description is a site-wide template line that is not even served consistently, so no page-description fetch exists by design.
Viewer session: self-established, no login
Profiles, /u/
Scope
Profiles (with achievements detail), the per-user weekly league standing, profile links of the form /profile/
Keep usage respectful of the source's terms.
Send results into your apps (MCP connectors)
Optionally pipe results into the apps you already use through Model Context Protocol (MCP) connectors. Authorize a connector under Apify, Settings, API & Integrations, then select it in the input:
mcpConnectors: pick one or more connectors (Notion, Linear, Airtable, Apify).notionParentPageUrl: for the Notion connector, the parent page under which one page per record is created.maxNotifyListings: cap on records written per connector per run (default 50).
The connector receives a condensed, human-readable summary per record: a title plus the key fields flattened to plain text. It does not mirror the full dataset record; the complete data is always in the Apify dataset. Leaving every connector empty skips the export entirely and never changes the dataset output.