# Class Central Courses Scraper (`scrapyx/classcentral-courses-scraper`) Actor

Searches Class Central, the largest cross-platform MOOC aggregator, and returns course records with rating, provider, level and price across Coursera, edX, Udemy, YouTube and hundreds of other providers in one search — no per-platform scraping needed.

- **URL**: https://apify.com/scrapyx/classcentral-courses-scraper.md
- **Developed by:** [Ibnu Adzim](https://apify.com/scrapyx) (community)
- **Categories:** AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.84 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Class Central Courses Scraper

Searches **[Class Central](https://www.classcentral.com)** — the largest
cross-platform MOOC discovery/aggregator site — and returns real course
records: rating, provider, subject, level, certificate and free/paid
status, spanning Coursera, edX, Udemy, YouTube, university OpenCourseWare
and hundreds of other providers in one search.

Public data only. No login, no cookies, no browser, **no bot challenge of
any kind** — 7/7 TLS profiles tested return 200, cold, on both the search
and course-detail surfaces.

### How this differs from `edx-courses-scraper`

`edx-courses-scraper` covers edX's own catalogue only. Class Central is a
cross-platform aggregator: one search returns matches from dozens of
providers at once, plus Class Central's own community rating layer that
individual provider sites don't have.

### What you get

Two record types share one dataset, told apart by `recordType`.

#### `COURSE` — one row per course

Raw passthrough of Class Central's own per-course analytics record:
`course_id`, `course_name`, `course_slug`, `course_provider`,
`course_institution`, `course_subject`, `course_level`,
`course_certificate`, `course_language`, `course_is_university`,
`course_is_classroom`, `course_is_free`, `course_avg_rating`,
`course_num_rating`. Flattened `course*` convenience fields are added for
the table view. With **Fetch full course details** on, four more fields
are added that the search result does not carry at all: `detailDescription`
(full text), `detailReviews` (dated reviews with body text),
`detailOffers` (exact price/currency/seller), `detailAggregateRating`.

#### `SEARCH_SUMMARY` — one row per search term

The site's own reported total, how many rows this run pulled, and how many
pages it fetched — plus `totalMayBeCapped`, explained below.

### Input

| Field | What it does |
| --- | --- |
| **Search terms** | one search per entry — e.g. `machine learning`, `general relativity` |
| **Max courses per search term** | `0` = unlimited, up to `maxPagesPerQuery` |
| **Max pages per search term** | 15 courses/page; caps how deep an unlimited crawl goes |
| **Fetch full course details** | one extra request per course for description/reviews/price (off by default) |

#### Example

```json
{
  "searchTerms": ["machine learning", "general relativity"],
  "maxItemsPerTerm": 30,
  "includeCourseDetails": true
}
```

### A known ceiling in the reported total — `totalMayBeCapped`

Class Central's own "Load the next 15 courses of N" text reports **exactly
19,985** for every broad subject tested — "machine learning", "data
science", "programming", "business", "engineering", "art", "health" all
land on the identical figure, while genuinely narrower queries ("python"
17,536, "history" 15,243, "quantum computing" 3,846) each report a
different, plausible number. Seven unrelated subjects landing on one exact
number rules out coincidence: this is a display ceiling on the *reported*
total, not necessarily the true match count.

**The actual course data is not affected** — pagination was verified
genuinely deep and honest even for a capped query (checked to page 1000 on
"machine learning": zero id overlap between any two pages tested, all the
way out). Only the headline number in `SEARCH_SUMMARY.totalMatches` can be
an undercount; the courses this actor returns are real and correctly
matched either way. `totalMayBeCapped: true` fires whenever `totalMatches`
equals exactly 19,985, so you know when to treat that number as "at least
this many" rather than "exactly this many."

### Notes on reliability

- **No WAF.** `chrome124`, `chrome131`, `chrome120`, `safari17_0`,
  `firefox133`, `chrome99_android` and `edge101` all return 200, cold.
- **`robots.txt`** has no ClaudeBot/anthropic-ai/Claude-User/
  Claude-SearchBot/CCBot group at all (only GPTBot is blocked, and this
  actor does not identify as GPTBot) — policy-open. `/search` is not
  disallowed; `/api/*` and `/reviews/*` are, and this actor never touches
  either — see CRAWLING\_METHOD.md.
- **Search is fuzzy relevance, not exact-phrase matching.** A
  narrow-looking multi-word query can still match thousands of courses on
  individual words — documented, not a bug (same as this portfolio's
  `app-store-keyword-rank-tracker` and `efinancialcareers-jobs-scraper`). A
  genuinely nonsense query does return a real, verified zero.
- **Residential proxy, no country pin** is the cloud default.

### Known limits

- **The 19,985 total ceiling** above — see the dedicated section.
- **Course detail URLs are only ever built from search results**, never
  accepted as raw user input. The detail page's slug segment is cosmetic
  (only the trailing numeric id is authoritative) — a hand-typed wrong
  slug with a real id silently resolves to a *different, unrelated*
  course. This actor cannot hit that trap because every `(slug, id)` pair
  it uses comes straight from the site's own search result, never from a
  user-guessed string.

# Actor input Schema

## `searchTerms` (type: `array`):

One search per entry, each with its own SEARCH\_SUMMARY row — e.g. `machine learning`, `data science`, `general relativity`. This is full-text relevance search, not exact-phrase filtering (a narrow-looking multi-word query can still match thousands of courses on individual words) — a nonsense query does return a genuine, verified zero.

## `maxItemsPerTerm` (type: `integer`):

Stop after this many courses per search term. Set to 0 for unlimited (walks every page 'maxPagesPerQuery' allows).

## `maxPagesPerQuery` (type: `integer`):

15 courses per page. Caps how deep the crawl walks even when 'maxItemsPerTerm' is set to unlimited.

## `includeCourseDetails` (type: `boolean`):

One extra request per course: full description text, dated reviews with body text, and the exact price/currency/seller offer — none of which are in the search result itself. Off by default because the search card already carries rating, provider, level, subject, certificate and free/paid — enough for most 'find me a course' use cases without doubling the request count.

## `maxConcurrency` (type: `integer`):

Upper bound on requests in flight at once, across all search terms and detail fetches.

## `proxyConfiguration` (type: `object`):

No bot-mitigation gate was observed anywhere in recon (7 TLS profiles clean, cold, on both the search and course-detail surfaces). Residential is still the cloud default as this portfolio's standard baseline.

## Actor input object example

```json
{
  "searchTerms": [
    "machine learning",
    "data science"
  ],
  "maxItemsPerTerm": 15,
  "maxPagesPerQuery": 20,
  "includeCourseDetails": false,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `items` (type: `string`):

One row per scraped record. See the dataset's default view for field definitions.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "searchTerms": [
        "machine learning",
        "data science"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapyx/classcentral-courses-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "searchTerms": [
        "machine learning",
        "data science",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("scrapyx/classcentral-courses-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchTerms": [
    "machine learning",
    "data science"
  ]
}' |
apify call scrapyx/classcentral-courses-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapyx/classcentral-courses-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/BUAhpCINxR44TTy3a/builds/5SJjyzsstLoNM5lxZ/openapi.json
