Как выгрузить вакансии из hh в excel

Обновлено: 04.07.2024

Со стороны пользователя это будет обычный веб-сайт, на котором можно авторизоваться с помощью API HeadHunter, получить список личных резюме и просмотреть рекомендованные вакансии. Со стороны разработчика еще добавляются модуль, который будет выкачивать через API актуальные вакансии, и модуль, который будет строить на основе собранных вакансий рекомендации.

Алгоритм простой. Раз в N минут получаем список вакансий, опубликованных за последние N минут. Тут надо учесть, что за один запрос возвращается не более 500 вакансий, поэтому запросы делаются с разбивкой на страницы. Путь к вакансиям в API выглядит как-то так: /vacancies?per_page=<>&date_from=<>&date_to=<>&page=<>. В списках содержатся не все данные вакансий, а значит, придется каждую вакансию запросить отдельно. Еще следует учесть, что иногда за короткий промежуток времени публикуется большое количество вакансий, так что программа не успевает за отведенные N минут скачать все опубликованные. Значит, качаем вакансии в несколько потоков. Кому интересно, вот ссылка на код качальщика на Github-е. Сразу извиняюсь за качество кода — я не «питонщик». Данный скрипт периодически запускается по крону.

Отдельно хочу отметить, что сначала я пытался сохранять вакансии в MySQL. Но на моем сервере очень ограниченное количество ресурсов и нет возможности при построении рекомендаций держать всё в памяти, а выгружать все каждый раз из MySQL небыстро. Поэтому приходилось получать данные частями, на рекомендации уходило около часа. Тогда я решил поискать in-memory хранилище для вакансий. Выбор пал на Redis из-за наличия поддержки в Python, простоты установки и использования, наличия поддержки структур данных и сохранения состояния при рестарте.

Логично, что все данные вакансии хранить нет смысла. Поэтому при скачивании преобразуем вакансию в вектор параметров. Под параметрами подразумеваются слова, которые входят в разные вакансии. Каждому документу (вакансии) соответствует вектор одинаковой длины. Каждому элементу вектора соответствует определенное слово и задается значение — вес, который это слово имеет в текущем документе.

Если взять все слова, которые содержатся во всех вакансиях, то получится слишком длинный список. Важно найти ограниченный список таких слов, которые бы максимально характеризовали все документы. Из этих слов составим словарь.

Вакансии принадлежат определенным профобластям. Я предположил, что имеет смысл разбить вакансии по профобластям и извлечь самые важные слова для каждой профобласти. Для создания словаря я скачал около 113 000 вакансий. Одно и то же слово может иметь несколько словоформ. Было бы хорошо представить их как одно слово. Для этого применяется стемминг — нахождение основы слова. В «Питоне» есть хорошая реализация (PyStemmer), поддерживающая русский язык.

После стемминга я разбил все документы по группам, соответствующим профобластям. Если вакансии соответствует несколько профобластей, то, конечно, она будет в нескольких группах. Каждый документ внутри каждой группы преобразуем в вектор. Для этого нам поможет sklearn-овкий CountVectorizer. Ему на вход подается список документов. Он достает все слова из списка и считает, сколько раз какое слово встречается в конкретном документе. Это и будет вектор.

Некоторые слова слишком часто встречаются во многих документах и являются незначительными. А некоторые — наоборот, встречаются не так часто, но хорошо описывают документ или несколько документов. Для компенсации считается TF-IDF для каждой группы векторов. При подсчете вес некоторого слова пропорционален количеству употребления этого слова в документе и обратно пропорционален частоте употребления слова в других документах коллекции. Для подсчета этой меры в sklearn есть TfidfTransformer. Он принимает на вход векторы, полученные из CountVectorizer-а, и возвращает пересчитанные векторы такой же размерности.

После того как посчитали TF-IDF для документов каждой группы, считаем в каждой группе среднее арифметическое для каждого параметра в векторах. Находим определенное количество параметров с максимальным значением и сохраняем слова, соответствующие этим значениям. Это и будут самые важные слова для конкретной специализации. Я сохранял для каждой специализации по 350 слов, чтобы в итоге получить словарь примерно из 10 000 слов. Вектором именно такой длины будет характеризоваться каждая вакансия. Вот полный код для создания словаря. Каждый документ, описывающий вакансию, был составлен из слов заголовка, основной информации и ключевых навыков.

