Парсер товаров Авито написан с учетом рефакторинга кода, то есть код разбит на части, каждая из которых отвечает за определенную функцию (что такое рефакторинг и как его реализовать, можно почитать тут)

Код проекта на Github

1. Импорт библиотек.

И так, начнем написание нашего парсера с импорта всех нужных нам библиотек:

import random
import re
import time
import pandas
import requests
from bs4 import BeautifulSoup
from pandas import ExcelWriter

Библиотеки random и time нам пригодятся для выставления задержки парсера между страницами, что бы не нагружать сайт c большим количеством запросов за единицу времени

re — библиотека регулярных выражений, пригодится для поиска по частичному совпадению текста, т.к. код периодически изменяется

2. Функция get_html(url, params=None)

Итак первая функция, это функция получения кода страницы get_html в нее будем передавать нашу ссылку url и параметры params (по умолчанию None)

def get_html(url, params=None):
    """ получение кода страницы """
    headers = {
        "Accept": "*/*",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0)"
    }
    html = requests.get(url, headers=headers, params=params)
    return html

Добавим свои headers — наши заголовки (где брать headers?) что бы сайт не заподозрил нас, как вредоносный код.

3. Функция get_pages(html)

Вторая функция get_pages, которая возвращает нам количество страниц выдачи поиска.

Для этого найдем крайнюю ссылку «След. →» по тегу span и по значению параметра datamarker, воспользуемся функцией beautifulsoup‘а privious_element , который находит предыдущий элемент найденного тэга, который является искомым. (Рисунок 1)

Принцип такой: находим «След. →» и берем предыдущий элемент, если «След. →» не найден, то присваиваем значение 1

def get_pages(html):
    """ получаем количество страниц """
    soup = BeautifulSoup(html.text, 'html.parser')
    # находим кол-во страниц, иначе количество страниц равно 1
    try:
        pages = soup.find('span', {'data-marker': 'pagination-button/next'}).previous_element
    except:
        pages = 1
    print('Количество найденных страниц: ', pages)
    return pages
Рисунок 1. Пагинация страницы выдачи поиска. Тэги выделены красным
Рисунок 1. Пагинация страницы выдачи поиска. Тэги выделены красным

4. Функция get_content(html)

Теперь пишем функцию сбора данных со страницы get_content, во всех поисках нужных тэгов я применил библиотеку re, т.к. классы тэгов периодически изменяются на сайте, что приводит к выводу из строя нашего парсера. (Рисунок 2)

Рисунок 2. Пример тэга, где изменяется часть класса
Рисунок 2. Пример тэга, где изменяется часть класса

Находим все блоки карточек с помощью функции BeautifulSoup’а find_all с тэгом div и классом iva-item-content. Заметьте, я беру только часть класса, не включив в нее UnQQ4 т.к. эта часть периодически изменяется (Рисунок 3):

blocks = soup.find_all('div', class_=re.compile('iva-item-content'))
Рисунок 3. тэг и класс карточки товара
Рисунок 3. тэг и класс карточки товара

Далее пробегаемся по всем карточкам товаров (blocks) и собираем с них нужные нам данные по тому же принципу, занося полученные данные в список (data). Функция полностью:

def get_content(html):
    """ сбор контента со страницы """
    soup = BeautifulSoup(html.text, 'lxml')
    blocks = soup.find_all('div', class_=re.compile('iva-item-content'))
    # сбор данных с страницы
    data = []
    for block in blocks:
        data.append({
            "Наименование": block.find('h3', class_=re.compile('title-root')).get_text(strip=True),
            'Цена': block.find('span', class_=re.compile('price-text')).get_text(strip=True).replace('₽', '').replace('\xa0', ''),
            'Город': block.find('a', class_=re.compile('link-link')).get('href').split('/')[1],
            'Район': block.find('div', class_=re.compile('geo-root')).get_text(strip=True),
            'Ссылка': url + block.find('a', class_=re.compile('link-link')).get('href'),
        })
    return data

5. Функция save_excel(data, file_name)

Функция save_excel получает собранные данные data и file_name это название под которым будем сохранять наш файл excel. Сохранение реализовано, через библиотеку pandas.

