Веб-парсинг - это автоматический сбор данных с веб-сайтов. Его используют для анализа рынка, сбора вакансий, мониторинга цен, исследований и создания собственных сервисов.
Python считается одним из лучших языков для веб-скрапинга благодаря простоте и большому количеству библиотек. Одна из самых популярных python-библиотек это BeautifulSoup. Она позволяет удобно разбирать HTML-страницы и извлекать из них нужные данные.
В этой статье мы затронем следующие аспекты:
как работает веб-скрапинг
как читать HTML-структуру сайта
как использовать BeautifulSoup
как на практике собрать вакансии с сайта hirify.me
Как работает веб-парсинг
На базовом уровне процесс выглядит так:
Скрипт отправляет HTTP-запрос на сайт.
Сервер возвращает HTML-страницу.
Python разбирает HTML.
Мы находим нужные элементы и извлекаем данные.
Важно понимать, что BeautifulSoup не запускает JavaScript. Он работает только с тем HTML, который приходит от сервера. Именно поэтому сайты с простой и стабильной разметкой, отлично подходят для обучения и практики.
Структура HTML и дерево документа
Любая веб-страница - это HTML-документ с вложенными элементами. Упрощенно HTML-дерево выглядит так:
html
├── head
│ └── title
└── body
├── h1
└── div
├── p
└── a
Каждый элемент может содержать другие элементы. BeautifulSoup позволяет легко перемещаться по этому дереву и находить нужные теги.
Установка необходимых библиотек
Для примеров в статье понадобятся две библиотеки:
requests - для выполнения HTTP-запросов
beautifulsoup4 - для парсинга HTML
Установка:
pip install requests beautifulsoup4Первый запрос к сайту
Начнем с простого запроса и загрузки HTML:
import requests
from bs4 import BeautifulSoup
url = "https://hirify.me/jobs"
headers = {
"User-Agent": "Mozilla/5.0"
}
response = requests.get(url, headers=headers)
html = response.text
soup = BeautifulSoup(html, "html.parser")Теперь переменная soup содержит разобранное HTML-дерево страницы.
Анализ реальной структуры вакансии hirify.me
Каждая вакансия на странице оформлена как карточка. Ее корневой элемент выглядит так:
<div class="relative flex flex-col shadow-sm hover:shadow-md vacancy-card island">
<a href="/jobs/297259-brand-creative-designer" class="vacancy-card-link">
...
</a>
</div>Из этой карточки можно извлечь:
ссылку на вакансию
название вакансии
компанию
тип занятости и формат работы
список навыков
дату публикации
Название вакансии
Название находится внутри тега h3 с классом title:
<h3 class="font-bold text-[22px] title">
Brand Creative Designer
</h3>Компания
Название компании:
<div class="company">
topfoxx
</div>Теги формата работы
Формат и уровень находятся в блоке с классом common-tags:
<div class="tag">remote</div>
<div class="tag">fulltime</div>
<div class="tag">senior,lead</div>Навыки
Навыки находятся в блоке vacancy-tags и представлены тегами div.tag:
<div class="vacancy-tags">
<div class="tag">photoshop</div>
<div class="tag">marketing</div>
<div class="tag">illustrator</div>
</div>Поиск карточек вакансий
Сначала найдем все карточки вакансий:
vacancies = soup.find_all(
"div",
class_="vacancy-card"
)Мы используем только стабильный класс vacancy-card, игнорируя utility-классы Tailwind.
Полный пример парсинга вакансий
Ниже приведен полноценный пример парсинга с пагинацией на основе реальной структуры HTML.
import requests
from bs4 import BeautifulSoup
import csv
import time
base_url = "https://hirify.me/jobs"
headers = {
"User-Agent": "Mozilla/5.0"
}
all_jobs = []
page = 1
while True:
if page == 1:
url = base_url
else:
url = f"{base_url}?page={page}"
response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.text, "html.parser")
vacancies = soup.find_all("div", class_="vacancy-card")
if not vacancies:
break
for vacancy in vacancies:
link_tag = vacancy.find("a", class_="vacancy-card-link")
link = "https://hirify.me" + link_tag["href"] if link_tag else ""
title_tag = vacancy.find("h3", class_="title")
title = title_tag.get_text(strip=True) if title_tag else ""
company_tag = vacancy.find("div", class_="company")
company = company_tag.get_text(strip=True) if company_tag else ""
meta = []
for block in vacancy.find_all("div", class_="common-tags"):
meta.extend([
tag.get_text(strip=True)
for tag in block.find_all("div", class_="tag")
])
skills_block = vacancy.find("div", class_="vacancy-tags")
skills = []
if skills_block:
skills = [
tag.get_text(strip=True)
for tag in skills_block.find_all("div", class_="tag")
if "more-tags" not in tag.get("class", [])
]
all_jobs.append({
"title": title,
"company": company,
"meta": ", ".join(meta),
"skills": ", ".join(skills),
"link": link
})
page += 1
time.sleep(1)Сохранение результата в CSV
После завершения пагинации можно сохранить все вакансии в csv-файл:
with open("hirify_jobs.csv", "w", newline="", encoding="utf-8") as file:
writer = csv.writer(file)
writer.writerow([
"Название",
"Компания",
"Формат",
"Навыки",
"Ссылка"
])
for job in all_jobs:
writer.writerow([
job["title"],
job["company"],
job["meta"],
job["skills"],
job["link"]
])BeautifulSoup отлично подходит для парсинга сайтов с простой и предсказуемой HTML-структурой и hirify.me - хороший пример такого сайта, у которого серверная отрисовка, понятные классы и логичная структура карточек.