DEV Community

Excalibra
Excalibra

Posted on

1

[Python Оригинал] Создал демо веб-скрейпера для сайта вакансий

Анализ Требований

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

Основные Проблемы и Решения

Меры Против Скрапинга

hh.ru внедряет различные меры против скрапинга, чтобы ограничить боты или скрапинг. Некоторые из них включают:

  • Ограничение Запросов: hh.ru накладывает временные ограничения на частоту запросов. После нескольких быстрых запросов может быть наложено ограничение. Это может привести к задержкам между запросами, обычно от нескольких минут до часа, или даже блокировке, если частота слишком высокая.

  • Ограничение на Пагинацию: Нет строгого лимита на количество страниц, которые можно скрапить. Однако, на практике, скрапинг нескольких страниц (например, 3-5) является типичным, чтобы избежать срабатывания мер защиты от скрапинга. Каждая страница обычно содержит до 30 вакансий, поэтому 3-5 страниц обычно достаточно для сбора приличного объема данных.

  • Куки и Заголовки: Если вы вставляете куки для вашей сессии, убедитесь, что не нажимаете "Enter" после вставки. Удаление последней пары ключ-значение может помочь обойти проблемы, которые могут возникнуть из-за недействительных куки.


Пояснение Ключевых Моментов

API Вакансий

hh.ru предоставляет API, которое можно использовать для поиска вакансий по ключевым словам (например, "IT", "Кибербезопасность" и т. д.) и местоположению. API не накладывает строгих ограничений на количество страниц, однако слишком быстрое скрапирование многих страниц может привести к блокировке или CAPTCHам.

API Вакансий:

https://api.hh.ru/vacancies
Enter fullscreen mode Exit fullscreen mode

Анализ Ссылки на Вакансии:

https://hh.ru/search/vacancy?text=IT+%D1%82%D0%B5%D1%85%D0%BD%D0%BE%D0%BB%D0%BE%D0%B3%D0%B8%D0%B8&area=1&experience=between_1_and_3&salary=100000&items_on_page=20
Enter fullscreen mode Exit fullscreen mode

Анализ Параметров:

keywords = "разработчик, кибербезопасность, IT"
city_name = "Москва"  # Можно указать 'Москва' или 'Москва и МО'
Enter fullscreen mode Exit fullscreen mode

Код Города:

city_code = get_citycode(city_name)
Enter fullscreen mode Exit fullscreen mode
def get_citycode(city_name):
    response = requests.get('https://api.hh.ru/areas')
    city_data = response.json()

    # Ищем в каждом регионе города
    for region in city_data:
        # Если город найден в регионе, возвращаем его ID
        for area in region.get('areas', []):
            if area['name'] == city_name:
                return area['id']
    return None
Enter fullscreen mode Exit fullscreen mode

Структура Конкатенации JSON API:

for region in city_data:
    for area in region.get('areas', []):
        if area['name'] == city_name:
            return area['id']
Enter fullscreen mode Exit fullscreen mode
for region in city_data:
    for area in region.get('areas', []):
        if area['name'] == city_name:
            return area['id']
Enter fullscreen mode Exit fullscreen mode
for job in job_listings:
    job_name = job['name']
    salary = job['salary']
    employer_name = job['employer']['name']
    area_name = job['area']['name']
    job_url = job['alternate_url']
Enter fullscreen mode Exit fullscreen mode

Цикл и Извлечение:

job_listings = get_job_list(keywords, city_name)

# Выводим информацию о найденных вакансиях
if job_listings:
    for job in job_listings:  # This is the loop that iterates through job listings
        job_name = job['name']
        salary = job['salary']
        employer_name = job['employer']['name']
        area_name = job['area']['name']
        job_url = job['alternate_url']  # URL вакансии

        # Зарплата (если указана)
        salary_from = salary.get('from', 'Не указано') if salary else 'Не указано'
        salary_to = salary.get('to', 'Не указано') if salary else 'Не указано'
        salary_currency = salary.get('currency', 'Не указана') if salary else 'Не указана'

        # Другие дополнительные данные
        employment_type = job['employment']['name'] if 'employment' in job else 'Не указано'
        experience_required = job['experience']['name'] if 'experience' in job else 'Не указано'
        skills = ', '.join(job['key_skills']) if 'key_skills' in job else 'Не указаны'

        # Выводим все найденные детали вакансии
        print(f"Вакансия: {job_name}")
        print(f"Зарплата: от {salary_from} до {salary_to} {salary_currency}")
        print(f"Компания: {employer_name}")
        print(f"Город: {area_name}")
        print(f"Тип занятости: {employment_type}")
        print(f"Требуемый опыт: {experience_required}")
        print(f"Навыки: {skills}")
        print(f"Ссылка на вакансию: {job_url}")
        print("-" * 40)
Enter fullscreen mode Exit fullscreen mode

Включённый Исходный Код:
https://github.com/Excalibra/scripts/blob/main/d-python/hh.ru_demo.py

import os
import requests
import json
from openpyxl import Workbook
from datetime import datetime

# Your existing get_citycode and get_job_list functions should already be defined in your script.
# Make sure they are present in the script or imported from another module.

# For example, let's assume this is part of your script:
def get_citycode(city_name):
    response = requests.get('https://api.hh.ru/areas')
    city_data = response.json()

    # Ищем в каждом регионе города
    for region in city_data:
        # Если город найден в регионе, возвращаем его ID
        for area in region.get('areas', []):
            if area['name'] == city_name:
                return area['id']
    return None

def get_job_list(keywords, city_name):
    city_code = get_citycode(city_name)
    if city_code is None:
        print(f"Город '{city_name}' не найден.")
        return []

    url = "https://api.hh.ru/vacancies"
    params = {
        'text': keywords,  # Ключевые слова для поиска вакансий
        'area': city_code,  # ID города
        'page': 0,  # Стартовая страница
        'per_page': 10,  # Количество вакансий на странице
    }

    response = requests.get(url, params=params)
    data = response.json()

    return data['items']

# Your DataToExcel class remains the same.

class DataToExcel:
    @staticmethod
    def save_to_excel(workbook, hhru_json):
        # Load the hh.ru JSON data
        with open(hhru_json, 'r', encoding='utf-8') as f:
            hhru_json = json.load(f)

        worksheet = workbook.create_sheet(title='Job Listings')

        # Define the column titles
        titles = [
            'Job Title', 'Skills', 'Experience', 'Salary From', 'Salary To', 'Currency', 
            'Company', 'Employment Type', 'Experience Required', 'City', 'Job URL'
        ]

        # Write the titles in the first row
        for col, title in enumerate(titles, start=1):
            worksheet.cell(row=1, column=col, value=title)

        # Extract job listings from the JSON data
        job_data_list = hhru_json.get('items', [])
        row = 2  # Start from the second row for data

        for job in job_data_list:
            worksheet.cell(row=row, column=1, value=job.get('name', 'N/A'))
            worksheet.cell(row=row, column=2, value=', '.join(job.get('key_skills', [])))
            worksheet.cell(row=row, column=3, value=job.get('experience', {}).get('name', 'N/A'))

            # Handle salary data
            salary = job.get('salary', {})
            salary_from = salary.get('from', 'N/A') if salary else 'N/A'
            salary_to = salary.get('to', 'N/A') if salary else 'N/A'
            salary_currency = salary.get('currency', 'N/A') if salary else 'N/A'

            worksheet.cell(row=row, column=4, value=salary_from)
            worksheet.cell(row=row, column=5, value=salary_to)
            worksheet.cell(row=row, column=6, value=salary_currency)

            worksheet.cell(row=row, column=7, value=job.get('employer', {}).get('name', 'N/A'))
            worksheet.cell(row=row, column=8, value=job.get('employment', {}).get('name', 'N/A'))
            worksheet.cell(row=row, column=9, value=job.get('experience', {}).get('name', 'N/A'))
            worksheet.cell(row=row, column=10, value=job.get('area', {}).get('name', 'N/A'))
            worksheet.cell(row=row, column=11, value=job.get('alternate_url', 'N/A'))

            row += 1  # Move to the next row

        # Save the workbook to a file
        save_xlsx = os.path.join(
            os.path.expanduser("~"), "Desktop", f"hhru_jobs_{datetime.now().strftime('%Y-%m-%d')}.xlsx"
        )
        workbook.save(save_xlsx)
        print(f"Job listings have been saved to: {save_xlsx}")


# Example function that saves the job data and calls the Excel export function
def save_hhru_data_to_excel():
    # Ensure get_job_list is available here.
    job_listings = get_job_list("разработчик, кибербезопасность, IT", "Москва")

    # Save the job listings to a JSON file
    hhru_json_path = os.path.join(os.path.expanduser("~"), "Desktop", f"hhru_jobs_{datetime.now().strftime('%Y-%m-%d')}.json")
    with open(hhru_json_path, 'w', encoding='utf-8') as f:
        json.dump({"items": job_listings}, f, ensure_ascii=False, indent=4)

    # Create a new workbook and save the data to Excel
    workbook = Workbook()
    workbook.remove(workbook.active)  # Remove the default sheet
    DataToExcel.save_to_excel(workbook, hhru_json_path)


# Call the function to save the job listings to Excel
save_hhru_data_to_excel()
Enter fullscreen mode Exit fullscreen mode

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay