Публичные вакансии LinkedIn, богатый источник данных о найме: названия должностей, компании, местоположения, даты размещения и поисковые фасеты, показывающие тенденции спроса. Проблема в программном получении этих данных. LinkedIn рендерит страницы на стороне клиента и агрессивно выявляет автоматизированный трафик, поэтому обычный HTTP-запрос возвращает пустую оболочку вместо вакансий. В этом руководстве показано, как парсить публичные вакансии LinkedIn на Python: небольшой рабочий скрипт, загружающий отрендеренную страницу через Crawling API, извлекающий нужные поля и записывающий их на диск.
Чтобы быть честными, всё руководство ограничено публичными данными: вакансиями, которые любой пользователь видит на публичных страницах поиска LinkedIn без входа в систему. Оно не касается аккаунтов, контента за входом в систему или личных профилей. Правовой раздел ниже чётко обозначает эту границу, и это не стандартная оговорка, поэтому прочитайте его перед применением к реальным объёмам.
Легально ли парсить LinkedIn?
Прочитайте этот раздел первым, поскольку LinkedIn является одной из наиболее чувствительных целей в интернете, и честный ответ, «зависит от обстоятельств, а объём имеет огромное значение». Это руководство намеренно ограничено публичными, неаутентифицированными вакансиями: страницами, которые LinkedIn отдаёт любому пользователю, вошедшему в систему или нет, на своей публичной площадке поиска вакансий. В этих рамках несколько правил являются обязательными:
- Не парсите за аутентификацией. Если для просмотра страницы требуется вход, она выходит за рамки этого руководства. Никаких куки сессии, никакого повторного использования учётных данных, никакого обхода аутентификации.
- Не собирайте личные данные или личные профили. Имена, контактные данные, графы связей и страницы отдельных профилей находятся под запретом. В этом руководстве собираются метаданные объявлений о вакансиях, а не данные об идентифицируемых людях.
- Не нарушайте Пользовательское соглашение LinkedIn. Прочитайте Условия обслуживания LinkedIn и его robots.txt перед началом и соблюдайте то, что в них написано. Уважайте заявленные ожидания по частоте запросов и поддерживайте объём запросов достаточно низким, чтобы не перегружать чужие серверы.
О правовом контексте: в деле hiQ Labs против LinkedIn американские суды рассматривали парсинг общедоступных данных и в целом пришли к выводу, что доступ к публичным страницам сам по себе не нарушал Закон о компьютерном мошенничестве и злоупотреблениях. Это дело часто цитируется как основание для парсинга LinkedIn, но оно не является безоговорочным разрешением. Договорные условия (Пользовательское соглашение), законы о конфиденциальности и то, как вы используете данные, по-прежнему применяются, и ситуация продолжает меняться. Воспринимайте границу публичных данных как минимальный порог, а не как лазейку.
Всё в этом руководстве собирает публичные метаданные объявлений о вакансиях: названия, компании, местоположения и даты размещения, которые любой пользователь видит без аккаунта. Оно не охватывает данные за входом в систему, личные профили, данные о связях, сообщения или обход аутентификации. Если вашему проекту нужно больше, чем публичные вакансии, правильный шаг, официальное партнёрство или соглашение об обмене данными с LinkedIn, а не более изощрённый скрапер.
Почему обычный запрос не работает на LinkedIn
Запросите URL поиска вакансий LinkedIn с помощью обычного HTTP-клиента, и вы получите ответ 200 с почти полным отсутствием данных о вакансиях в теле. Против вас работают два фактора. Во-первых, LinkedIn рендерит объявления в браузере с помощью JavaScript, поэтому исходный HTML является оболочкой, заполняющейся только после выполнения скриптов страницы. Во-вторых, LinkedIn быстро выявляет автоматизированный трафик: IP датацентров и паттерны запросов, не похожие на запросы настоящего посетителя, блокируются до того, как достигают отрендеренного контента.
Поэтому рабочему скраперу вакансий LinkedIn нужны сразу две вещи в одном запросе: браузер, рендерящий страницу, и IP, который платформа воспринимает как настоящего посетителя. Можно собрать это самостоятельно с помощью headless-браузера плюс пула ротируемых резидентных прокси, но поддержание такого стека в рабочем состоянии, это и есть основная работа. Crawling API объединяет оба компонента в одном вызове: отправьте ему URL с JavaScript-токеном, он рендерит страницу за доверенным IP и возвращает готовый HTML.
Crawlbase предлагает два типа токенов. Обычный токен загружает статический HTML; JavaScript (JS) токен сначала рендерит страницу в реальном браузере. LinkedIn является клиентским рендерингом, поэтому здесь нужен JS-токен. Использование обычного токена возвращает ту же пустую оболочку, что и обычный запрос.
Понимание цели: публичный URL поиска вакансий LinkedIn
Публичный поиск вакансий LinkedIn находится по предсказуемому URL, параметры запроса которого напрямую соответствуют форме поиска, поэтому любой поиск можно программно создать без управления пользовательским интерфейсом. Вот конкретный пример: вакансии разработчика Python в Лондоне.
https://www.linkedin.com/jobs/search?keywords=Python%20Developer&location=London
Важные параметры:
- keywords искомая роль или навык, закодированный в URL.
- location город, регион или страна для поиска.
Сформируйте URL с нужными параметрами и получите повторяемую цель. Варьируйте ключевые слова и местоположение в цикле и получите задачу отслеживания тенденций найма, наблюдающую за публичным спросом с течением времени.
Настройка окружения
Вам нужен Python 3.8 или выше. Проверьте версию, создайте виртуальное окружение для изоляции зависимостей, затем установите библиотеки.
python --version python -m venv linkedin_env source linkedin_env/bin/activate pip install crawlbase beautifulsoup4
В Windows активируйте окружение командой linkedin_env\Scripts\activate вместо строки с source. Две зависимости выполняют работу: crawlbase является официальным клиентом для Crawling API, а beautifulsoup4 парсит возвращаемый HTML для извлечения полей. Также нужны аккаунт Crawlbase и JS-токен, который вы получаете из панели управления после регистрации. Вставьте его в код там, где указано YOUR_CRAWLBASE_JS_TOKEN.
Загрузка отрендеренной страницы поиска вакансий
Начните с получения готовой страницы. Вы передаёте два параметра, важных для такого сайта, как LinkedIn: ajax_wait говорит API ждать загрузки асинхронного контента, а page_wait выдерживает фиксированное количество миллисекунд после загрузки, чтобы поздно рендерящиеся объявления успели появиться. Пять секунд, разумная отправная точка; увеличьте значение, если результаты возвращаются скудными.
from crawlbase import CrawlingAPI api = CrawlingAPI({"token": "YOUR_CRAWLBASE_JS_TOKEN"}) options = {"ajax_wait": "true", "page_wait": 5000} jobs_url = "https://www.linkedin.com/jobs/search?keywords=Python%20Developer&location=London" def fetch_jobs_html(url): response = api.get(url, options) if response["status_code"] == 200: return response["body"].decode("utf-8") print("Failed to fetch the page. Status code:", response["status_code"]) return None html = fetch_jobs_html(jobs_url) print(html[:500])
Запустите и вы должны увидеть настоящую разметку с карточками вакансий, а не пустую оболочку, которую возвращает обычный запрос. Это подтверждает, что рендеринг работает, прежде чем вы напишете единственный селектор.
LinkedIn требует отрендеренной страницы за доверенным IP в одном вызове. Crawling API принимает JS-токен, запускает страницу в реальном браузере, на стороне сервера ротирует резидентные IP и передаёт готовый HTML, поэтому вам не нужно самостоятельно управлять парком headless-браузеров и пулом прокси. Попробуйте на публичном поиске вакансий в рамках бесплатного тарифа.
Парсинг объявлений о вакансиях
Имея HTML, загрузите его в BeautifulSoup и пройдите по карточкам вакансий. Каждая карточка на публичной странице поиска содержит нужные поля: название должности, название компании, местоположение и дата размещения. Проверьте живую страницу в инструментах разработчика браузера, чтобы найти текущие селекторы, а затем сопоставьте каждое поле с одним из них.
from bs4 import BeautifulSoup def extract_jobs(html): soup = BeautifulSoup(html, "html.parser") jobs = [] for card in soup.select("div.base-card"): title = card.select_one("h3.base-search-card__title") company = card.select_one("h4.base-search-card__subtitle") location = card.select_one("span.job-search-card__location") posted = card.select_one("time") jobs.append({ "title": title.get_text(strip=True) if title else "", "company": company.get_text(strip=True) if company else "", "location": location.get_text(strip=True) if location else "", "posted": posted["datetime"] if posted else "", }) return jobs
Имена классов LinkedIn меняются без предупреждения. Воспринимайте приведённые выше селекторы как отправную точку, а не контракт. Когда извлечение возвращает пустые строки, заново проверьте живую публичную страницу поиска вакансий и обновите селекторы. Это нормальное обслуживание для любого производственного скрапера, а не признак неисправности.
Объедините загрузку и парсинг в функции main, чтобы получить один рабочий скрипт.
def main(): html = fetch_jobs_html(jobs_url) if not html: return jobs = extract_jobs(html) for job in jobs: print(job) if __name__ == "__main__": main()
Как выглядит результат
Запустите полный скрипт и получите список структурированных объектов вакансий. Краткий образец:
[ { "title": "Senior Python Developer", "company": "Monzo Bank", "location": "London, England, United Kingdom", "posted": "2026-01-14" }, { "title": "Python Backend Engineer", "company": "Deliveroo", "location": "London, England, United Kingdom", "posted": "2026-01-12" } ]
Обработка пагинации
Публичный поиск вакансий показывает первую партию объявлений и раскрывает больше при прокрутке или переходе по страницам. Самый чистый способ обходить их, параметр запроса start, смещающий результаты: start=0, первая страница, start=25, следующая, и так далее. Перебирайте смещение, загружайте каждую страницу через Crawling API и накапливайте строки.
all_jobs = [] base = "https://www.linkedin.com/jobs/search?keywords=Python%20Developer&location=London" for start in range(0, 75, 25): page_url = f"{base}&start={start}" html = fetch_jobs_html(page_url) if not html: break all_jobs.extend(extract_jobs(html)) print(f"Collected {len(all_jobs)} listings")
Поддерживайте небольшое количество страниц и соблюдайте темп цикла. Обход трёх страниц для выборки поиска кардинально отличается от прохождения тысяч смещений в тесном цикле, и именно второй паттерн приводит к блокировке скрапера.
Сохранение результатов в CSV
Вывод в консоль удобен при итерации, но нужны данные на диске. Встроенный модуль Python csv сопоставляет каждый ключ объекта со столбцом и записывает строки в несколько строк кода без дополнительных зависимостей.
import csv def save_to_csv(jobs, path="linkedin_jobs.csv"): fields = ["title", "company", "location", "posted"] with open(path, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=fields) writer.writeheader() writer.writerows(jobs) print(f"Saved {path}")
Вызовите save_to_csv(all_jobs) после цикла пагинации, и каждый запуск записывает аккуратный файл linkedin_jobs.csv, который можно открыть в любой таблице или загрузить в конвейер. Если вы предпочитаете запрашивать данные с помощью SQL, запишите те же строки в таблицу SQLite; парсинг остаётся идентичным.
Как оставаться незаблокированным
Даже при обработке рендеринга, LinkedIn следит за трафиком, типичным для скраперов. Несколько привычек помогают поддерживать работоспособность парсера, и они применимы к любой сложной коммерческой цели.
- Соблюдайте темп запросов. Быстрые запросы к одному поиску в тесном цикле, самый быстрый способ получить блокировку. Распределяйте запросы и варьируйте ключевые слова и местоположение.
- Опирайтесь на ротацию. Пул резидентных прокси распределяет запросы по многим реальным IP, чтобы ни один адрес не превысил лимит. Crawling API делает это за вас; если вы предпочитаете направлять собственный трафик, Smart AI Proxy предоставляет ту же ротацию резидентных IP в качестве прокси-эндпоинта.
- Следите за кодами статуса. Запуск, возвращающий проверки или ошибки, сигнализирует о том, что текущий темп или уровень IP больше недостаточны. Воспринимайте коды ошибок статуса прокси как сигнал, а не шум, и снижайте темп при их появлении.
Для изучения общей стратегии смотрите руководство как парсить сайты, не попадая под блокировку.
Ключевые выводы
- Оставайтесь в рамках публичных данных о вакансиях. Это руководство собирает только публичные метаданные объявлений. Никаких страниц за входом в систему, личных профилей, обхода аутентификации; соблюдайте Пользовательское соглашение и robots.txt LinkedIn.
- LinkedIn использует клиентский рендеринг. Обычный запрос возвращает пустую оболочку, поэтому необходимо отрендерить страницу перед парсингом.
-
Вам нужны рендеринг и доверенный IP вместе. Crawling API с JS-токеном обеспечивает оба в одном вызове;
ajax_waitиpage_waitуправляют временем ожидания контента. -
Пагинация, это смещение. Увеличивайте параметр
startс шагом 25 для обхода дополнительных страниц публичных результатов. - Ротируйте и соблюдайте темп для защиты от блокировки. Crawling API ротирует IP за вас; Smart AI Proxy, вариант с прямым подключением, если вы направляете собственный трафик.
Часто задаваемые вопросы
Легально ли парсить LinkedIn?
Зависит от того, что вы парсите и как используете данные. Это руководство строго ограничено публичными, неаутентифицированными вакансиями и избегает личных профилей, контента за входом в систему и обхода аутентификации. Даже здесь применяются Пользовательское соглашение, robots.txt и законы о конфиденциальности LinkedIn. Дело hiQ против LinkedIn рассматривало доступ к публичным данным в целом, но оно не является безоговорочным разрешением. Сначала прочитайте условия LinkedIn и ограничьте область публичными данными.
Почему обычный запрос не возвращает данные о вакансиях с LinkedIn?
Потому что LinkedIn рендерит объявления на стороне клиента с помощью JavaScript. Исходный HTML является оболочкой, заполняющейся только после выполнения скриптов страницы в браузере, поэтому обычный HTTP-запрос возвращает статус 200 с пустыми полями вакансий. Для получения реальных данных необходимо сначала отрендерить страницу, именно это JS-токен Crawling API делает за вас.
Нужен ли обычный токен или JS-токен для LinkedIn?
JS-токен. Обычный токен загружает статический HTML, который в LinkedIn представляет ту же пустую оболочку, что и обычный запрос. JS-токен рендерит страницу в реальном браузере перед возвратом HTML, так что объявления присутствуют при их парсинге BeautifulSoup.
Мои селекторы возвращают пустые строки. Что изменилось?
Почти наверняка разметка LinkedIn. Его имена классов меняются без предупреждения, поэтому селекторы, работавшие в прошлом месяце, могут сломаться. Заново проверьте живую публичную страницу поиска вакансий в инструментах разработчика браузера и обновите селекторы. Периодическое обслуживание селекторов нормально для любого производственного скрапера.
Как избежать блокировки при парсинге LinkedIn?
Поддерживайте низкую частоту запросов с одного IP, варьируйте ключевые слова и местоположение вместо повторения одного URL, и используйте ротацию резидентных IP, чтобы ни один адрес не превысил лимит. Crawling API управляет ротацией и пулом доверенных IP за вас; если вы строите собственный стек, Smart AI Proxy предоставляет эту ротацию в качестве прокси-эндпоинта. Следите за кодами статуса и снижайте темп при появлении проверок.
Можно ли парсить профили или сообщения LinkedIn с помощью этого?
Нет, и не следует пробовать. Это руководство намеренно ограничено публичными вакансиями. Личные профили, данные о связях и сообщения находятся за аутентификацией и содержат персональные данные, что выходит за рамки руководства. Если вашему проекту нужно больше, чем публичные вакансии, обращайтесь за официальным партнёрством или соглашением об обмене данными с LinkedIn, а не парсите аутентифицированные страницы.
Обходите любой сайт в масштабе, без борьбы с инфраструктурой.
Crawlbase берёт на себя прокси, отпечатки и CAPTCHA, чтобы ваша команда выпускала конвейеры данных вместо поддержки обвязки краулинга. 1 000 запросов бесплатно, без карты.
