Привет, сообщество.
Проработав в одной компании три года, пришло время сменить работодателя. И тут я понял, что я не знаю рыночную цену своим компетенциям. Во время работы на одном месте перестаешь ходить на собеседования, общаться по-душам с рекрутерами, а рост внутри одной компании как правило небольшой, как по скилам, так и по зарплате.
В то же время, как хобби, я юзал аналитический инструмент Power BI - красивые графики, диаграммы и тд. А главный сервис с вакансиями в РФ, ну вы знаете. Поразмыслив, я почувствовал, что добавив одно к другому, может получится интересная история.
Итак, мне нужны данные по рынку. Если воспользоваться поиском на сайте HHru, в выдаче можно увидеть кучу вакансий, но когда их сотни, для человека анализ не представляется возможным. Нашел в документации по API HHru, что данные по вакансиям бесплатны и открыты. То есть можно получить те же результаты, что и поиском, только в формате json, что в конечном счете съедобно для Power BI. Поехали!
Далее по шагам строим модель
Запускаем Power BI Desktop и заходим в редактор запросов.
-
Создаем новый Пустой запрос с именем function 1 и прописываем в него код ниже:
= (Adress as text) => let data = Json.Document(Web.Contents(Adress)) in data
Создаем новый Пустой запрос с именем function 2 и прописываем в него код ниже:
= (Adress as text) => let
data = Json.Document(Web.Contents("https://api.hh.ru/vacancies/"&Adress))
in
data
-
Создаем новый Пустой запрос с именем Вакансии и прописываем в него код ниже:
let Источник = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("BcFRCoAgDADQq4QHUOmjj0A6iEkMXW1QQ3RGx++9GA2p1r46B5UtkW3DvZBBMmPfFD8NBRQmlIsFse3D+3npCC3TcTLeJQg8aFL6AQ==", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [API = _t]), #"Измененный тип" = Table.TransformColumnTypes(Источник,{{"API", type text}}), #"Вызвана настраиваемая функция" = Table.AddColumn(#"Измененный тип", "Rez", each #"function 1"([API])), #"Развернутый элемент Rez" = Table.ExpandRecordColumn(#"Вызвана настраиваемая функция", "Rez", {"pages", "per_page", "page"}, {"Rez.pages", "Rez.per_page", "Rez.page"}), #"Вызвана настраиваемая функция1" = Table.AddColumn(#"Развернутый элемент Rez", "RezAll", each let rt = [API], pages = [Rez.pages] in List.Generate( ()=> [a = 0], each [a] < pages, each [ a = [a] + 1 ], each #"function 1"(rt&"&page="&Number.ToText([a]))) ), #"Удаленные столбцы" = Table.RemoveColumns(#"Вызвана настраиваемая функция1",{"Rez.pages", "Rez.per_page", "Rez.page"}), #"Развернутый элемент RezAll" = Table.ExpandListColumn(#"Удаленные столбцы", "RezAll"), #"Развернутый элемент RezAll1" = Table.ExpandRecordColumn(#"Развернутый элемент RezAll", "RezAll", {"items", "found", "pages", "per_page", "page", "clusters", "arguments", "alternate_url"}, {"RezAll.items", "RezAll.found", "RezAll.pages", "RezAll.per_page", "RezAll.page", "RezAll.clusters", "RezAll.arguments", "RezAll.alternate_url"}), #"Другие удаленные столбцы" = Table.SelectColumns(#"Развернутый элемент RezAll1",{"API", "RezAll.items", "RezAll.page"}), #"Развернутый элемент RezAll.items" = Table.ExpandListColumn(#"Другие удаленные столбцы", "RezAll.items"), #"Развернутый элемент RezAll.items1" = Table.ExpandRecordColumn(#"Развернутый элемент RezAll.items", "RezAll.items", {"id", "premium", "name", "department", "has_test", "response_letter_required", "area", "salary", "type", "address", "response_url", "sort_point_distance", "published_at", "created_at", "archived", "apply_alternate_url", "insider_interview", "url", "alternate_url", "relations", "employer", "snippet", "contacts", "schedule", "working_days", "working_time_intervals", "working_time_modes", "accept_temporary"}, {"RezAll.items.id", "RezAll.items.premium", "RezAll.items.name", "RezAll.items.department", "RezAll.items.has_test", "RezAll.items.response_letter_required", "RezAll.items.area", "RezAll.items.salary", "RezAll.items.type", "RezAll.items.address", "RezAll.items.response_url", "RezAll.items.sort_point_distance", "RezAll.items.published_at", "RezAll.items.created_at", "RezAll.items.archived", "RezAll.items.apply_alternate_url", "RezAll.items.insider_interview", "RezAll.items.url", "RezAll.items.alternate_url", "RezAll.items.relations", "RezAll.items.employer", "RezAll.items.snippet", "RezAll.items.contacts", "RezAll.items.schedule", "RezAll.items.working_days", "RezAll.items.working_time_intervals", "RezAll.items.working_time_modes", "RezAll.items.accept_temporary"}), #"Другие удаленные столбцы1" = Table.SelectColumns(#"Развернутый элемент RezAll.items1",{"RezAll.items.id"}), #"Измененный тип1" = Table.TransformColumnTypes(#"Другие удаленные столбцы1",{{"RezAll.items.id", type text}}), #"Вызвана настраиваемая функция2" = Table.AddColumn(#"Измененный тип1", "API", each #"function 2"([RezAll.items.id])), #"Развернутый элемент API" = Table.ExpandRecordColumn(#"Вызвана настраиваемая функция2", "API", {"id", "premium", "billing_type", "relations", "name", "insider_interview", "response_letter_required", "area", "salary", "type", "address", "allow_messages", "site", "experience", "schedule", "employment", "department", "contacts", "description", "branded_description", "vacancy_constructor_template", "key_skills", "accept_handicapped", "accept_kids", "archived", "response_url", "specializations", "professional_roles", "code", "hidden", "quick_responses_allowed", "driver_license_types", "accept_incomplete_resumes", "employer", "published_at", "created_at", "negotiations_url", "suitable_resumes_url", "apply_alternate_url", "has_test", "test", "alternate_url", "working_days", "working_time_intervals", "working_time_modes", "accept_temporary"}, {"API.id", "API.premium", "API.billing_type", "API.relations", "API.name", "API.insider_interview", "API.response_letter_required", "API.area", "API.salary", "API.type", "API.address", "API.allow_messages", "API.site", "API.experience", "API.schedule", "API.employment", "API.department", "API.contacts", "API.description", "API.branded_description", "API.vacancy_constructor_template", "API.key_skills", "API.accept_handicapped", "API.accept_kids", "API.archived", "API.response_url", "API.specializations", "API.professional_roles", "API.code", "API.hidden", "API.quick_responses_allowed", "API.driver_license_types", "API.accept_incomplete_resumes", "API.employer", "API.published_at", "API.created_at", "API.negotiations_url", "API.suitable_resumes_url", "API.apply_alternate_url", "API.has_test", "API.test", "API.alternate_url", "API.working_days", "API.working_time_intervals", "API.working_time_modes", "API.accept_temporary"}), #"Удаленные столбцы1" = Table.RemoveColumns(#"Развернутый элемент API",{"API.premium", "API.billing_type", "API.relations", "RezAll.items.id", "API.insider_interview", "API.response_letter_required"}), #"Удаленные столбцы2" = Table.RemoveColumns(#"Удаленные столбцы1",{"API.type", "API.address", "API.allow_messages", "API.site", "API.professional_roles"}), #"Удаленные столбцы3" = Table.RemoveColumns(#"Удаленные столбцы2",{"API.department", "API.contacts", "API.branded_description", "API.vacancy_constructor_template", "API.accept_handicapped", "API.accept_kids", "API.archived", "API.response_url", "API.code", "API.hidden", "API.quick_responses_allowed", "API.driver_license_types", "API.accept_incomplete_resumes", "API.negotiations_url", "API.suitable_resumes_url", "API.apply_alternate_url", "API.accept_temporary", "API.working_days", "API.working_time_intervals", "API.working_time_modes", "API.has_test", "API.test", "API.specializations"}), #"Переименованные столбцы" = Table.RenameColumns(#"Удаленные столбцы3",{{"API.id", "id"}, {"API.name", "Вакансия"}, {"API.area", "Локация"}, {"API.schedule", "График"}, {"API.employment", "Занятость"}, {"API.experience", "Опыт"}, {"API.alternate_url", "Ссылка"}, {"API.key_skills", "Навыки"}, {"API.employer", "Компания"}}), #"Развернутый элемент API.employment" = Table.ExpandRecordColumn(#"Переименованные столбцы", "Занятость", {"name"}, {"API.employment.name"}), #"Развернутый элемент API.schedule" = Table.ExpandRecordColumn(#"Развернутый элемент API.employment", "График", {"name"}, {"API.schedule.name"}), #"Развернутый элемент API.experience" = Table.ExpandRecordColumn(#"Развернутый элемент API.schedule", "Опыт", {"name"}, {"API.experience.name"}), #"Развернутый элемент API.salary" = Table.ExpandRecordColumn(#"Развернутый элемент API.experience", "API.salary", {"from", "to", "currency"}, {"API.salary.from", "API.salary.to", "API.salary.currency"}), #"Развернутый элемент API.area" = Table.ExpandRecordColumn(#"Развернутый элемент API.salary", "Локация", {"name"}, {"API.area.name"}), #"Развернутый элемент API.employer" = Table.ExpandRecordColumn(#"Развернутый элемент API.area", "Компания", {"name"}, {"API.employer.name"}), #"Переименованные столбцы1" = Table.RenameColumns(#"Развернутый элемент API.employer",{{"API.employer.name", "Компания"}, {"API.employment.name", "Занятость"}, {"API.schedule.name", "График"}, {"API.experience.name", "Опыт"}, {"API.salary.currency", "Валюта"}, {"API.salary.to", "ЗП до"}, {"API.salary.from", "ЗП от"}, {"API.area.name", "Локация"}}), #"Измененный тип2" = Table.TransformColumnTypes(#"Переименованные столбцы1",{{"id", Int64.Type}, {"Вакансия", type text}, {"Локация", type text}, {"ЗП от", type number}, {"ЗП до", type number}, {"Валюта", type text}, {"Опыт", type text}, {"График", type text}, {"Занятость", type text}, {"API.description", type text}, {"Компания", type text}, {"Ссылка", type text}}) in #"Измененный тип2"
-
Создаем новый Пустой запрос с именем именем Навыки и пропысываем в него код:
let Источник = Вакансии, #"Развернутый элемент API.key_skills" = Table.ExpandListColumn(Источник, "Навыки"), #"Другие удаленные столбцы" = Table.SelectColumns(#"Развернутый элемент API.key_skills",{"id", "Навыки"}), #"Развернутый элемент Навыки" = Table.ExpandRecordColumn(#"Другие удаленные столбцы", "Навыки", {"name"}, {"Навыки.name"}), #"Переименованные столбцы" = Table.RenameColumns(#"Развернутый элемент Навыки",{{"Навыки.name", "Навыки"}}), #"Измененный тип" = Table.TransformColumnTypes(#"Переименованные столбцы",{{"Навыки", type text}, {"id", type text}}) in #"Измененный тип"
-
В редакторе запросов должна получится такая картина
Hidden text
С кодом всё! Жмакаем по кнопке Закрыть и применить. Ждём пару минут.
Далее необходимо связать таблицы Вакансии и Навыки по полям id на вкладке Моделирование.
После того как мы подготовили модель начинается творческая работа, можно строить дашборды и проводить анализ. Давайте построим таблицу по вакансиям и отсортируем по полю ЗП, для этого используем таблицу Вакансии. Потом добавим график по требуемуму опыту. Выведем карточку с общим количеством вакансий, табличку в разрезе зарплат с указанием валюты.
Используя таблицу Навыки, добавим диаграмму по требуемым навыкам с сортировкой по убыванию. Должно получиться примерно так:
Причем, если в таблице поле Ссылка обозначить как URL-адрес, поле будет кликабельным. Можно отфильтровать вакансии и кликать по ссылке для просмотра подробной информации на сайте-источнике.
Отчет доступен онлайн по ссылке
Заключение
Таким образом, меняя название вакансии в запросах, можно получить анализ по любой вакансии. Программировать не нужно!
Есть ограничения HHru. В данном отчете можно получить не более 1500 вакансий. Нужно проверить колличество на сайте HHru. При проверке не забудьте проставить галочку "Искать в названиях вакансий" - так точнее поиск. Если превысите этот лимит, потом блокируют Ваш IP примерно на час.
На этом все :-)
Комментарии (2)
BetAlbo
05.04.2022 08:43Дашборд бомба! как изменить условие поиска на другие профессии по той ссылке?
KirillSimonov
Отличное применение знание постройки дэшборда! Как раз хотел посмотреть данные по вакансиям на Казахстане!