|
1 | | -# FixPrice API (not official / не официальный) |
| 1 | +<div align="center"> |
2 | 2 |
|
3 | | -FixPrice - https://fix-price.com/ |
| 3 | +# FixPrice API (not official) |
4 | 4 |
|
5 | | -# Usage / Использование |
| 5 | + |
| 6 | +[](https://github.com/Open-Inflation/fixprice_api/actions/workflows/tests.yml) |
| 7 | + |
| 8 | + |
| 9 | +[](https://pypi.org/project/fixprice-api/) |
| 10 | +[](https://github.com/Open-Inflation/fixprice_api/blob/main/LICENSE) |
| 11 | +[](https://discord.gg/UnJnGHNbBp) |
| 12 | +[](https://t.me/miskler_dev) |
6 | 13 |
|
7 | | -> `product_info` не реализован т.к. информация вшита в страницу. |
| 14 | +FixPrice (Фикс Прайс) - https://fix-price.com/ |
8 | 15 |
|
| 16 | +**[⭐ Star us on GitHub](https://github.com/Open-Inflation/fixprice_api)** | **[📚 Read the Docs](https://open-inflation.github.io/fixprice_api/quick_start)** | **[🐛 Report Bug](https://github.com/Open-Inflation/fixprice_api/issues)** |
| 17 | + |
| 18 | +### Принцип работы |
| 19 | + |
| 20 | +</div> |
| 21 | + |
| 22 | +> Библиотека полностью повторяет сетевую работу обычного пользователя на сайте. |
| 23 | +
|
| 24 | +<div align="center"> |
| 25 | + |
| 26 | +# Usage |
| 27 | + |
| 28 | +</div> |
| 29 | + |
| 30 | +```bash |
| 31 | +pip install fixprice_api |
| 32 | +python -m camoufox fetch |
| 33 | +``` |
9 | 34 |
|
10 | | -### Базовая структура |
11 | 35 | ```py |
12 | 36 | import asyncio |
13 | | -from fixprice_api import FixPrice, CatalogSort |
| 37 | +from fixprice_api import FixPriceAPI, CatalogSort |
| 38 | +from PIL import Image |
| 39 | + |
14 | 40 |
|
15 | 41 | async def main(): |
16 | | - ... |
| 42 | + async with FixPriceAPI() as api: |
| 43 | + # 1. Получаем дерево категорий |
| 44 | + tree_data = (await api.Catalog.tree()).json() |
| 45 | + first_alias = tree_data[next(iter(tree_data))]["alias"] |
| 46 | + print(f"Первая категория: {first_alias}") |
| 47 | + |
| 48 | + # 2. Список товаров в категории |
| 49 | + products = ( |
| 50 | + await api.Catalog.products_list( |
| 51 | + category_alias=first_alias, |
| 52 | + page=1, |
| 53 | + limit=24, |
| 54 | + sort=CatalogSort.POPULARITY, |
| 55 | + ) |
| 56 | + ).json() |
| 57 | + first_product_id = products[0]["id"] |
| 58 | + print(f"Первый товар: {products[0]['title']!s:.60s} ({first_product_id})") |
| 59 | + |
| 60 | + # 3. Геолокация (влияет на каталог и баланс) |
| 61 | + cities = (await api.Geolocation.cities_list(country_id=2)).json() # Россия |
| 62 | + api.city_id = cities[0]["id"] |
| 63 | + print(f"Текущий city_id: {api.city_id}") |
| 64 | + |
| 65 | + # 4. Проверка наличия товара по магазинам |
| 66 | + balance = (await api.Catalog.Product.balance(product_id=first_product_id)).json() |
| 67 | + print(f"Проверено магазинов: {len(balance)}") |
| 68 | + |
| 69 | + # 5. Загрузка изображения |
| 70 | + image_url = products[0]["images"][0]["src"] |
| 71 | + image_stream = await api.General.download_image(image_url) |
| 72 | + with Image.open(image_stream) as img: |
| 73 | + print(f"Image format: {img.format}, size: {img.size}") |
| 74 | + |
17 | 75 |
|
18 | 76 | if __name__ == "__main__": |
19 | 77 | asyncio.run(main()) |
20 | 78 | ``` |
21 | 79 |
|
22 | | -### Взаимодействие с каталогом |
23 | | - |
24 | | -```py |
25 | | -async with FixPrice() as Api: |
26 | | - # Получение списка категорий |
27 | | - categories = await Api.Catalog.categories_list() |
28 | | - products = [] |
29 | | - tq = tqdm(categories, desc='Обработано категорий') |
30 | | - |
31 | | - async def process_sub(category_alias, subcategory_alias=None, depth=0): |
32 | | - page = 1 # Счет от единицы, а не нуля! |
33 | | - limit = 27 # Максимальное значение |
34 | | - |
35 | | - while page > 0: |
36 | | - # count - общее количество айтемов на всех страницах (в данном случае не используем) |
37 | | - count, catalog = await Api.Catalog.products_list( |
38 | | - category_alias=category_alias, |
39 | | - subcategory_alias=subcategory_alias, |
40 | | - page=page, |
41 | | - limit=limit, |
42 | | - sort=CatalogSort.POPULARITY |
43 | | - ) |
44 | | - if not catalog: |
45 | | - break |
46 | | - |
47 | | - for product in catalog: |
48 | | - products.append(f'{product["title"]} ({product["id"]})') |
49 | | - tq.set_description(f'Обработано карточек: {len(products)}') |
50 | | - |
51 | | - if len(catalog) <= 0: |
52 | | - break |
53 | | - |
54 | | - time.sleep(0.4) # Специально замедляем обработку, чтобы не получить код 429, советую эксперементировать |
55 | | - page += 1 |
56 | | - |
57 | | - # Обход всех категорий и подкатегорий |
58 | | - for category in tq: |
59 | | - subcategories = category.get("items", []) |
60 | | - # Можно и не обрабатывать подкатегории отдельно, зависит от желания и ТЗ |
61 | | - if subcategories: |
62 | | - for subcategory in subcategories: |
63 | | - await process_sub(category["alias"], subcategory["alias"]) |
64 | | - else: |
65 | | - await process_sub(category["alias"]) |
66 | | - |
67 | | - tq.close() |
68 | | - |
69 | | - # Вывод статистики |
70 | | - print(f'Общее количество встреченных карточек: {len(products)}') |
71 | | - print(f'Уникальных товаров: {len(set(products))}') |
72 | | - print(f'Среднее количество повторений карточки: {round(len(products) / len(set(products)), 2)}') |
73 | | -``` |
74 | 80 | ```bash |
75 | | -> Обработано карточек: 6019: 100%|███████████████| 29/29 [04:29<00:00, 9.29s/it] |
76 | | -> Общее количество встреченных карточек: 6019 |
77 | | -> Уникальных товаров: 4900 |
78 | | -> Среднее количество повторений карточки: 1.23 |
| 81 | +> Первая категория: kosmetika-i-gigiena |
| 82 | +> Первый товар: Крем для рук и тела, 150 мл (2345678) |
| 83 | +> Текущий city_id: 3 |
| 84 | +> Проверено магазинов: 339 |
| 85 | +> Image format: WEBP, size: (190, 190) |
79 | 86 | ``` |
80 | 87 |
|
81 | | -### Работа с геолокацией в сессии: |
82 | | -*От геолокации зависит выдача каталога!* |
83 | | -```py |
84 | | -async with FixPrice() as Api: |
85 | | - print(f"ID города перед первым запросом: {Api.city_id}, язык: {Api.language}") # По умолчанию не назначено |
86 | | - await Api.Catalog.home_brands_list() # Можем обработать любую функцию |
87 | | - print(f"ID города после первого запроса: {Api.city_id}, язык: {Api.language}") # Сервер прислал стандартные значения |
| 88 | +Для более подробной информации смотрите референсы [документации](https://open-inflation.github.io/fixprice_api/quick_start). |
88 | 89 |
|
89 | | - country = await Api.Geolocation.country_list(alias="RU") # alias работает сортировкой |
| 90 | +--- |
90 | 91 |
|
91 | | - # получаем объект "Объединенные Арабские Эмираты" |
92 | | - print(f"Найдена страна {country[0]['title']} ({country[0]['id']}), валюта: {country[0]['currency']['title']} / {country[0]['currency']['symbol']}") |
| 92 | +## Автотесты API (pytest + snapshots) |
93 | 93 |
|
94 | | - citys = await Api.Geolocation.city_list(country_id=country[0]["id"]) # получаем список городов |
| 94 | +В проекте используется автотест-фреймворк из `human_requests`: |
95 | 95 |
|
96 | | - Api.city_id = citys[0]["id"] # меняем ID города |
97 | | - print(f"Город изменен на {citys[0]['name']} ({citys[0]['id']})") |
| 96 | +- endpoint-методы в бизнес-коде помечаются `@autotest`; |
| 97 | +- pytest-плагин сам находит эти методы и запускает их; |
| 98 | +- JSON-ответы проверяются через `pytest-jsonschema-snapshot` (`schemashot`); |
| 99 | +- параметры вызова и пост-обработка результата регистрируются в `tests/api_test.py` через: |
| 100 | + - `@autotest_params` |
| 101 | + - `@autotest_hook` |
| 102 | + - `@autotest_depends_on` |
98 | 103 |
|
99 | | - # Вне РФ каталог не работает |
100 | | - print(f"Категории: {len(await Api.Catalog.categories_list())} штук") |
101 | | -``` |
102 | | -```bash |
103 | | -> ID города перед первым запросом: None, язык: None |
104 | | -> ID города после первого запроса: 3, язык: ru |
105 | | -> Найдена страна Россия (2), валюта: Рубль / ₽ |
106 | | -> Город изменен на Щербинка (229) |
107 | | -> Категории: 29 штук |
108 | | -``` |
| 104 | +Минимальная конфигурация уже включена в `pyproject.toml`: |
109 | 105 |
|
110 | | -### Проверка наличия товара |
111 | | -```py |
112 | | -async with FixPrice() as Api: |
113 | | - Api.city_id = 3 # Обязательно указываем перед запросом город, иначе ошибка |
114 | | - check = await Api.Store.product_balance(1851089) # Круассан, 7DAYS, 110 г, с двойным кремом |
115 | | - |
116 | | - stoks = [] |
117 | | - for i in check: |
118 | | - stoks.append(i.get("count", 0)) |
119 | | - |
120 | | - print(f"Самое большое количество: {max(stoks)}") |
121 | | - print(f"Самое малое количество: {min(stoks)}") |
122 | | - print(f"Среднее количество: {round(sum(stoks) / len(stoks), 2)}") |
123 | | - print(f"Обработано {len(stoks)} магазинов") |
124 | | -``` |
125 | | -```bash |
126 | | -> Самое большое количество: 67 |
127 | | -> Самое малое количество: 10 |
128 | | -> Среднее количество: 28.5 |
129 | | -> Обработано 339 магазинов |
| 106 | +```ini |
| 107 | +[tool.pytest.ini_options] |
| 108 | +anyio_mode = "auto" |
| 109 | +autotest_start_class = "fixprice_api.FixPriceAPI" |
130 | 110 | ``` |
131 | 111 |
|
132 | | -### Загрузка изображений |
133 | | -```py |
134 | | -async with FixPrice() as Api: |
135 | | - img = await Api.General.download_image("https://img.fix-price.com/190x190/_marketplace/images/origin/90/903ce795a221a6978444a86391816f93.jpg") |
| 112 | +Запуск тестов: |
136 | 113 |
|
137 | | - with open(img.name, "wb") as f: |
138 | | - f.write(img.read()) |
| 114 | +```bash |
| 115 | +pytest |
139 | 116 | ``` |
140 | 117 |
|
141 | | -### Или параллельная загрузка |
142 | | -```py |
143 | | -async with FixPrice() as Api: |
144 | | - tasks = [ |
145 | | - Api.General.download_image("https://img.fix-price.com/190x190/_marketplace/images/origin/90/903ce795a221a6978444a86391816f93.jpg"), |
146 | | - Api.General.download_image("https://img.fix-price.com/190x190/_marketplace/images/origin/51/519a1d3c838e3e7e30493fb9b1f69a05.jpg") |
147 | | - ] |
148 | | - |
149 | | - results = await asyncio.gather(*tasks) |
150 | | - for result in results: |
151 | | - with open(result.name, "wb") as f: |
152 | | - f.write(result.read()) |
153 | | -``` |
| 118 | +Важно: |
| 119 | + |
| 120 | +- используется `pytest-anyio` (не `pytest-asyncio`); |
| 121 | +- ручные тесты остаются только для кейсов, которые не относятся к JSON-схемам endpoint-методов (например, `download_image`). |
154 | 122 |
|
155 | 123 | --- |
156 | 124 |
|
157 | | -### Report / Обратная связь |
| 125 | +<div align="center"> |
| 126 | + |
| 127 | +### Report |
158 | 128 |
|
159 | 129 | If you have any problems using it / suggestions, do not hesitate to write to the [project's GitHub](https://github.com/Open-Inflation/fixprice_api/issues)! |
160 | 130 |
|
161 | | -Если у вас возникнут проблемы в использовании / пожелания, не стесняйтесь писать на [GitHub проекта](https://github.com/Open-Inflation/fixprice_api/issues)! |
| 131 | +</div> |
0 commit comments