Урок 1. Пишем парсер каталога товаров на Python

Опубликовано: 3 ноября в 22:51


Всем привет! В данном уроке мы займемся разработкой парсера каталога товаров.

Код будем писать на языке программирования Python в среде разработки Python IDLE. В рамках данной статьи не рассматривается установка и настройка последних. Для получения подробной информации вы можете посетить официальный сайт Python.

Собирать данные о товарах будем со специального сервиса на нашем сайте – тестового каталога, где к каждому полю подписан CSS-селектор, по которому можно найти элемент на странице – это нам пригодится в процессе парсинга. Сохранять результат будем в файл формата JSON.

Библиотеки, используемые в данной статье:
1. requestsофициальную страница.
2. beautifulsoup4ссылка на документацию.
3. json – является частью стандартной библиотеки python.

По любым возникающим в ходе урока вопросам оставляйте комментарии ниже.
Ссылка на полный текст исходного кода находится в конце статьи.

Шаг 1. Подготовка

Предварительно проанализируем наш каталог товаров: видим 10 страниц, 117 товаров. Каждая карточка содержит поля: название, цена, артикул, таблица характеристик, изображение и описание. Выберем для парсинга название, цену и характеристики.

Алгоритм на поверхности:
1. Пройтись по страницам с 1 по 10 и собрать ссылки на страницы с товарами.
2. Зайти на страницу каждого из товаров и спарсить интересующие нас данные.
3. Сохранить полученные данные в файл формата JSON.

Шаг 2. Установка зависимостей

Использовать мы будем две сторонние библиотеки:
1. requests – для загрузки HTML-кода страниц по URL.
2. beautifulsoup4 – для парсинга данных с HTML, поиска элементов на странице.

Установим зависимости командами:

pip install requests
pip install beautifulsoup4

Также мы будем использовать стандартную библиотеку json для записи результата в файл JSON формата.

Шаг 3. Программирование

Приступим к написанию кода – создадим основной скрипт main.py.

Объявим функцию crawl_products, которая будет принимать на вход количество страниц с карточками товаров, а на выходе возвращать список ссылок на товары с этих страниц.

Объявим вторую функцию parse_products, которая принимает список URL-адресов, парсит необходимую информацию по каждому товару и добавляет её в общий массив data, который и возвращает

Нам известно, что всего 10 страниц с карточками товаров, объявим глобальную переменную PAGES_COUNT – количество страниц.

# -*- coding: utf-8 -*-

PAGES_COUNT = 10


def crawl_products(pages_count):
    urls = []
    return urls


def parse_products(urls):
    data = []
    return data


def main():
    urls = crawl_products(PAGES_COUNT)
    data = parse_products(urls)


if __name__ == '__main__':
    main()

Для парсинга страницы нам необходимо получить HTML-код страницы с помощью библиотеки requests и создать объект BeautifulSoup на основе этого кода.

Создадим метод get_soup, который принимает на вход URL-адрес страницы и возвращает объект BeautifulSoup, который мы будем использовать для поиска элементов на странице по CSS-селекторам. Если не удалось загрузить страницу по переданному URL, метод возвращает None.

def get_soup(url, **kwargs):
    response = requests.get(url, **kwargs)
    if response.status_code == 200:
        soup = BeautifulSoup(response.text, features='html.parser')
    else:
        soup = None
    return soup

Приступим к реализации функции crawl_products. Смотрим, как формируется URL-адрес конкретной страницы, на примере страницы с номером 2:

https://parsemachine.com/sandbox/catalog/?page=2

Видим GET-параметр page=2, который является номером страницы. Объявим переменную fmt, которая будет шаблоном URL-адреса страницы с товарами. Пройдемся по страницам с 1 по pages_count.

Cформируем URL-адрес страницы с номером page_n и получим для него объект BeautifulSoup, используя метод get_soup. Если soup страницы является None, то произошла ошибка при получении HTML-кода страницы, поэтому мы прерываем обработку с помощью оператора break.

Если soup получен, то нас интересует элемент «Название товара», который является ссылкой на товар. Копируем CSS-селектор .product-card .title и передаем его аргументов в метод soup.select(), который вернет множество найденных на странице элементов. Для каждого из них мы получим атрибут href, а затем сформируем URL и добавим его в общий массив. Ниже приведен полный текст данной функции.

def crawl_products(pages_count):
    urls = []
    fmt = 'https://parsemachine.com/sandbox/catalog/?page={page}'

    for page_n in range(1, 1 + pages_count):
        print('page: {}'.format(page_n))

        page_url = fmt.format(page=page_n)
        soup = get_soup(page_url)
        if soup is None:
            break

        for tag in soup.select('.product-card .title'):
            href = tag.attrs['href']
            url = 'https://parsemachine.com{}'.format(href)
            urls.append(url)

    return urls

Переходим к реализации parse_products. Пройдемся по каждому товару и получим soup аналогично предыдущей функции.

Первое поле, которое мы парсим – «Название товара». Вызываем метод select_one у объекта soup, который аналогичен методу select за исключением, что возвращает один элемент, найденный на странице. В качестве аргумента передаем CSS-селектор #product_name. Получаем видимый текст и очищаем от пробельных символов с обеих сторон.

Аналогично получаем поле «Цена» по CSS-селектору #product_amount.

Переходим к парсингу характеристик товара. Найдем строки по CSS-селектору #characteristics tbody tr и для каждой из них получим значения ячеек, которые сохраним в переменную techs. Данные по товару мы спарсили, теперь сохраним их в переменую item и добавим в массив data. Ниже представлен полный код функции.

def parse_products(urls):
    data = []

    for url in urls:
        print('product: {}'.format(url))

        soup = get_soup(url)
        if soup is None:
            break

        name = soup.select_one('#product_name').text.strip()
        amount = soup.select_one('#product_amount').text.strip()
        techs = {}
        for row in soup.select('#characteristics tbody tr'):
            cols = row.select('td')
            cols = [c.text.strip() for c in cols]
            techs[cols[0]] = cols[1]

        item = {
            'name': name,
            'amount': amount,
            'techs': techs,
        }
        data.append(item)

    return data

Осталось добавить запись в файл и парсер будет готов!

Импортируем библиотеку json для записи данных в JSON-файл. Объявляем глобальную переменную OUT_FILENAME – имя файла для записи результата.

Переходим к сохранению. Вызовем метод json.dump() с передачей аргументов:
1. Сохраняемый объект – у нас это data.
2. Файл.
Также передадим ensure_ascii=False, чтобы символы кириллицы не экранировались при записи в файл. И зададим отступ в 1 символ indent=1.

Итог

Все, сохранение в файл готово, и разработка парсера завершена!

import json

...
OUT_FILENAME = 'out.json'

...
def main():
    urls = crawl_products(PAGES_COUNT)
    data = parse_products(urls)

    with open(OUT_FILENAME, 'w') as f:
        json.dump(data, f, ensure_ascii=False, indent=1)

Ссылка на полный текст исходного кода – catalog.py

Если у вас возникли какие-то вопросы, можете задать их в комментариях к этой статье или под видео на YouTube. Предлагайте темы следующих уроков.

Спасибо за внимание!

Обсудить в Telegram