Бот-парсер маркетплейса на Python
Всем привет! В этой статье я решил показать один из методов парсинга на Python на примере маркетплейса Wildberries.
Суть подхода в том, что мы будем не разбирать запрошенную html страницу по ссылке, а будем использовать API сайта, который используется сервисом для получения и отображения всех товаров требуемой категории.
В проекте будут использоваться следующие библиотеки:
requests — для парсинга данных API.
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, объявленный в начале функции.
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"Категория: Гарнитуры и наушники\n\nНазвание: {product['name']}\nБренд: {product['brand']}\n\nОтзывов всего: {product['feedbacks']}\nСредняя оценка: {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"Категория: Гарнитуры и наушники\n\nНазвание: {product['name']}\nБренд: {product['brand']}\n\nОтзывов всего: {product['feedbacks']}\nСредняя оценка: {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.
Я покажу последовательность команд для первого коммита и отправки (пуша) файлов:
git init
— инициализирует git локальноgit remote add amvera https://git.amvera.ru/Ваш_Ник/Имя_проекта
— команда для подключения к репозиторию Amvera. Команду можно найти во вкладке «Репозиторий»git add .
— добавляет все файлы в локальном репозиторииgit commit -m "Комментарий"
— первый коммитgit push amvera master
— пуш в репозиторий.
Иногда могут возникнуть проблемы. Здесь собраны возможные ошибки и решения, связанные с Git.
При пуше автоматически начинается сборка. Если вы загрузили через интерфейс — перейдите во вкладку «Конфигурация» и нажмите кнопку «Собрать». Важно именно собирать проект при обновлении кода/конфигурации.
Итог
Когда приложение собралось — вам осталось дождаться запуска бота и проверить его работоспособность.
Cегодня мы научились парсить страницу, а точнее использовать для этого API маркетплейса, получая данные о карточках, добавили функционал в бота и научились развертывать минимальное приложение в Amvera.