Теперь у нас есть словарь, используя который можно превратить каждую вакансию при сохранении в вектор, соответствующий только этим словам. Для этого создается новый CountVectorizer, параметризованный словарем.

Для каждой вакансии, данные которой мы хотим сохранить, производим стемминг данных, прогоняем через CountVectorizer и TfidfTransformer и сохраняем в Redis. При сохранении в Redis векторов вакансий я столкнулся с проблемой недостатка места в оперативной памяти. Я для рекомендаций использую вакансии за последние 5 дней, а это около 130 000. Для каждой из них приходится хранить вектор размером 10000 элементов. У меня этот объем занял 7,5GB. Столько оперативки нет на моем сервере. Тогда я подумал о том, что, раз данные я сохраняю в json и они получилиcь очень разреженными, они наверняка отлично сжимаются. Поэтому перед сохранением я их энкодю в zlib. В итоге те же данные стали занимать примерно 250MB.

  1. Сохраняемым записям можно выставлять TTL, после которого они автоматически удаляются и не приходится заботиться о высвобождении места.
  2. Одному ключу можно сопоставить HashMap. Так, вместе с вектором я для вакансии сохраняю её регион и зп.

Для желающих попробовать Redis есть инструкция. Поднимается за пару минут.

Когда пользователь авторизуется на сайте, его резюме сохраняется в приложении. Чтобы сравнивать резюме с вакансиями, его так же надо преобразовать в вектор той же размерности и с таким же порядком параметров. Для создания вектора я брал текст из заголовка, ключевых навыков и поля «Обо мне». Также я предварительно сохранил CountVectorizer со словарем и TfidfTransformer, обученные на данных вакансий, которые я скачал в самом начале. Используя их, легко получить векторы для резюме.


Для создания рекомендаций находим векторы вакансий, похожие на вектор резюме. В качестве меры схожести я использовал косинусное расстояние. В sklearn есть готовая реализация.

Для каждого резюме сохраняем список самых похожих вакансий.

Еще надо учесть такие вещи, как зарплата и региональность. Поэтому исключаем для каждого резюме вакансии из неподходящих регионов, и если зарплата не соответствует определенной вилке. Часто в вакансии не указывается сумма. А именно: 31% из сохраненных 113 000 не содержал ЗП. Я решил, что такие вакансии тоже стоит рекомендовать.

Скрипт по подбору рекомендаций запускается периодически по крону. Это значит, что придется немного подождать, чтобы получить рекомендацию для своего резюме.

Собственно, вот результат того, что получилось. Пробуйте. Если кому интересны исходники, то вот.

Итак, я написал систему рекомендаций вакансий по информации, взятой из резюме. Все данные были получены через API HeadHunter. Используйте API, если у вас возникнет желание сделать свой сервис или мобильное приложение, связанное с HR тематикой. О проблемах или недостающем функционале пишите нам в issues.

UPD: По результатам готовых рекомендаций я понял, что не стоило вообще включать в выдачу результаты с низким коэффициентом схожести. Так же имело смысл исключить из результатов, если в векторе заполнено слишком мало параметров

UPD2: Добавил к резюме данные из последнего опыта работы. Теперь у тех, у кого недостаточно данных в остальных полях, должно стать лучше. Но мне этот подход не очень нравится по причине того, что в прошлом опыте может быть и то, что соискателю больше не интересно

Те кто сталкивался с задачами автоматизированного анализа резюме, представляют современное состояние дел в этой области — существующие парсеры в основном ограничиваются выделением контактных данных и ещё нескольких полей, таких как «должность» и «город».

Для сколько-нибудь осмысленного анализа этого мало. Важно не только выделить некие строки и пометить их тегами, но и определить, что за объекты кроются за ними.

Живой пример (кусок XML результата анализа резюме от одного из лидеров области Sovren):


Парсер Sovren прекрасно справился с выделением полей. Ребята не зря занимаются этим делом без малого 20 лет!

Но что дальше делать с «Ведущий специалист отдела развития информационных систем»? Как понять, что же это за должность, насколько опыт работы этого человека релевантен для той или иной вакансии?

Если ваша задача — поиск сотрудника под требования вакансии или наоборот — вакансии под опыт и пожелания кандидата, то поиск ключевых слов, сравнение bag of words дают посредственные результаты. Для объектов, которым соответствует множество возможных синонимичных наименований такой подход не сработает.

Для начала нужно нормализовать наименования, превратить «специалистов в чём-либо» в программистов, сисадминов и прочих отоларингологов.

