HH.ru Scraper at a glance
HH.ru Scraper is a ready-to-run Apify actor from abotapi for collecting jobs data. Its documented output includes id, change type (incremental mode), url, title and published, which you can export as JSON, CSV or Excel or read through the Apify API. It costs $2.00 per 1,000 results on the Apify Free plan and from $1.00 on paid plans, with smaller per-run or optional feature charges listed on Apify.
- Runs on
- Apify cloud: console, API, schedules and integrations
- Data type
- Jobs, Automation, Developer Tools
- Region
- Europe
- Pricing
- $2.00 per 1,000 results (Free plan), from $1.00 on paid plans
- Output
- Documented below · JSON, CSV, Excel
- Inputs
- 25 options
- Run success
- 100.0% of completed public runs in the last 30 days
Scrape HH.ru (HeadHunter) job listings by filters or URLs. Extract 50+ fields including salaries, experience, schedules, employment types and job roles. Optional enrichment adds full descriptions, required skills, contact details and employer logos.
What HH.ru Scraper can do
- Three ways to find data. Build a search from keywords, area and filters, paste hh.ru search URLs as-is, or hand in specific vacancy links or IDs and get one full detail card per entry.
- 50+ fields per vacancy. Salary ranges, GPS coordinates, metro stations, employer logos and badges, full HTML and plain-text descriptions, and key skills, plus employer details.
- Detail on demand. Turn full detail-page enrichment on or off; results still stream into the dataset as each one finishes, so a mid-run stop keeps everything collected so far.
- Full filter coverage. Search field (title-only or everywhere), experience, employment, schedule, salary range, only-with-salary, professional role, industry and sort order.
- Built for schedules. Incremental mode returns only new and changed vacancies on recurring runs, and a run that can't reach hh.ru fails loudly instead of returning an empty dataset.
- Clear about hh.ru's own 2000-result cap. Slice one broad search into narrower ones by area, salary band or role to capture everything beyond it.
Choose what to collect
These inputs let you configure HH.ru Scraper - Jobs, Salaries, Skills & Employers. Open the actor on Apify to enter your values and review the current options.
| Input | What it controls |
|---|---|
modestring | Search mode = type your keywords + area + filters and let the scraper build URLs. URL mode = paste any hh.ru search URL already refined in a browser (recommended for complex filters or saved searches). Vacancy mode = paste a list of specific vacancy URLs or IDs and get the full detail card for each (no search performed). |
queriesarray | One or more keywords. Each keyword runs as a separate search. Examples: 'python', 'AI engineer', 'data scientist', 'Java middle'. Russian keywords are fine. |
searchFieldstring | Controls WHERE hh.ru looks for your keyword. Vacancy title only (default) returns the roles whose TITLE matches your keyword, the relevant results you expect when you search by job title. Everywhere is hh.ru's own default: it matches the keyword anywhere in the vacancy (title, description, or company) and mixes in loosely-related roles, so a search for 'Продуктовый аналитик' also returns marketing directors and sales managers, switch to it only if you deliberately want that broad reach. 'Company name' and 'Description' restrict the match to those fields. |
areasarray | Optional. One or more hh.ru area IDs. Leave empty to search all of Russia (defaults to 113 = whole country). Common values: 1=Moscow, 2=Saint Petersburg, 113=Russia (whole country), 4=Novosibirsk, 88=Kazan, 1438=Yekaterinburg, 5=Krasnodar, 76=Rostov-on-Don, 78=Voronezh, 66=Nizhny Novgorod. Full list: https://api.hh.ru/areas |
experiencestring | Filter candidates by required experience. |
employmentarray | Multi-select. Leave empty to get all types. |
schedulearray | Multi-select. Leave empty for all schedules. |
Show 18 more inputs
| Input | What it controls |
|---|---|
salaryMininteger | Minimum monthly salary in RUB. Listings without a salary are still returned unless you also enable 'Only with salary'. |
onlyWithSalaryboolean | If on, only listings that publish a salary range are returned. |
orderBystring | How to order results. Default = relevance (hh.ru's own default). |
professionalRolearray | Optional. Numeric professional role IDs to filter by. Examples: 96=Programmer/Developer, 156=Software engineer, 165=Data Analyst, 10=Project Manager. Full taxonomy: https://api.hh.ru/professional_roles |
industryarray | Optional. Numeric industry IDs. Examples: 7=IT/Telecom, 9=Marketing/PR, 29=Finance. Full taxonomy: https://api.hh.ru/industries |
urlsarray | One or more full hh.ru search URLs. Tip: open hh.ru in your browser, pick keywords + area + filters, then paste the resulting URL here. Pagination starts at page in URL and walks forward. |
vacancyInputarray | One entry per vacancy you want a full detail card for. Accepts full URLs (https://hh.ru/vacancy/123456789), mobile URLs (https://m.hh.ru/vacancy/123456789), or bare numeric IDs (123456789). Each entry fetches that vacancy's page directly, no search runs, so hh.ru's 2000-result cap does not apply. Duplicate IDs are removed. Removed or archived vacancies are omitted from the dataset (never charged); each skipped ID is named in the run log and summarised in the run status message. |
maxPagesinteger | How many result pages to walk per query / URL (Search and URL modes only; ignored in Vacancy mode). hh.ru returns 50 listings per page, so 2 pages ≈ 100 listings. Maximum is 40 pages, hh.ru's own hard cap is 2000 results per filter, so any single search beyond ~2000 matches must be split into narrower slices (by area, salary band, or role) to capture the rest. The run logs a warning when a search exceeds what's reachable. |
maxListingsinteger | Hard cap across the whole run. 0 = unlimited (bounded only by Max pages). |
fetchDetailsboolean | Adds the most valuable fields: full HTML description + plain-text version, keySkills array, employer logos, validThroughTime, hr-brand status, work format details, and metro station list. Turn off for a faster run with just the SERP-level fields. Note on contact info: hh.ru hides phone / email / advertiser name behind a login wall, so contactInfo.phones / .email / .name will be empty for non-authenticated scrapes. The actor returns contactInfo.contactsHidden=true for those listings. Some employers paste their contact details into the descriptionText (search the description if you need it). Each delivered vacancy whose detail page is read is charged as one detail-page event on top of the per-result charge; vacancies whose detail page is refused, and vacancies incremental mode suppresses as unchanged, are not charged. |
resumeFromRunIdstring | Paste a previous run ID or dataset ID to continue a large pull without returning or charging for vacancies already collected there. Use this after an interrupted run, or to keep extending one walk-all pull across separate runs. For recurring daily monitoring of the same search, use Incremental mode below instead. |
incrementalModeboolean | Turn this on for daily or recurring monitoring. The first run returns all matching vacancies as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED vacancies. Turn on "Emit unchanged" or "Emit expired" only when you also want those vacancies returned (and billed). State is kept separately per mode + search/URL/vacancy-list configuration; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above. |
stateKeystring | Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the mode + search/URL/vacancy settings, different searches then never mix state with each other. |
emitUnchangedboolean | Off by default. Turn on to also return vacancies that have not changed since the last run, marked UNCHANGED. This returns, and bills, extra rows you already have, so leave it off unless you specifically want the full snapshot every run. |
emitExpiredboolean | Off by default. Turn on to also return vacancies that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search, not when Max listings capped it or when Resume was used. This returns, and bills, extra synthetic rows, so leave it off unless you need expiry tracking. |
mcpConnectorsarray | Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON, the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com). |
notionParentPageUrlstring | URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors. |
maxNotifyListingsinteger | Cap on items written to each connector per run. Does not affect the dataset. |
Fields in the output
The documented dataset includes the fields below. Availability can depend on the source page and selected mode.
vacancyIdIDchangeTypeChange type (incremental mode)urlURL (link)nameTitlepublicationDatePublishedcompany.nameCompanycompany.isAccreditedITIT-accreditedsalaryFromSalary fromsalaryToSalary tosalaryCurrencyCurrencyarea.nameCityaddress.metroStationsMetroShow 13 more fields
address.latLataddress.lngLngworkExperienceExperienceemploymentFormEmploymentworkFormatsFormatworkScheduleSchedulekeySkillsKey skillsresponsesCountResponsestotalResponsesCountTotal responseschangedFieldsChanged fields (incremental mode)firstSeenAtFirst seen (incremental mode)lastSeenAtLast seen (incremental mode)detailFetcheddetailFetchedExample output
An example from this actor’s documentation. Values are illustrative; this is not a fresh live result.
{
"vacancyId": "100000001",
"url": "https://hh.ru/vacancy/100000001",
"name": "Senior Python-разработчик",
"publicationDate": "2026-05-05T09:00:00.000+03:00",
"creationDate": "2026-05-01T09:00:00.000+03:00",
"lastChangeTime": "2026-05-04T09:00:00.000+03:00",
"validThroughTime": "2026-06-04T09:00:00.000+03:00",
"isAdv": false,
"searchUrl": "https://hh.ru/search/vacancy?text=python&area=1&page=0",
"searchSessionId": "00000000-0000-0000-0000-000000000000",
"company": {
"id": 1000000,
"name": "ООО «Пример»",
"isAccreditedIT": true,
"isTrusted": true,
"siteUrl": "https://www.example.com/",
"logoUrl": "https://hh.ru/employer-logo-original/0000000.png",
"badges": [
{
"type": "hrbrand",
"description": "Пример награды"
}
]
},
"salaryFrom": 200000,
"salaryTo": 300000,
"salaryCurrency": "RUR",
"salaryGross": false,
"salaryMode": "MONTH",
"salaryFrequency": "TWICE_PER_MONTH",
"area": {
"id": 1,
"name": "Москва",
"path": ".113.1."
},
"address": {
"city": "Москва",
"street": "Тверская улица",
"lat": 55.7558,
"lng": 37.6173,
"metroStations": [
{
"id": 1,
"name": "Охотный Ряд",
"lineColor": "#000000"
}
],
"district": "Тверской район"
},
"workExperience": "between3And6",
"employment": "FULL",
"employmentForm": "FULL",
"workSchedule": "remote",
"workFormats": [
"REMOTE"
],
"acceptHandicapped": false,
"acceptLaborContract": true,
"responsesCount": 42,
"totalResponsesCount": 100,
"onlineUsersCount": 5,
"professionalRoleIds": [
96,
156
],
"description": "<p>Обязанности: разработка и поддержка сервисов. Требования: опыт с Python 3+ года. Условия: удалённая работа, ДМС.</p>",
"descriptionText": "Обязанности: разработка и поддержка сервисов. Требования: опыт с Python 3+ года. Условия: удалённая работа, ДМС.",
"keySkills": [
"Python",
"Django",
"PostgreSQL",
"Docker",
"Git"
],
"contactInfo": {
"name": null,
"email": null,
"phones": [],
"contactsHidden": false
},
"detailScraped": true,
"scrapedAt": "2026-05-05T12:00:00.000Z"
}Ways to use this data
- Recruiting and talent sourcing: build a candidate-facing job feed, or watch a market for hiring signals by company, role or region.
- Salary and compensation research: collect published salary ranges by role, region, and experience level to benchmark pay.
- Job aggregators and alert bots: feed vacancies into your own site, Telegram bot or newsletter, filtered by keyword, area or schedule.
- Market and competitor monitoring: track which companies are hiring, for which roles, and how listings change over time with incremental mode.
- Targeted outreach: hand in a specific list of vacancy URLs or IDs to get full detail cards, for example to reach out to companies that just posted a role.
Before you run
Start with a small input and check the resulting records against your expected fields. Source content and actor options can change; the current Apify listing is the reference for availability and pricing.
Questions about this actor
What data does HH.ru Scraper return?
The documented output for HH.ru Scraper includes id, change type (incremental mode), url, title, published, company, it-accredited and salary from. Availability of each field depends on the source page and the selected mode.
How much does HH.ru Scraper cost?
It costs $2.00 per 1,000 results on the Apify Free plan and from $1.00 on paid plans, with smaller per-run or optional feature charges listed on Apify. The Apify listing shows the current rate for every plan.
Can I get only new or changed vacancies on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only new, updated and reappeared vacancies, and unchanged ones are not billed unless you turn on emitUnchanged.
Why does search mode only match vacancy titles by default?
searchField defaults to "name" so a keyword search stays relevant. hh.ru's own default behavior matches the keyword anywhere (title, description or company), which mixes in loosely related roles. Set "searchField": "everywhere" if you deliberately want that wider reach.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.