# Создали фрейм собранных данных:

df_data = pandas.DataFrame(data)
# Далее чистим данные от дубликатов

data_clear = df_data.drop_duplicates('Ссылка')
# и само сохранение в файл .xlsx

writer = ExcelWriter(f'{file_name}.xlsx')
data_clear.to_excel(writer, f'{file_name}')
writer.save()

Функция полностью:

def save_excel(data, file_name):
    # сохраняем полученные данные в эксель через pandas
    df_data = pandas.DataFrame(data)
    print(f'До удаления дубликатов: {len(df_data)} записей')
    # чистим дубликаты записей (проплаченные посты дублируются на разных страницах)
    data_clear = df_data.drop_duplicates('Ссылка')
    print(f'После удаления дубликатов: {len(data_clear)} записей')
    writer = ExcelWriter(f'{file_name}.xlsx')
    data_clear.to_excel(writer, f'{file_name}')
    writer.save()
    print(f'Данные сохранены в файл "{file_name}.xlsx"')

6. Функция parse(url) (основная функция Авито парсера)

Основная функция выполняющая все предыдущие + пользовательский ввод.

От пользователя принимаем запрос поиска search, минимальную/максимальную цену товара, для уменьшения выдачи поиска:

search = input('Введите запрос поиска: ')
min_price = input('Введите минимальную стоимость: ')
max_price = input('Введите максимальную стоимость: ')

Отправляем запрос с заданными параметрами:

    html = get_html(url, params={'bt': 1, 'pmax': max_price, 'pmin': min_price, 'q': search, 's': '2', 'view': 'gallery'})

создаем на его основе объект BeautifulSoup и вытаскиваем заголовок

soup = BeautifulSoup(html.text, 'lxml')
print(soup.h1.get_text())

Далее сделаем проверку на ответ от сайта, если доступ к данным есть, т.е. ответ от сервера равен 200, то выполняем блок кода, если сервер нас ограничил в данных, или данные по нашему запросу не найдены, выведем ошибку и завершим программу.

Если статус код с сайта равен 200, то определяем количество страниц выдачи и спрашиваем у пользователя сколько страниц спарсить:

get_pages(html)
pagination = int(input('Сколько страниц спарсить? '))

Далее пробегаемся по заданным пользователем страницам собирая с них данные и расширяя ими список data

for page in range(1, pagination + 1):
            html = get_html(url, params={'bt': 1, 'p': page, 'pmax': max_price, 'pmin': min_price, 'q': search, 's': '2', 'view': 'gallery'})
            print(f'Парсинг страницы {page} из {pagination}...')
            data.extend((get_content(html)))

Функция полностью:

def parse(url):
    search = input('Введите запрос поиска: ')
    min_price = input('Введите минимальную стоимость: ')
    max_price = input('Введите максимальную стоимость: ')
    html = get_html(url, params={'bt': 1, 'pmax': max_price, 'pmin': min_price, 'q': search, 's': '2', 'view': 'gallery'})
    soup = BeautifulSoup(html.text, 'lxml')
    print(soup.h1.get_text())
    print('Ссылка со всеми параметрами:\n', html.url)
    print('Статус код сайта: ', html.status_code)
    data = []
    # проверка сайта на доступ
    if html.status_code == 200:
        # вызов функции для вывода количества найденных страниц
        get_pages(html)
        pagination = int(input('Сколько страниц спарсить? '))
        for page in range(1, pagination + 1):
            html = get_html(url, params={'bt': 1, 'p': page, 'pmax': max_price, 'pmin': min_price, 'q': search, 's': '2', 'view': 'gallery'})
            print(f'Парсинг страницы {page} из {pagination}...')
            data.extend((get_content(html)))
            time.sleep(random.randint(1, 3))
        print(f'Получили {len(data)} позиций')
        # сохраняем в эксель, передав наши собранные данные и запрос
        save_excel(data, search)
    else:
        print('Ошибка доступа к сайту')

7. Запуск основной функции Авито парсера:

if __name__ == "__main__":
     parse(url)

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

Читать далее: Авито. Cбор данных товаров используя Python