Бот-парсер маркетплейса на Python

от автора

Всем привет! В этой статье я решил показать один из методов парсинга на Python на примере маркетплейса Wildberries.

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

В проекте будут использоваться следующие библиотеки:

  1. requests — для парсинга данных API.

  2. aiogram 3.10.0 — одна из самых популярных библиотек для разработки telegram ботов.

Работа проекта

Приложение будет отправлять GET запрос к API, получая на выходе данные о карточках товара. Далее мы отфильтруем требуемые данные, такие как название бренда, название товара и т.п., после чего «завернем» приложение в telegram-бота. И естественно не забудем про деплой.

Wildberries, как и другие крупные сервисы могут блокировать IP бота-парсера, поэтому я предлагаю использовать proxies.

Разбор API

Как было уточнено выше — wildberries использует API для получения интересующей нам информации на странице. Давайте перейдем на сайт и откроем любую категорию. Пусть это будет Электроника -> Гарнитура и наушники.

Теперь клавишей откроем инструмент разработчика, переключимся во вкладку network для вывода запросов, которые отправляются сайтом и выберем фильтр Fetch/XHR. Перезагрузим страницу.

Здесь нас интересует запрос, начинающийся на catalog?. Кликаем на него, и, изучая содержимое во вкладке Preview, можем перейти по ключу data — products и увидеть содержащиеся внутри данные продуктов!

Теперь мы знаем как выглядит запрос и можем перейти к написанию кода, так как совсем скоро нам понадобятся данные из DevTools.

Пишем основную логику проекта

Сначала импортируем необходимую библиотеку — requests, объявим переменную для прокси и зададим структуру main.py.

import requests  proxies = 'ЗАПИШИТЕ-СЮДА-СВОЙ-ПРОКСИ-В-НУЖНОМ-ФОРМАТЕ'  def get_category(): pass   def format_items(response):     pass              def main():     pass  if __name__ == '__main__':     main() 

Как вы видите, будет использоваться 3 основных функции:

  • В get_category() мы укажем url и headers, которые мы получим, скопировав curl (bash) запроса через DevTools. И вернем response запроса GET в json:

def get_category():     url = 'https://catalog.wb.ru/catalog/electronic14/v2/catalog?ab_testing=false&appType=1&cat=9468&curr=rub&dest=-1185367&sort=popular&spp=30'          headers = {         'Accept': '*/*',         'Accept-Language': 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',         'Connection': 'keep-alive',         'DNT': '1',         'Origin': 'https://www.wildberries.ru',          'Referer': 'https://www.wildberries.ru/catalog/elektronika/igry-i-razvlecheniya/aksessuary/garnitury',         'Sec-Fetch-Dest': 'empty',         'Sec-Fetch-Mode': 'cors',         'Sec-Fetch-Site': 'cross-site',         'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',         'sec-ch-ua': '"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"',         'sec-ch-ua-mobile': '?0',         'sec-ch-ua-platform': '"Windows"',     }          response = requests.get(url=url, headers=headers, proxies=proxies)          return response.json()
  • format_items() — принимает response запроса. Здесь с помощью цикла for пробежимся по данным товаров, предварительно сделав проверки на наличие самих товаров, и запишем все в products:

def format_items(response):     products = []          products_raw = response.get('data', {}).get('products', None)          if products_raw != None and len(products_raw) > 0:         for product in products_raw:             print(product.get('name', None))             products.append({                 'brand': product.get('brand', None),                 'name': product.get('name', None),                 'id': product.get('id', None),                 'reviewRating': product.get('reviewRating', None),                 'feedbacks': product.get('feedbacks', None),             })                               return products

Получаем products_raw (все продукты) с помощью метода get в response. Нам нужно дойти до products, который мы видели в инструменте разработчика в браузере. Для этого сначала получаем содержимое data, а после — самого products.

Что за метод GET и как с ним работать? Сначала берется любой ключ, в нашем случае — это product. И в методе get, первым параметром указывается название следующего ключа/переменной, содержащие в себе какое-то значение. Вторым параметром указывается то, что будет возвращено, если такого ключа/переменной не найдется. Если найдено — возвращается словарь с содержащимися внутри ключа/переменной данными. С помощью append мы записываем словарь с данными в массив products, объявленный в начале функции.

  • в main() вызовем функции и попробуем вывести данные карточек:

def main():     response = get_category()     products = format_items(response)          print(products)

Теперь можно запустить. На выходе успешно получаем выделенные данные!

Адаптируем код для работы бота

Когда логика парсера готова, мы можем написать бота. Он будет простым — бот не будет давать пользователю выбрать категорию, а просто отправит 10 карточек по выбранной нами категории.

Добавим нужные импорты:

import os import asyncio import time  import logging  from aiogram import Bot, Dispatcher, types from aiogram.filters import CommandStart from aiogram.enums.parse_mode import ParseMode from aiogram.types.inline_keyboard_button import InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder

Запустим logging и объявим класс бота с диспетчером и переменную прокси:

logging.basicConfig(level=logging.INFO)  proxies = os.getenv('PROXIES')  bot = Bot(os.getenv("TOKEN")) dp = Dispatcher()

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

Теперь немного изменим main(), сделаем его асинхронным и вместо обычного вызова переменной main, используем asyncio для запуска асинхронных функций:

async def main():     await bot.delete_webhook(drop_pending_updates=True)     await dp.start_polling(bot)      if __name__ == '__main__':     asyncio.run(main())

И самое главное: обработчик команды /start:

@dp.message(CommandStart) async def start(message: types.Message):     response = get_category()     products = format_items(response)          items = 0          for product in products:         text=f"<b>Категория</b>: Гарнитуры и наушники\n\n<b>Название</b>: {product['name']}\n<b>Бренд</b>: {product['brand']}\n\n<b>Отзывов всего</b>: {product['feedbacks']}\n<b>Средняя оценка</b>: {product['reviewRating']}"                  builder = InlineKeyboardBuilder()         builder.add(InlineKeyboardButton(text="Открыть", url=f"https://www.wildberries.ru/catalog/{product['id']}/detail.aspx"))                  await message.answer(text, parse_mode=ParseMode.HTML, reply_markup=builder.as_markup())                  if items >= 10:             break         items += 1                  time.sleep(0.3)

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

Бот отправляет до 10 самых популярных товаров (фильтр можно задать в параметрах url в первой функции) с перерывами в 0.3 секунды, чтобы API telegram не жаловался на слишком частые отправки сообщений.

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

Запускаем, и при команде старт видим, что все работает!

Весь код выглядит так:

import requests import os import asyncio import time  import logging  from aiogram import Bot, Dispatcher, types from aiogram.filters import CommandStart from aiogram.enums.parse_mode import ParseMode from aiogram.types.inline_keyboard_button import InlineKeyboardButton from aiogram.utils.keyboard import InlineKeyboardBuilder  logging.basicConfig(level=logging.INFO)  proxies = os.getenv('PROXIES')  bot = Bot(os.getenv("TOKEN")) dp = Dispatcher()  def get_category():     url = 'https://catalog.wb.ru/catalog/electronic14/v2/catalog?ab_testing=false&appType=1&cat=9468&curr=rub&dest=-1185367&sort=popular&spp=30'          headers = {         'Accept': '*/*',         'Accept-Language': 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',         'Connection': 'keep-alive',         'DNT': '1',         'Origin': 'https://www.wildberries.ru',          'Referer': 'https://www.wildberries.ru/catalog/elektronika/igry-i-razvlecheniya/aksessuary/garnitury',         'Sec-Fetch-Dest': 'empty',         'Sec-Fetch-Mode': 'cors',         'Sec-Fetch-Site': 'cross-site',         'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',         'sec-ch-ua': '"Not)A;Brand";v="99", "Google Chrome";v="127", "Chromium";v="127"',         'sec-ch-ua-mobile': '?0',         'sec-ch-ua-platform': '"Windows"',     }          response = requests.get(url=url, headers=headers, proxies=proxies)          return response.json()  def format_items(response):     products = []          products_raw = response.get('data', {}).get('products', None)          if products_raw != None and len(products_raw) > 0:         for product in products_raw:             products.append({                 'brand': product.get('brand', None),                 'name': product.get('name', None),                 'id': product.get('id', None),                 'reviewRating': product.get('reviewRating', None),                 'feedbacks': product.get('feedbacks', None),             })                  return products              @dp.message(CommandStart) async def start(message: types.Message):     response = get_category()     products = format_items(response)          items = 0          for product in products:         text=f"<b>Категория</b>: Гарнитуры и наушники\n\n<b>Название</b>: {product['name']}\n<b>Бренд</b>: {product['brand']}\n\n<b>Отзывов всего</b>: {product['feedbacks']}\n<b>Средняя оценка</b>: {product['reviewRating']}"                  builder = InlineKeyboardBuilder()         builder.add(InlineKeyboardButton(text="Открыть", url=f"https://www.wildberries.ru/catalog/{product['id']}/detail.aspx"))                  await message.answer(text, parse_mode=ParseMode.HTML, reply_markup=builder.as_markup())                  if items >= 10:             break         items += 1                  time.sleep(0.3)              async def main():     await bot.delete_webhook(drop_pending_updates=True)     await dp.start_polling(bot)      if __name__ == '__main__':     asyncio.run(main())

Деплой бота в Amvera

Разворачивать нашего бота мы будем в сервисе Amvera.

Amvera — одно из лучший решений для быстрого деплоя ботов из-за своей простоты в загрузке файлов (через git или интерфейс) и простоты настройки (вам не нужно настраивать виртуальную машину, зависимости и т.д.). Единственное, что надо настроить — переменные окружения, задать параметры в конфигурации и создать файл зависимостей (requirements.txt). Хоть это и звучит страшно, но на деле делается в пару кликов.

В Amvera вы сможете быстро обновлять код через git (консольную утилиту или интерфейс IDE) буквально за 3 команды.

Еще одна из интересных особенностей Amvera — то, что деньги не будут сгорать, если проект не работает из-за почасовой тарификации. Если ваше приложение будет работать час — спишется за час. 20 минут — спишется за 20 минут и так далее. Цена в тарифе указана, если приложение будет работать весь месяц без остановок.

Для начала зарегистрируемся по ссылке. После подтверждения номера телефона и почты, вам начисляется 111 рублей на баланс.

Открываем страницу проектов и нажимаем кнопку “Создать”. В открывшемся окне прописываем название проекта на латинице или кириллице, выбираем понравившийся тариф, тип сервиса оставляем “Приложение”.

Нажимаем далее. Теперь доступно окно загрузки данных. Можно загрузить сейчас через интерфейс, а можно через Git. Я рекомендую использовать Git, если в будущем будут обновления проекта.

Выбор не важен — вы в любом случае сможете пользоваться и интерфейсом и Git.

Нажимаем далее и нас встречает окно с конфигурацией. Это и есть инструкции для запуска проекта. Все просто — Выбираем окружение Python, инструмент pip, указываем версию python, название запускаемого скрипта и все. Больше нам ничего настраивать не нужно.

Завершаем настройку проекта и теперь нам остается только создать файл зависимостей и загрузить данные.

Файл зависимостей создается просто — нужно указать название библиотеки и ее версию в формате

библиотека1==версия1 библиотека2==версия

В нашем случае requirements.txt

aiogram==3.10.0 requests==2.32.3

Финальная настройка и доставка кода в репозиторий (GIT)

Перед загрузкой файлов нужно настроить переменные окружения. Нужно зайти на страницу проекта и перейти во вкладку “Переменные”, где создать секреты с указанным в коде названием и нужным значением.

Мы готовы к загрузке кода! Если хотите — можно быстро загрузить через интерфейс сайта во вкладке “Репозиторий”, но я воспользуюсь git.

Я покажу последовательность команд для первого коммита и отправки (пуша) файлов:

  1. git init — инициализирует git локально

  2. git remote add amvera https://git.amvera.ru/Ваш_Ник/Имя_проекта — команда для подключения к репозиторию Amvera. Команду можно найти во вкладке “Репозиторий”

  3. git add . — добавляет все файлы в локальном репозитории

  4. git commit -m "Комментарий" — первый коммит

  5. git push amvera master — пуш в репозиторий.

Иногда могут возникнуть проблемы. Здесь собраны возможные ошибки и решения, связанные с Git.

При пуше автоматически начинается сборка. Если вы загрузили через интерфейс — перейдите во вкладку “Конфигурация” и нажмите кнопку “Собрать”. Важно именно собирать проект при обновлении кода/конфигурации.

Итог

Когда приложение собралось — вам осталось дождаться запуска бота и проверить его работоспособность.

Cегодня мы научились парсить страницу, а точнее использовать для этого API маркетплейса, получая данные о карточках, добавили функционал в бота и научились развертывать минимальное приложение в Amvera.


ссылка на оригинал статьи https://habr.com/ru/articles/834582/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *