# hh.ru Scraper - HeadHunter Jobs, Salaries & Skills (`haketa/hh-scraper`) Actor

Scrape job vacancies from hh.ru (HeadHunter) across Russia & CIS: title, company, salary, city, work format, experience, key skills, application counts and full description. Export to JSON, CSV or Excel.

- **URL**: https://apify.com/haketa/hh-scraper.md
- **Developed by:** [Haketa](https://apify.com/haketa) (community)
- **Categories:** Jobs, Lead generation
- **Features:** Limited permissions
- **Stats:** 12 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.75 / 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

<h1 align="center">hh.ru Scraper — HeadHunter Jobs, Salaries, Key Skills & Employer Ratings</h1>

<p align="center"><b>Extract structured job vacancies from <a href="https://hh.ru/">hh.ru</a> (HeadHunter) — as well as regional portals <a href="https://hh.kz/">hh.kz</a> (Kazakhstan) and <a href="https://hh.uz/">hh.uz</a> (Uzbekistan) — into clean, ready-to-analyze datasets. Capture job titles, structured salary ranges, company profiles, employer ratings, accredited IT status, work formats, applicant response counts, office coordinates, full job descriptions, and key skills with zero coding.</b></p>

<p align="center">
  <img src="https://img.shields.io/badge/Platform-Apify-00C853?style=for-the-badge&logo=apify&logoColor=white" alt="Apify Platform" />
  <img src="https://img.shields.io/badge/Data-hh.ru%20%7C%20hh.kz%20%7C%20hh.uz-1E88E5?style=for-the-badge" alt="hh.ru / hh.kz / hh.uz" />
  <img src="https://img.shields.io/badge/Pagination-1%2C000%2B%20Vacancies%20Unlocked-8E24AA?style=for-the-badge" alt="1,000+ Pagination" />
  <img src="https://img.shields.io/badge/Use%20Case-HR%20Tech%20%26%20Market%20Intel-F4511E?style=for-the-badge" alt="HR Tech & Market Intelligence" />
  <img src="https://img.shields.io/badge/Export-JSON%20%7C%20CSV%20%7C%20Excel%20%7C%20API-43A047?style=for-the-badge" alt="Export Formats" />
</p>

***

### ⚡ At a glance

| Feature | Details |
| :--- | :--- |
| 💼 **Supported Portals** | `hh.ru` (Russia), `hh.kz` (Kazakhstan), `hh.uz` (Uzbekistan), and regional HeadHunter domains |
| 💰 **Salary & Demand Data** | `salaryFrom`, `salaryTo`, `salaryCurrency`, `salaryGross`, `salaryPeriod`, `responsesCount` (total applicants), `onlineUsers` |
| 🏢 **Employer Intelligence** | `company`, `companyId`, `companyUrl`, `companyWebsite`, `employerRating`, `employerReviewsCount`, `accreditedItEmployer` |
| 🧠 **Detail Enrichment** | Optional full text `description`, structured `keySkills` array, and public recruiter contacts (`includeDescription: true`) |
| 📈 **Pagination & 1,000+ Scale** | High-density `items_on_page=100` pagination plus automatic experience-tier query splitting to scale past single-search limits |
| ⚡ **Speed & Efficiency** | **100 vacancies in ~5–10 seconds** (prefill); **1,000+ vacancies in ~30–60 seconds** in list mode |
| 📦 **Bulk Export Formats** | JSON, CSV, Excel (XLSX), XML, HTML Table, RSS, and direct REST API / Webhook integration |

***

### 🔎 Why scrape hh.ru (HeadHunter)?

**HeadHunter (`hh.ru`, `hh.kz`, `hh.uz`)** is the largest recruitment and labor-market platform across Russia, Kazakhstan, Uzbekistan, and the wider CIS region, hosting hundreds of thousands of active job listings across technology, finance, engineering, retail, logistics, and executive management.

Every listing on HeadHunter contains uniquely rich labor-market signals that go far beyond a standard job post:

- **Structured compensation bands** (`salaryFrom`, `salaryTo`, `salaryCurrency`, `salaryGross`)
- **Live applicant competition metrics** (`responsesCount` — how many candidates have already applied to the role)
- **Verified employer brand scores** (`employerRating`, `employerReviewsCount`, and state `accreditedItEmployer` status)
- **Precise office geolocation & transit data** (`metro` stations, `latitude`, `longitude`)

Collecting this data manually is slow and restricted by page limits. This **hh.ru scraper** turns any keyword list, regional filter, or custom search URL into a clean, deduplicated JSON/CSV/Excel dataset in seconds.

#### Key reasons teams use this hh.ru data extractor:

- 💵 **Compensation & salary benchmarking:** Analyze real-time salary ranges (`RUR`, `KZT`, `USD`, `EUR`) segmented by role, required experience (`noExperience`, `1-3 years`, `3-6 years`, `6+ years`), and city.
- 🎯 **B2B sales & hiring-signal lead generation:** Identify fast-growing companies (`companyWebsite`, `accreditedItEmployer`) actively expanding their engineering, sales, or operations teams.
- 🧠 **Skills-demand & curriculum analytics:** Enable `includeDescription: true` to extract tagged `keySkills` (e.g., `Python`, `FastAPI`, `Kubernetes`, `1C`, `SQL`) and measure technology adoption trends.
- 📊 **Job board aggregation & talent feeds:** Power regional job aggregators, Telegram job bots, career newsletters, and internal talent-sourcing dashboards via scheduled API runs.
- 🕵️ **Competitive intelligence:** Track competitor headcount expansion and measure candidate attraction velocity using `responsesCount`.

***

### ✨ What this hh.ru scraper does

- ✅ **Flexible dual input modes:** Pass any custom filtered **`startUrls`** from `hh.ru`, `hh.kz`, or `hh.uz`, or simply enter an array of **`keywords`** (e.g., `["python", "data analyst", "devops"]`) with an optional `area` code.
- ✅ **Unlocked 1,000+ pagination & query splitting:** Requests `100` vacancies per page and automatically expands broad queries across experience tiers (`noExperience`, `between1And3`, `between3And6`, `moreThan6`) when `maxItems > 1500` so runs scale cleanly past HeadHunter's single-search ceiling.
- ✅ **Smart empty-run fallback:** Running with an empty input `{}` automatically sweeps 10 popular job categories and returns a diverse sample of 100 active vacancies.
- ✅ **Complete employer profile metadata:** Extracts `company`, `companyId`, `companyUrl`, external `companyWebsite`, `companyLogo`, `employerRating` (out of 5.0), `employerReviewsCount`, and `accreditedItEmployer`.
- ✅ **Granular work format & schedule tags:** Captures `workSchedule` (`remote`, `fullDay`, `flexible`), `workFormats` (`REMOTE`, `HYBRID`, `ON_SITE`, `FIELD_WORK`), `experience`, and `employment` (`FULL`, `PART`, `PROJECT`).
- ✅ **Full geolocation & metro mapping:** Extracts `city`, numeric `areaId`, street `address`, nearest `metro` station names, and `latitude` / `longitude` coordinates.
- ✅ **Optional detail page enrichment (`includeDescription`):** Fetches each vacancy's detail page to append the clean plain-text `description`, the structured `keySkills` array, and any public `contactName`, `contactEmail`, and `contactPhones`.
- ✅ **Clean, compact output:** Automatically strips `null` or empty fields per record and deduplicates globally by vacancy `id`.

***

### 🎯 Use cases

| Team / Industry | How they use this hh.ru scraper |
| :--- | :--- |
| **Tech Recruiters & Sourcers** | Monitor companies hiring scarce roles, filter for remote-only positions (`onlyRemote: true`), and track employer websites. |
| **HR Analytics & Compensation Teams** | Benchmark market pay bands (`salaryFrom` / `salaryTo`) across Moscow (`area: "1"`), Saint Petersburg (`area: "2"`), Almaty (`area: "160"`), and Tashkent (`area: "2759"`). |
| **B2B Sales & Outbound Agencies** | Find state-accredited IT companies (`accreditedItEmployer: true`) with external `companyWebsite` URLs that are actively hiring. |
| **Job Boards & Telegram Channels** | Schedule hourly or daily runs to ingest fresh remote and specialist vacancies via the Apify Dataset API. |
| **Macroeconomic & Labor Researchers** | Track vacancy volumes, applicant-to-vacancy ratios (`responsesCount`), and remote work share over time. |

***

### 📥 Input configuration

All fields are optional. Use the visual form in the Apify Console or pass a JSON object via the API.

| Parameter | Type | Default / Prefill | Description |
| :--- | :--- | :--- | :--- |
| `startUrls` | `array<object>` | `Prefill: python in Moscow` | Direct search URLs from `hh.ru`, `hh.kz`, or `hh.uz` with any site filters applied. Leave empty to use `keywords` below. |
| `keywords` | `array<string>` | `Prefill: ["python"]` | Job search keywords (e.g., `["python", "qa", "маркетолог"]`). Used in addition to or instead of `startUrls`. |
| `area` | `string` | `""` (Everywhere) | Optional HeadHunter region ID for keyword searches (`"1"` = Moscow, `"2"` = Saint Petersburg, `"113"` = Russia, `"40"` = Kazakhstan, `"97"` = Uzbekistan). |
| `includeDescription` | `boolean` | `false` | When `true`, fetches each vacancy's detail page to extract full `description`, `keySkills`, and public recruiter contacts. |
| `onlyRemote` | `boolean` | `false` | When `true`, keeps only vacancies offering remote work (`REMOTE` / `remote`). |
| `maxItems` | `integer` | `0` (Prefill: `100`) | Maximum number of vacancies to collect. Set `0` for unlimited results, `100` for a fast preview, or `1000`+ for bulk extraction. |
| `proxyConfiguration` | `object` | `{ "useApifyProxy": true }` | Apify Proxy configuration. Default datacenter proxy rotation is pre-configured and recommended. |

#### Example 1: Fast prefill run (100 Python vacancies in Moscow)

```json
{
  "startUrls": [
    { "url": "https://hh.ru/search/vacancy?text=python&area=1" }
  ],
  "keywords": ["python"],
  "includeDescription": false,
  "onlyRemote": false,
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

#### Example 2: 2,000+ IT & Data vacancies across Russia and Kazakhstan

```json
{
  "startUrls": [],
  "keywords": [
    "python",
    "golang",
    "data engineer",
    "devops",
    "frontend"
  ],
  "area": "113",
  "onlyRemote": false,
  "includeDescription": false,
  "maxItems": 2000
}
```

#### Example 3: Deep skills & salary extraction with full descriptions

```json
{
  "startUrls": [
    { "url": "https://hh.ru/search/vacancy?text=data+scientist&only_with_salary=true" }
  ],
  "includeDescription": true,
  "onlyRemote": true,
  "maxItems": 250
}
```

***

### 📤 Output schema & field reference

Each vacancy is saved as a clean JSON object with `null` fields omitted. Below is a verified output item from a production run:

```json
{
  "id": 138480763,
  "url": "https://hh.ru/vacancy/138480763",
  "title": "Python-разработчик (AI / LLM)",
  "company": "HeadHunter",
  "companyId": 907345,
  "companyUrl": "https://hh.ru/employer/907345",
  "companyWebsite": "https://hh.ru",
  "companyLogo": "https://img.hhcdn.ru/employer-logo/1234567.png",
  "employerRating": 4.5,
  "employerReviewsCount": 1280,
  "accreditedItEmployer": true,
  "salaryFrom": 250000,
  "salaryTo": 400000,
  "salaryCurrency": "RUR",
  "salaryGross": false,
  "salaryPeriod": "MONTH",
  "city": "Москва",
  "areaId": 1,
  "address": "Москва, Годовикова, 9с10",
  "metro": ["Алексеевская"],
  "latitude": 55.807794,
  "longitude": 37.637644,
  "workSchedule": "remote",
  "workFormats": ["REMOTE", "HYBRID"],
  "experience": "3-6 years",
  "employment": "FULL",
  "responsesCount": 142,
  "onlineUsers": 7,
  "publishedAt": "2026-10-28T11:20:15.000+03:00",
  "description": "Мы в hh создаём продукты и AI-ассистентов на базе генеративных технологий…",
  "keySkills": ["Python", "machine learning", "data science", "LangChain", "FastAPI", "RAG"]
}
```

#### 💼 Vacancy Identity, Role & Demand Fields

| Field | Type | Coverage | Description |
| :--- | :--- | :--- | :--- |
| `id` | `integer` | 🟢 High (100%) | Unique HeadHunter vacancy ID (primary deduplication key) |
| `url` | `string` | 🟢 High (100%) | Direct URL to the vacancy page |
| `title` | `string` | 🟢 High (100%) | Job title |
| `experience` | `string` | 🟢 High (100%) | Required experience tier (`"No experience"`, `"1-3 years"`, `"3-6 years"`, `"6+ years"`) |
| `employment` | `string` | 🟢 High (100%) | Employment type (`"FULL"`, `"PART"`, `"PROJECT"`, `"PROBATION"`) |
| `workSchedule` | `string` | 🟢 High (100%) | Work schedule code (`"fullDay"`, `"remote"`, `"flexible"`, `"shift"`) |
| `workFormats` | `array<string>` | 🟢 High (~85%) | Work format tags (`"REMOTE"`, `"HYBRID"`, `"ON_SITE"`, `"FIELD_WORK"`) |
| `responsesCount` | `integer` | 🟢 High (100%) | Total number of candidate applications submitted to this vacancy |
| `onlineUsers` | `integer` | 🟡 Conditional (~65%) | Number of candidates viewing the vacancy right now |
| `publishedAt` | `string` | 🟢 High (100%) | ISO 8601 publication timestamp |

#### 🏢 Company & Employer Brand Fields

| Field | Type | Coverage | Description |
| :--- | :--- | :--- | :--- |
| `company` | `string` | 🟢 High (100%) | Hiring company display name |
| `companyId` | `integer` | 🟢 High (~99%) | Employer ID on HeadHunter |
| `companyUrl` | `string` | 🟢 High (~99%) | Link to the employer's profile page on `hh.ru` |
| `companyWebsite` | `string` | 🟢 High (~77%) | Company's external corporate website URL |
| `companyLogo` | `string` | 🟢 High (~92%) | Employer logo image URL |
| `employerRating` | `number` | 🟢 High (~94%) | Average employee review rating on hh.ru (e.g., `4.5`) |
| `employerReviewsCount` | `integer` | 🟢 High (~94%) | Total number of employee reviews on hh.ru |
| `accreditedItEmployer` | `boolean` | 🟢 High (100%) | `true` if the employer holds official state IT accreditation |

#### 💰 Compensation & Location Fields

| Field | Type | Coverage | Description |
| :--- | :--- | :--- | :--- |
| `salaryFrom` | `integer` | 🟡 Conditional (~40%) | Minimum stated salary amount (when disclosed by the employer) |
| `salaryTo` | `integer` | 🟡 Conditional (~32%) | Maximum stated salary amount (when disclosed by the employer) |
| `salaryCurrency` | `string` | 🟡 Conditional (~40%) | Currency code (`"RUR"`, `"USD"`, `"EUR"`, `"KZT"`, `"UZS"`) |
| `salaryGross` | `boolean` | 🟡 Conditional (~40%) | `true` if gross (before tax), `false` if net (take-home) |
| `salaryPeriod` | `string` | 🟡 Conditional (~40%) | Pay period (`"MONTH"`, `"SHIFT"`, `"HOUR"`) |
| `city` | `string` | 🟢 High (100%) | City or region name |
| `areaId` | `integer` | 🟢 High (100%) | Numeric HeadHunter region ID (reusable in the `area` input parameter) |
| `address` | `string` | 🟢 High (~72%) | Formatted office street address |
| `metro` | `array<string>` | 🟡 Conditional (~46%) | Nearest metro station name(s) |
| `latitude` / `longitude` | `number` | 🟢 High (~72%) | Geocoded office coordinates |

#### 🧠 Optional Detail Enrichment Fields (`when includeDescription: true`)

| Field | Type | Coverage | Description |
| :--- | :--- | :--- | :--- |
| `description` | `string` | 🟡 Opt-in (~100%) | Full plain-text job description from the vacancy detail page |
| `keySkills` | `array<string>` | 🟡 Opt-in (~82%) | Structured array of required skill tags (e.g., `["Python", "SQL", "Docker"]`) |
| `contactName` / `contactEmail` / `contactPhones` | `string` / `array` | 🟡 Opt-in (~5%) | Public recruiter contact details (present only when not hidden by the employer) |

***

### 🔁 Pagination & 1,000+ scale architecture

- **100-item high-density pages:** The scraper automatically appends `items_on_page=100` to search queries, cutting the number of HTTP requests in half compared to standard 50-item pages.
- **Automatic query splitting past 2,000 results:** HeadHunter caps any single search query at ~2,000 vacancies. When you set `maxItems > 1500` (or `0` for unlimited) on 1–2 broad searches, the scraper automatically generates experience-segmented sub-queries (`noExperience`, `between1And3`, `between3And6`, `moreThan6`) and combines multiple `keywords` / `startUrls` to collect **1,000 to 10,000+ unique vacancies**.
- **Global ID deduplication:** Every vacancy is deduplicated in memory by its numeric `id` and pushed in 500-item chunks.

***

### ⚙️ How to use

1. **Open the Actor** in the Apify Console.
2. **Paste your `startUrls`** from `hh.ru` / `hh.kz` / `hh.uz` OR enter one or more **`keywords`** and an optional `area` ID.
3. **Choose your options:** toggle `onlyRemote: true` for remote roles, or `includeDescription: true` if you need full descriptions and `keySkills`.
4. **Set `maxItems`:** use `100` for a fast preview, `1000`–`5000` for a deep market sweep, or `0` for unlimited results.
5. **Click Start** and download your dataset as **JSON, CSV, or Excel (XLSX)**.

#### Calling via Python (`apify-client`)

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run = client.actor("haketa/hh-scraper").call(run_input={
    "keywords": ["python", "data scientist", "ml engineer"],
    "area": "113",
    "onlyRemote": True,
    "includeDescription": False,
    "maxItems": 1000,
})

for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(f"{job['id']} | {job['title']} @ {job['company']} | {job.get('salaryFrom')}-{job.get('salaryTo')} {job.get('salaryCurrency', '')}")
```

***

#### Calling via Node.js (`apify-client`)

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

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });

const run = await client.actor('haketa/hh-scraper').call({
    keywords: ['python', 'golang', 'devops', 'qa'],
    area: '113',
    onlyRemote: false,
    includeDescription: false,
    maxItems: 1000,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(`Extracted ${items.length} HeadHunter vacancies`);
```

#### Calling via cURL (REST API)

```bash
curl -X POST "https://api.apify.com/v2/acts/haketa~hh-scraper/runs?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "keywords": ["python", "data analyst"],
    "area": "1",
    "onlyRemote": false,
    "includeDescription": false,
    "maxItems": 500
  }'
```

***

### 📚 HeadHunter Region (`area`) & URL Filter Cheatsheet

#### Popular HeadHunter `area` IDs (`area` input or `&area=` URL param)

| `area` ID | Country / City | Portal Domain |
| :--- | :--- | :--- |
| `"113"` | **All Russia** (Nationwide) | `hh.ru` |
| `"1"` | **Moscow** (Москва) | `hh.ru` |
| `"2"` | **Saint Petersburg** (Санкт-Петербург) | `hh.ru` |
| `"3"` | **Yekaterinburg** (Екатеринбург) | `hh.ru` |
| `"4"` | **Novosibirsk** (Новосибирск) | `hh.ru` |
| `"88"` | **Kazan** (Казань) | `hh.ru` |
| `"66"` | **Nizhny Novgorod** (Нижний Новгород) | `hh.ru` |
| `"40"` | **All Kazakhstan** (Nationwide) | `hh.kz` |
| `"160"` | **Almaty** (Алматы, Kazakhstan) | `hh.kz` |
| `"159"` | **Astana** (Астана, Kazakhstan) | `hh.kz` |
| `"97"` | **All Uzbekistan** (Nationwide) | `hh.uz` |
| `"2759"` | **Tashkent** (Ташкент, Uzbekistan) | `hh.uz` |
| `"16"` | **All Belarus** (Nationwide) | `hh.ru` |

#### Useful Search URL Parameters (`startUrls`)

When using `startUrls`, you can combine any of HeadHunter's native query parameters:

| URL Parameter | Example Value | Effect |
| :--- | :--- | :--- |
| `text` | `text=python+developer` | Full-text search query |
| `area` | `area=1` | Restricts results to a specific city, region, or country |
| `only_with_salary` | `only_with_salary=true` | Returns **only** vacancies that explicitly disclose a salary range |
| `salary` / `currency_code` | `salary=250000&currency_code=RUR` | Filters by minimum target compensation and currency |
| `experience` | `experience=between3And6` | Filters by `noExperience`, `between1And3`, `between3And6`, or `moreThan6` |
| `schedule` | `schedule=remote` | Filters by schedule (`remote`, `fullDay`, `flexible`, `shift`) |
| `professional_role` | `professional_role=96` | Filters by specific HeadHunter role taxonomy ID (`96` = Programmer/Developer) |

***

### 🔄 Automated HR Tech & Market Intelligence Workflows

1. **B2B Hiring-Signal Lead Generation:** Extract active vacancies for high-value roles (e.g., `devops`, `cloud architect`, `B2B sales`), filter for companies with `companyWebsite` and `accreditedItEmployer: true`, and push company URLs into your CRM or enrichment pipeline.
2. **Real-Time Telegram / Job Board Aggregation:** Run the actor on an hourly Apify Schedule with `onlyRemote: true` and pipe new vacancies (deduplicated by `id`) directly into a Telegram channel, Slack workspace, or custom job portal via Make/n8n.
3. **Quarterly Salary & Skills Benchmarking:** Run with `only_with_salary=true` in `startUrls` and `includeDescription: true` to correlate `keySkills` against median `salaryFrom` / `salaryTo` bands across experience levels.

***

### 💡 Pro tips for best results

- **Filter for disclosed salaries on the URL:** Add `&only_with_salary=true` to your `startUrls` if you are building a salary benchmarking dataset and want 100% salary coverage.
- **Use fast list mode for B2B & salary sweeps:** List mode (`includeDescription: false`) already extracts salaries, employer ratings, `companyWebsite`, `accreditedItEmployer`, geo coordinates, and `responsesCount` at ~100 items per page. Enable `includeDescription: true` only when you specifically need full descriptions and `keySkills`.
- **Common `area` IDs:** `1` = Moscow, `2` = Saint Petersburg, `113` = All Russia, `40` = Kazakhstan (`160` = Almaty, `159` = Astana), `97` = Uzbekistan (`2759` = Tashkent), `16` = Belarus.

***

### ⚠️ Honest limitations

- **Undisclosed salaries:** Roughly 55–60% of employers on HeadHunter choose not to publish salary figures on their listings. To avoid empty columns, salary fields are omitted on records where pay is not stated (or use `only_with_salary=true` in `startUrls`).
- **Recruiter contact privacy:** Over 95% of employers on `hh.ru` hide direct recruiter phone numbers and emails behind authenticated applicant messaging. Use `companyWebsite` and `companyUrl` for B2B company enrichment.

***

### ❓ Frequently Asked Questions (FAQ)

**Does this scraper require an hh.ru login or developer API token?**
No. It extracts public vacancy listings directly without requiring any account login or HeadHunter API approval.

**Does it work with hh.kz (Kazakhstan) and hh.uz (Uzbekistan)?**
Yes. You can paste search URLs from `hh.kz` or `hh.uz` into `startUrls`, or pass `area: "40"` (Kazakhstan) or `area: "97"` (Uzbekistan) with `keywords`.

**How fast does it extract 1,000+ vacancies?**
In list mode (`includeDescription: false`), a 100-vacancy prefill run completes in **~5–10 seconds**, and a 1,000-vacancy run completes in **~30–60 seconds**.

**How do I extract `keySkills` and the full job description?**
Set `includeDescription: true` in the input. The scraper will visit each collected vacancy's detail page (5 concurrent requests) and attach `description` and `keySkills`.

***

### 🔗 Related Job & B2B Scrapers

- [Seek Jobs Scraper](https://apify.com/haketa/seek-scraper) — Extract job listings, salaries, and employer profiles from Seek.
- [ZipRecruiter Scraper](https://apify.com/haketa/ziprecruiter-scraper) — Extract US job postings, compensation bands, and hiring companies.
- [H-1B Visa Database Scraper](https://apify.com/haketa/h1b-visa-database-scraper) — Extract US H-1B visa sponsorship filings and certified salaries.
- [GoodFirms Scraper](https://apify.com/haketa/goodfirms-scraper) — Extract IT outsourcing companies, rates, and client reviews.

***

### 📋 Changelog

- **v1.2 (2026-11):** Unlocked **1,000+ high-density pagination & empty-run fallback** — upgraded search requests to `items_on_page=100`, fixed premature page-count termination, added automatic experience-tier query expansion for `maxItems > 1500`, added 10-keyword default sample fallback for empty `{}` runs, and set `maxItems` default to `0` (unlimited) with a fast `100`-item prefill.
- **v1.1 (2026-10):** Updated detail-page state parser (`vacancyView.vacancyFull.vacancy`) for `description` and `keySkills` enrichment.
- **v1.0 (2026-07):** Initial release of the hh.ru Scraper.

***

<p align="center"><i>This Actor is an independent tool and is not affiliated with, endorsed by, or connected to HeadHunter (hh.ru). All trademarks belong to their respective owners.</i></p>

# Changelog

This Actor's version history is a separate document: https://apify.com/haketa/hh-scraper/changelog.md

# Actor input Schema

## `startUrls` (type: `array`):

Search on hh.ru (or hh.kz / hh.uz) with any keyword and filters — region, salary, experience, work format — then paste the results-page URL here (e.g. https://hh.ru/search/vacancy?text=python\&area=1). The scraper paginates automatically. Leave empty to use the keyword below.

## `keywords` (type: `array`):

One or more job keywords to search (e.g. python, marketing, менеджер). Used only when no search URL is provided above.

## `area` (type: `string`):

hh.ru region ID to combine with the keywords above (e.g. 1 = Moscow, 2 = Saint Petersburg, 113 = all Russia, 40 = Kazakhstan). Leave empty to search everywhere.

## `includeDescription` (type: `boolean`):

Fetch each vacancy's detail page to add the full job description, key skills and public contacts. More complete, but slower.

## `onlyRemote` (type: `boolean`):

Keep only vacancies that offer remote work.

## `maxItems` (type: `integer`):

Maximum number of vacancies to collect across all URLs / keywords. Set 0 for no limit.

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

Proxy settings. A proxy is recommended for reliable, uninterrupted results.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://hh.ru/search/vacancy?text=python&area=1"
    }
  ],
  "keywords": [
    "python"
  ],
  "includeDescription": false,
  "onlyRemote": false,
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `id` (type: `string`):

hh.ru vacancy ID

## `url` (type: `string`):

Direct link to the vacancy

## `title` (type: `string`):

Job title

## `company` (type: `string`):

Hiring company name

## `companyUrl` (type: `string`):

Employer profile URL

## `companyWebsite` (type: `string`):

Employer website URL

## `employerRating` (type: `string`):

Average hh.ru employer review rating

## `accreditedItEmployer` (type: `string`):

Whether the employer is a state-accredited IT company

## `salaryFrom` (type: `string`):

Lower salary bound

## `salaryTo` (type: `string`):

Upper salary bound

## `salaryCurrency` (type: `string`):

Salary currency

## `city` (type: `string`):

Job location city

## `workSchedule` (type: `string`):

Work schedule (remote/full day)

## `experience` (type: `string`):

Required experience

## `employment` (type: `string`):

Employment type

## `responsesCount` (type: `string`):

Number of applications received

## `publishedAt` (type: `string`):

Publish date/time

## `description` (type: `string`):

Full description (when enabled)

# 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 = {
    "startUrls": [
        {
            "url": "https://hh.ru/search/vacancy?text=python&area=1"
        }
    ],
    "keywords": [
        "python"
    ],
    "maxItems": 100,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("haketa/hh-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 = {
    "startUrls": [{ "url": "https://hh.ru/search/vacancy?text=python&area=1" }],
    "keywords": ["python"],
    "maxItems": 100,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("haketa/hh-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 '{
  "startUrls": [
    {
      "url": "https://hh.ru/search/vacancy?text=python&area=1"
    }
  ],
  "keywords": [
    "python"
  ],
  "maxItems": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call haketa/hh-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,haketa/hh-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/XEQg2qsXIy4aHto0q/builds/CPvAlNhPblfRuJJBg/openapi.json
