Цель статьи — описать алгоритм действий поиска открытого API сайта.
Целевая аудитория статьи — программисты, которым интересен парсинг и анализ уязвимостей сайтов.
В статье рассмотрим пример поиска API сайта edadeal.ru, познакомимся с протоколом google protobuf и сравним скорость различных подходов парсинга
1. Введение
Парсинг (в контексте статьи) — это автоматизированный процесс извлечение данных из Интернета.
Существует 2 подхода к извлечению данных со страниц сайта
-
Извлекать данные из HTML-кода страницы сайта
Плюсы — этот способ прост и работает всегда, так как код страницы всегда доступен пользователю
Минусы — этот способ может работать долго (несколько секунд), если часть данных генерирует java script (например, данные появляются только после прокручивания страницы или нажатия кнопки) -
Использовать API сайта
Плюсы — быстрее первого способа и не зависит от изменений структуры html-страницы
Минус — не у всех сайтов есть открытое API
В статье рассмотрим пример поиска API сайта edadeal.ru, познакомимся с протоколом google protobuf и сравним скорость двух подходов парсинга
2. Постановка задачи
Задача — извлечь данные о продуктах с сайта Едадил (название продукта, цена, размер скидки, магазин, город и т.д)
3. Решение
1 Делаем запрос к странице, которую мы хотим парсить.
2 Перебираем все запросы, которые делает сайт. Для этого используем DevTools браузера
3 Анализируем запросы
Из названия запроса понимаем, что нам нужен запрос
https://squark.edadeal.ru/web/search/offers?count=30&locality=moskva&page=1&retailer=5ka
В ответ на запрос получаем файл (назовем его binary_file.bin). Как узнать кодировку этого файла?
Формат файла из пункта 3 нам подсказывает строка-хедер content-type: application/x-protobuf
4 Определим структуру данных (.proto файл)
с помощью утилиты protoc (http://google.github.io/proto-lens/installing-protoc.html) преобразуем закодированный файл в понятный человеку формат
protoc --decode_raw < binary_file.bin
Получаем список словарей:
1 {
1: "e341_260 07177W202222O326316233326 00A"
2: "320242321203320260320273320265321202320275320260321217 320261321203320274320260320263320260 Familia Plus, 2 321201320273320276321217, 12 321200321203320273320276320275320276320262, 1 321203320277."
3: "https://leonardo.edadeal.io/dyn/cr/catalyst/offers/u4nf6zbkjc3m5lss46ucvxjafm.jpg"
4: 0x43ad7eb8
5: 0x4347e666
7: ";5332^c 21 21346204237RT 00 20266 10"
8: 0x41400000
9: "321210321202"
10: 0x422c0000
11: "%"
13: 43
15: "2022-07-26T00:00:00Z"
16: "2022-08-01T00:00:00Z"
19: "A1 05L332nPg230342q375 31335 14336"
20 {
1: 0x3f800000
2: 0x418547ae
3: "321210321202"
4: 1
}
21: "224331203202B303 21346224 31RT 00 20266 10"
22: "K3 202537{O271273374K351376224310*"
22: "300336d(224kL 25224300355256247327R 35"
22: "303O:202330262A326246 23307D314F303G"
22: "210" 22?250|L.272375345{335c, 26"
22: "=3yP 26 04N334267377320 36F326331\"
22: "E211 00246e6EI223 00)2423348216M"
22: "V#263 22367324H350232r 13 10_KX273"
23: "320232320276320273320270321207320265321201321202320262320276"
24: 1
}
5 Формируем .proto файл
Используем номера из предыдущего пункта, по контенту из предыдущего пункта нужно догадаться, какие поля, что означают (например 3 — это ссылка на изображение продукта)
Методом проб и ошибок получаем следующую структуру:
syntax = "proto2";
message Offers {
repeated Offer offer = 1;
}
message Offer {
optional string name = 2;
optional string image_url = 3;
optional float price_before = 4;
optional float price_after = 5;
optional float amount = 8;
optional float discount = 10;
optional string start_date = 15;
optional string end_date = 16;
}
4 Переходим к написанию кода
Создаем питоновский файл с описанием структуры из .proto файла
protoc --proto_path=proto_files --python_out=proto_structs offers.proto
proto_files
— имя директории с .proto файлами
proto_structs
— в этой директории сохраняются результаты (_pb2.py файлы)
Код работает следующим образом:
- Делает запрос к API сайта
- Преобразует ответ сайта в json
- Выводит результат
import json
import requests
from google.protobuf.json_format import MessageToJson
from proto_structs import offers_pb2
def parse_page(city = "moskva", shop = "5ka", page_num = 1):
"""
:param city: location of the shop
:param shop: shop name
:param page_num: parsed page number
:return: None
"""
url = f"https://squark.edadeal.ru/web/search/offers?count=30&locality={city}&page={page_num}&retailer={shop}"
data = requests.get(url, allow_redirects=True) # data.content is a protobuf message
offers = offers_pb2.Offers() # protobuf structure
offers.ParseFromString(data.content) # parse binary data
products: str = MessageToJson(offers) # convert protobuf message to json
products = json.loads(products)
print(json.dumps(products, indent=4, ensure_ascii=False,))
if __name__ == "__main__":
parse_page()
Результат работы программы — список продуктов с описанием
{
"offer": [
{
"name": "Наггетсы, куриные с ветчиной, Мираторг, 300 г",
"imageUrl": "https://leonardo.edadeal.io/dyn/cr/catalyst/offers/necnmkv43splbm3hr5636snpry.jpg",
"priceBefore": 218.99000549316406,
"priceAfter": 109.48999786376953,
"amount": 300.0,
"discount": 51.0,
"startDate": "2022-08-02T00:00:00Z",
"endDate": "2022-08-08T00:00:00Z"
},
...
5 Сравним результаты
Время выполнения кода из предыдущего пункта 0.3 — 0.4 секунды
Альтернативный вариант парсинга — загрузка всего html-кода страницы и извлечения нужной информации из этого кода
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("https://edadeal.ru/moskva/retailers/5ka")
# извлечение данных из html кода
Время полной загрузки страницы 5 — 6 секунд.
6 Выводы
Лучше использовать API сайта для извлечения данных, если есть такая возможность
Использование API сайта позволяет не зависеть от измений в html-коде страниы