Для этого придётся составить базу знаний, таксономию объектов. Причём специфика такова, что недостаточно описать, например только строителей — люди меняют области деятельности и в резюме строителя могут встречаться и другие, не связанные со строительством места работы.

И если таксономия будет описывать только строительство — в текстах, относящихся к другим областям деятельности, будут ложные срабатывания. В строительстве «архитектор» — это одно, а в IT — совсем другое. «Операция», «Акция», «Объект» и множество словосочетаний, содержащих эти слова — примеры неоднозначностей, которые необходимо разрешать.

Простая нормализация тоже не спасёт отца русской демократии. Фантазия людей пишущих резюме и составляющих штатные расписания не перестаёт удивлять. К сожалению для нас, разработчиков, это значит, что в общем случае из строки описывающей объект идентифицировать этот объект нельзя. То есть вы конечно можете попытаться обучить какой-нибудь классификатор, скармливая ему поле и желаемую должность.
И он даже будет работать. На «бухгалтерах», «секретарях», «программистах».
Только вот в резюме люди пишут «специалист отдела N» и понять, бухгалтер он или секретарь, можно лишь по контексту, набору выполняемых обязанностей.

Казалось бы — хорошо, учтём контекст, пусть классификатор обучается ещё и на обязанностях. Так, да не так — при определении набора обязанностей та же проблема: неоднозначность трактовки, всякие непонятные анафоры).

Мы решили применить вероятностный (байесовский) подход:

Анализируя исходный текст, для всех строк (например, «архитектор», «работа
с клиентами») мы определяем множество всех возможных трактовок
(например, для «архитектора» это будет «архитектор зданий», «архитектор программного
обеспечения» и т.д.). В результате получается набор множеств трактовок. Затем
ищем такое сочетание трактовок из всех множеств, чтобы его правдоподобность
была максимальной.

    2007-2009 ООО Umbrella corp. Старший менеджер. Работа с ключевыми клиентами, поиск новых клиентов, переговоры, оформление сделок;

Для того, чтобы выбрать между «менеджером по работе с клиентами» и «продавцом» мы оцениваем правдоподобность сочетания этих должностей с найденными в этом месте работы навыками. При этом навыки могут таким же образом выбираться из нескольких вариантов, поэтому задача состоит в выборе наиболее правдоподобного сочетания из множества найденных в тексте объектов.

Количество объектов разных типов (навыки, должности, отрасли, города и пр.) очень большое (сотни тысяч в нашей базе знаний), поэтому пространство, которому принадлежат резюме очень-очень многомерное. Для обучения большинства machine learning алгоритмов понадобится астрономическое количество примеров.

Мы решили резать. То есть кардинально сокращать количество параметров и использовать результат обучения только там, где у нас есть достаточное количество примеров.

Для начала мы стали собирать статистику по сочетаниям кортежей признаков, например: должность-отрасль, должность-отдел, должность-навык. На основе этой статистики мы оцениваем правдоподобность новых, ещё не встречаемых ранее, сочетаний
объектов и выбираем лучшее сочетание.

В примере выше набор навыков склоняет парсер в сторону менеджера в первом случае и в сторону продавца во втором.

Использование простых счётчиков и оценки вероятности по Байесу позволяет получать хорошие результаты при небольшом количестве примеров. В нашей базе знаний сейчас около ста тысяч размеченных специалистами вакансий и резюме, и это позволяет разрешать большинство неоднозначностей для распространённых объектов.

На выходе мы получаем JSON объект, описывающий вакансию или резюме в терминах нашей базы знаний, а не в тех, которые придумал соискатель или работодатель.

Это представление можно использовать для точного поиска по параметрам, оценки («скоринга») резюме соискателя или сопоставления пар «резюме-вакансия».

Мы сделали простой интерфейс, в котором можно загрузить резюме (doc, docx, pdf (не картинкой) и другие форматы) и получить его представление в JSON. Только не забывайте про 152ФЗ! Не надо эксперементировать с резюме с реальными персональными данными :)

Например вот такое резюме:

Ответственный и трудолюбивый менеджер по продажам.

Опыт работы

  • 2001-2002: ООО Бытдомуправ. Уборщик территории во дворе. Особенно добросовестно выполнял работы по уборке снега.
  • С 12.03.2005 по 30.01.2007 Магазин «Надежда». Старший киоскёр по продаже колбасы. Увеличил продажи колбасы на 146% за 2 месяца.
  • 2002-2003: ООО Кузремонт Жестянщик-кузовщик в автомастерской. Переделывал «Запорожцы» в «Мерседесы».

Превращается в следующий JSON:


Извлечение персональных данных в парсере отключено, не ищите их в JSON.

На мой предвзятый взгляд, результат интересный и применений ему — множество. Хотя до точности распознавания объектов, сравнимой с человеческой, ещё очень далеко. Нужно развивать базу знаний, обучать алгоритм на примерах, вводить дополнительные эвристики и, возможно, узкоспециализированные классификаторы, например для определения отраслей.

Интересно, какие методики используете или использовали бы Вы? Особенно интересно, использует ли кто-нибудь семантический подход а-ля Compreno от ABBYY?


Хочешь узнать, какая ситуация на рынке труда, особенно в области "дата-сайенс"?
Если знаешь Python и Pandas, парсинг Хедхантера это кажется один с самый надежных и легких способов.
Код работает на Python3.6 и Pandas 0.24.2
Ipython можно скачать здесь.
Чтобы проверить версию Pandas(Linux/MacOS) console:

И потом в командной строке

Уже все настроили? Поехали!

HH позволяет найти работу в России. Данный рекрутинговый ресурс обладает самой большой базой данных вакансий. HH делится удобным api.

Немного погуглил и вот получилось написать парсер.

В итоге мы получили файл csv с названием указанным в job_title.
В указанном будет загружен один файл с вакансиями с фразой
«Data Analyst» и «data scientist». Если хотите отдельно поменяйте строку на

Тогда вы получаете 2 файла с этими названиями.

Что интересно, есть и другие операторы кроме «and». С их помощью можно искать точные совпадения. Подробнее по ссылке.


Что делать, чтобы поменять название колонки “Unnamed”?


Получилось разделить зарплаты на валюты, самостоятельно сможете попробовать сделать анализ например только для евро. Я займусь сейчас только рублевыми зарплатами

Наконец-то узнали, около 150 тыс рублей, как ожидаемо.

Для средней зарплаты у меня были такие условия:

  • не считал вакансии, в которых нет указанной зарплаты (df.salary.dropna)
  • взял только зарплаты в рублях
  • если была вилка, тогда взял среднее значение (например, вилка от 10000 rub до 20000 rub → 15000 rub).


Как востребованы в области "Data Science" джуны?

EDIT:
Версия пользователя zoldaten
Парсер hh
Код не авторский, кроме некоторых костылей.


Обработать все файлы Excel в заданной папке, и извлечь информацию из определенных ячеек.
Искать в файлах данные не привязываясь к адресам ячеек (структура файлов немного отличается, часть данных смещена)


Перебрать все файлы в заданной папке, и сформировать таблицу Excel с данными из этих файлов.


Получить список файлов в папке, выбираемой пользователем.
Вывести в первый столбец имена файлов с гиперссылками.


Загрузить данные из выбранного текстового файла (на примере файла html), и вывести результат в таблицу Excel.


Собрать все данные о соискателях из файлов резюме в заданной папке, и вывести результат в таблицу Excel


Преобразовать базу формата Word с контактными данными, в таблицу Excel



Преобразовать файлы выписки (формата XML) из росрееестра (ЕГРН) в таблицу Excel для дальнейшего анализа


Создать таблицу цен на грузоперевозки между городами России,
взяв данные из ПДФ файлов с сайта транспортной компании.


Реализовать обработку банковских выписок формата 1CClientBankExchange (из программы 1С) с выводом данных в Excel.
Вывести в отдельные столбцы: название файла с выпиской, дату операции, номер документа, сумму (дебет и кредит - в отдельные столбцы), назначение платежа, тип документа, данные по контрагенту и компании, чья выписка обрабатывается (наименование организации, ИНН, расчетный счет, название банка, БИК банка)
Из всех выписок 1С в папке, создать единый файл Excel.


Обработать все файлы XML в выбранной папке, и сформировать отчёт в формате Excel (одна строка таблицы Excel соответствует одному XML файлу)


Обработать все файлы Word в заданной папке, и сформировать новую таблицу Excel с данными из файлов Word из 9 столбцов:
Имя файла, ФИО, Должность, Руководитель, Место, Номинация, Работа, ФИО сотрудника, Должность сотрудника

Читайте также: