CoderCastrov logo
CoderCastrov
LinkedIn

Парсинг вакансий на Linkedin с помощью Python

Парсинг вакансий на Linkedin с помощью Python
просмотров
9 мин чтение
#LinkedIn

Что, если вы хотите создать свою собственную базу данных вакансий для определенного местоположения и затем использовать эту базу данных для своих клиентов или просто анализировать новые тенденции в вакансиях и зарплатах? В обоих случаях вам нужно либо парсить данные, либо использовать API платформы (если они достаточно дешевые или доступны для общего использования).

В этом руководстве мы извлечем данные с Linkedin, и поскольку он не предоставляет открытого API для доступа к этим данным, наш единственный выбор - это парсить их. Мы будем использовать Python 3.x.

Настройка предварительных требований

Предполагается, что вы уже установили Python 3.x на свой компьютер. Создайте пустую папку, в которой будет находиться наш скрипт на Python, а затем создайте внутри этой папки файл на Python.

mkdir jobs

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

  • [Requests ](https://pypi.org/project/requests/)— Он поможет нам отправить GET-запрос на веб-сайт-хост.
  • [BeautifulSoup](https://www.crummy.com/software/BeautifulSoup/bs4/doc/) — С помощью этой библиотеки мы сможем разбирать важные данные.

Давайте установим эти библиотеки

pip install requests
pip install beautifulsoup4

Анализ работы поиска работы на Linkedin

Это страница для поиска работы по Python в Лас-Вегасе. Теперь, если вы посмотрите на URL этой страницы, он будет выглядеть так - https://www.linkedin.com/jobs/search?keywords=Python (Programming Language)&location=Las Vegas, Nevada, United States&geoId=100293800&currentJobId=3415227738&position=1&pageNum=0

Давайте разберем его.

На этой странице у нас есть 118 вакансий, но когда я прокручиваю вниз до следующей страницы (эта страница имеет бесконечную прокрутку), pageNum не меняется. Так вопрос в том, как мы можем спарсить все вакансии?

Эту проблему можно решить с помощью веб-драйвера Selenium. Мы можем использовать метод .execute_script() для прокрутки страницы и извлечения всех страниц.

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

Вы можете сказать, что мы можем использовать функцию .click(), предоставленную Selenium. Согласно этой логике, вам придется перебирать каждую перечисленную вакансию с помощью цикла for и нажимать на них, чтобы получить подробности в правом блоке.

Да, этот метод правильный, но он слишком трудоемкий. Прокрутка и нажатие будут нагружать нашу систему, что помешает нам парсить в большом масштабе.

Что если я скажу вам, что есть легкий способ избежать этой проблемы и мы можем спарсить все данные всего лишь с помощью простого GET-запроса?

Звучит нереально, верно😕??

Нахождение решения в инструментах разработчика

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

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

Если вы нажмете на вкладку "Предварительный просмотр" для того же URL, то увидите все данные о вакансиях.

Откроем этот URL в нашем браузере.

Хорошо, теперь мы можем сделать небольшой вывод: каждый раз, когда вы прокручиваете и LinkedIn загружает другую страницу, LinkedIn делает GET-запрос по вышеуказанному URL для загрузки всех перечисленных вакансий.

Разберем URL, чтобы лучше понять, как это работает.

https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search?keywords=Python (Programming Language)&location=Las Vegas, Nevada, United States&geoId=100293800&currentJobId=3415227738&position=1&pageNum=0&start=25

Единственный параметр, который меняется с каждой страницей, это параметр start. Когда вы прокручиваете до третьей страницы, значение start становится 50. Таким образом, значение _start_ увеличивается на 25 для каждой новой страницы. Еще одна вещь, которую можно заметить, если увеличить значение _start_ на 1, то последняя вакансия скроется.

Хорошо, теперь у нас есть решение для получения всех перечисленных вакансий. А как насчет данных, которые появляются справа, когда вы нажимаете на какую-либо вакансию? Как их получить?

Когда вы нажимаете на вакансию, LinkedIn делает GET-запрос по этому URL. Но в URL слишком много шума. Самая простая форма URL будет выглядеть так - https://www.linkedin.com/jobs-guest/jobs/api/jobPosting/3415227738

Здесь 3415227738 - это currentJobId, который можно найти в теге li каждой перечисленной вакансии.

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

Что мы собираемся парсить?

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

  • Название компании
  • Должность
  • Уровень должности

С помощью метода .find_all() из BeautifulSoup мы собираемся парсить все вакансии. Затем мы извлечем jobid из каждой вакансии. После этого мы собираемся извлечь детали вакансии из этого API.

Парсинг идентификаторов вакансий на Linkedin

Давайте сначала импортируем все необходимые библиотеки.

import requests
from bs4 import BeautifulSoup

На этой странице для языка программирования Python в Лас-Вегасе размещено 117 вакансий.

Поскольку на каждой странице размещено 25 вакансий, наша логика поможет нам спарсить все вакансии.

  • Разделим 117 на 25
  • Если значение является десятичным числом или целым числом, мы будем использовать метод math.ceil().
import requests
from bs4 import BeautifulSoup
import math


target_url='https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search?keywords=Python%20%28Programming%20Language%29&location=Las%20Vegas%2C%20Nevada%2C%20United%20States&geoId=100293800&currentJobId=3415227738&start={}'
number_of_loops=math.ceil(117/25)

Давайте найдем расположение идентификаторов вакансий в DOM.

Идентификатор можно найти внутри элемента div с классом base-card. Вам нужно найти атрибут data-entity-urn внутри этого элемента, чтобы получить идентификатор.

Нам понадобятся вложенные циклы for, чтобы получить идентификаторы всех вакансий. Первый цикл будет изменять страницу, а второй цикл будет перебирать каждую вакансию на каждой странице. Надеюсь, это понятно.

target_url='https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search?keywords=Python%20%28Programming%20Language%29&location=Las%20Vegas%2C%20Nevada%2C%20United%20States&geoId=100293800&currentJobId=3415227738&start={}'
for i in range(0,math.ceil(117/25)):

    res = requests.get(target_url.format(i))
    soup=BeautifulSoup(res.text,'html.parser')
    alljobs_on_this_page=soup.find_all("li")

    for x in range(0,len(alljobs_on_this_page)):
        jobid = alljobs_on_this_page[x].find("div",{"class":"base-card"}).get('data-entity-urn').split(":")[3]
        l.append(jobid)

Вот пошаговое объяснение приведенного выше кода.

  • Мы объявили целевой URL, где размещены вакансии.
  • Затем мы запускаем цикл for до последней страницы.
  • Затем мы делаем GET запрос на страницу.
  • Мы используем BS4 для создания дерева разбора.
  • С помощью метода .find_all() мы находим все теги li, так как все вакансии хранятся внутри тегов li.
  • Затем мы запускаем еще один цикл, который будет выполняться до последней вакансии на любой странице.
  • Мы находим расположение идентификатора вакансии.
  • Мы добавляем все идентификаторы в массив.

В конце массив l будет содержать все идентификаторы для любого местоположения.

Парсинг деталей вакансии

Давайте найдем местоположение названия компании внутри DOM.

Название компании является значением атрибута alt, которое можно найти внутри div с классом top-card-layout__card.

Название вакансии можно найти внутри div с классом top-card-layout__entity-info. Текст находится внутри первого тега a этого div.

Уровень старшинства можно найти в первом теге li внутри ul с классом description__job-criteria-list.

Теперь мы сделаем GET-запрос к URL-адресу страницы вакансии. Эта страница предоставит нам информацию, которую мы хотим извлечь из Linkedin. Мы будем использовать вышеуказанные местоположения элементов DOM внутри BS4 для поиска соответствующих элементов.

target_url='https://www.linkedin.com/jobs-guest/jobs/api/jobPosting/{}'
for j in range(0,len(l)):

    resp = requests.get(target_url.format(l[j]))
    soup=BeautifulSoup(resp.text,'html.parser')

    try:
        o["company"]=soup.find("div",{"class":"top-card-layout__card"}).find("a").find("img").get('alt')
    except:
        o["company"]=None

    try:
        o["job-title"]=soup.find("div",{"class":"top-card-layout__entity-info"}).find("a").text.strip()
    except:
        o["job-title"]=None

    try:
        o["level"]=soup.find("ul",{"class":"description__job-criteria-list"}).find("li").text.replace("Seniority level","").strip()
    except:
        o["level"]=None



    k.append(o)
    o={}

print(k)
  • Мы объявили URL, который содержит URL-адрес специальной вакансии Linkedin для любой компании.
  • Цикл for будет выполняться для количества идентификаторов, присутствующих в массиве l.
  • Затем мы сделали GET-запрос к странице Linkedin.
  • Снова создали дерево разбора BS4.
  • Затем мы используем операторы try/except для извлечения всей информации.
  • Мы добавили объект o в массив k.
  • Объявили объект o пустым, чтобы он мог хранить данные другого URL-адреса.
  • В конце мы печатаем массив k.

После печати это результат.

Мы успешно смогли спарсить данные со страницы вакансий Linkedin. Теперь давайте сохраним их в CSV-файл.

Сохранение данных в CSV файл

Для этой операции мы будем использовать библиотеку pandas. Всего за две строки кода мы сможем сохранить наш массив в CSV файл.

Как установить?

pip install pandas

Импортируйте эту библиотеку в наш основной файл Python.

import pandas as pd

Теперь, используя метод DataFrame, мы преобразуем наш список k в формат строк и столбцов. Затем, используя метод .to_csv(), мы преобразуем DataFrame в CSV файл.

df = pd.DataFrame(k)
df.to_csv('linkedinjobs.csv', index=False, encoding='utf-8')

Вы можете добавить эти две строки, когда ваш список k будет готов со всеми данными. После выполнения программы вы получите CSV файл с именем linkedinjobs.csv в вашей корневой папке.

Таким образом, всего за несколько минут мы смогли спарсить страницу Linkedin Jobs и сохранить ее в CSV файл. Теперь, конечно же, вы можете спарсить гораздо больше другой информации, такой как зарплата, местоположение и т.д. Моя цель была объяснить вам, насколько просто спарсить вакансии с Linkedin без использования ресурсоемкого Selenium.

Полный код

Вот полный код для парсинга вакансий на Linkedin.

import requests
from bs4 import BeautifulSoup
import math
import pandas as pd
l=[]
o={}
k=[]
headers={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"}
target_url='https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search?keywords=Python%20%28Programming%20Language%29&location=Las%20Vegas%2C%20Nevada%2C%20United%20States&geoId=100293800&currentJobId=3415227738&start={}'
for i in range(0,math.ceil(117/25)):

    res = requests.get(target_url.format(i))
    soup=BeautifulSoup(res.text,'html.parser')
    alljobs_on_this_page=soup.find_all("li")
    print(len(alljobs_on_this_page))
    for x in range(0,len(alljobs_on_this_page)):
        jobid = alljobs_on_this_page[x].find("div",{"class":"base-card"}).get('data-entity-urn').split(":")[3]
        l.append(jobid)

target_url='https://www.linkedin.com/jobs-guest/jobs/api/jobPosting/{}'
for j in range(0,len(l)):

    resp = requests.get(target_url.format(l[j]))
    soup=BeautifulSoup(resp.text,'html.parser')

    try:
        o["company"]=soup.find("div",{"class":"top-card-layout__card"}).find("a").find("img").get('alt')
    except:
        o["company"]=None

    try:
        o["job-title"]=soup.find("div",{"class":"top-card-layout__entity-info"}).find("a").text.strip()
    except:
        o["job-title"]=None

    try:
        o["level"]=soup.find("ul",{"class":"description__job-criteria-list"}).find("li").text.replace("Seniority level","").strip()
    except:
        o["level"]=None



    k.append(o)
    o={}

df = pd.DataFrame(k)
df.to_csv('linkedinjobs.csv', index=False, encoding='utf-8')
print(k)

Избегайте блокировки с помощью API Scrapingdog для Linkedin Jobs

Вы должны зарегистрироваться для бесплатного аккаунта, чтобы начать его использовать. Вам потребуется всего 10 секунд, чтобы начать работу с Scrapingdog.

После успешной регистрации вы получите свой собственный API-ключ из панели управления.

import requests

target_url='https://api.scrapingdog.com/linkedinjobs?api_key=Your-API-Key&field=Python%20(Programming%20Language)&geoid=100293800&page=1'

resp = requests.get(target_url).json()

print(resp)

С помощью этого API вы получите разобранные данные JSON со страницы вакансий LinkedIn. Вам нужно только передать field, который является типом работы, которую вы хотите спарсить, затем geoid, который является идентификатором местоположения, предоставленным самим LinkedIn. Вы можете найти его в URL-адресе страницы вакансий LinkedIn, и, наконец, номер page. Для каждого номера страницы вы получите 25 или менее вакансий.

После запуска приведенного выше кода вы получите следующий результат.

Для более подробного описания этого API посетите документацию или Linkedin Jobs API.

Заключение

Мы смогли собрать данные с Linkedin, используя обычный GET-запрос, без использования метода прокрутки и клика. С помощью библиотеки [pandas](https://pandas.pydata.org/) мы также сохранили данные в CSV-файл. Теперь вы можете создать свою собственную логику для извлечения данных о вакансиях из множества других мест. Но код останется примерно таким же.

Вы можете использовать [lxml](https://lxml.de/) вместо BS4, но я обычно предпочитаю BS4. Однако, если вы хотите собрать миллионы вакансий, то Linkedin заблокирует вас в кратчайшие сроки. Поэтому я всегда рекомендую использовать API парсинга веб-страниц, которое поможет вам собирать данные с этого сайта без ограничений.

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

Дополнительные ресурсы

Вот несколько дополнительных ресурсов, которые могут быть полезными во время вашего путешествия по парсингу веб-страниц: