wbapi 0.0.1__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wbapi-0.2.1/PKG-INFO +366 -0
- wbapi-0.2.1/README.md +356 -0
- wbapi-0.2.1/pyproject.toml +71 -0
- wbapi-0.2.1/pyproject.toml.orig +51 -0
- wbapi-0.2.1/src/wbapi/__init__.py +5 -0
- wbapi-0.2.1/src/wbapi/_endpoint.py +13 -0
- wbapi-0.2.1/src/wbapi/_json.py +2 -0
- wbapi-0.2.1/src/wbapi/_transport.py +238 -0
- wbapi-0.2.1/src/wbapi/_version.py +3 -0
- wbapi-0.2.1/src/wbapi/client.py +76 -0
- wbapi-0.2.1/src/wbapi/exceptions.py +213 -0
- wbapi-0.2.1/src/wbapi/models/__init__.py +74 -0
- wbapi-0.2.1/src/wbapi/models/analytics.py +154 -0
- wbapi-0.2.1/src/wbapi/models/finances.py +124 -0
- wbapi-0.2.1/src/wbapi/models/general.py +44 -0
- wbapi-0.2.1/src/wbapi/models/items.py +48 -0
- wbapi-0.2.1/src/wbapi/models/reports.py +65 -0
- wbapi-0.2.1/src/wbapi/py.typed +0 -0
- wbapi-0.2.1/src/wbapi/rate_limit.py +235 -0
- wbapi-0.2.1/src/wbapi/resources/__init__.py +10 -0
- wbapi-0.2.1/src/wbapi/resources/analytics.py +327 -0
- wbapi-0.2.1/src/wbapi/resources/finances.py +191 -0
- wbapi-0.2.1/src/wbapi/resources/general.py +174 -0
- wbapi-0.2.1/src/wbapi/resources/items.py +139 -0
- wbapi-0.2.1/src/wbapi/resources/reports.py +203 -0
- wbapi-0.2.1/src/wbapi/response.py +27 -0
- wbapi-0.0.1/.gitignore +0 -10
- wbapi-0.0.1/.python-version +0 -1
- wbapi-0.0.1/PKG-INFO +0 -9
- wbapi-0.0.1/README.md +0 -1
- wbapi-0.0.1/pyproject.toml +0 -17
- wbapi-0.0.1/src/wbapi/__init__.py +0 -2
wbapi-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: wbapi
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Typed async client for the Wildberries seller API
|
|
5
|
+
Author: Dmitry Drozdov
|
|
6
|
+
Author-email: Dmitry Drozdov <ds.drozdov@gmail.com>
|
|
7
|
+
Requires-Dist: httpx2>=2.13.1,<3
|
|
8
|
+
Requires-Python: >=3.14
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# wbapi
|
|
12
|
+
|
|
13
|
+
Внутренний типизированный Python-клиент для официального API продавцов Wildberries.
|
|
14
|
+
|
|
15
|
+
Текущая версия реализует три read-only метода раздела «Общее» из
|
|
16
|
+
`specs/upstream/01-general.yaml`, получение детализации отчётов реализации из
|
|
17
|
+
`specs/upstream/13-finances.yaml`, статистику карточек товаров из
|
|
18
|
+
`specs/upstream/11-analytics.yaml`, получение товаров с ценами из `specs/upstream/02-items.yaml`
|
|
19
|
+
и отчёт о платном хранении из `specs/upstream/12-reports.yaml`.
|
|
20
|
+
Ответы возвращаются без преобразования в виде обычных `dict` и `list`. `TypedDict` используется
|
|
21
|
+
только для подсказок IDE и статической проверки.
|
|
22
|
+
|
|
23
|
+
## Установка
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv add --editable /path/to/wbapi
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Требуется Python 3.14 или новее.
|
|
30
|
+
|
|
31
|
+
Для HTTP-запросов используется `httpx2`. Если передаёте собственный клиент через
|
|
32
|
+
`AsyncWBClient(http_client=...)`, создавайте его как `httpx2.AsyncClient`.
|
|
33
|
+
|
|
34
|
+
## Асинхронный клиент
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import asyncio
|
|
38
|
+
|
|
39
|
+
from wbapi import AsyncWBClient
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
async def main() -> None:
|
|
43
|
+
async with AsyncWBClient("personal-token") as client:
|
|
44
|
+
result = await client.general.ping()
|
|
45
|
+
print(result)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
asyncio.run(main())
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Все примеры ниже предполагают, что вызов выполняется внутри блока
|
|
52
|
+
`async with AsyncWBClient(...) as client`.
|
|
53
|
+
|
|
54
|
+
## Поддерживаемые эндпоинты
|
|
55
|
+
|
|
56
|
+
### Проверка подключения
|
|
57
|
+
|
|
58
|
+
`client.general.ping()` выполняет `GET https://common-api.wildberries.ru/ping`. Метод проверяет
|
|
59
|
+
доступность API и валидность токена, не принимает аргументов и возвращает исходный JSON-ответ WB.
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
result = await client.general.ping()
|
|
63
|
+
|
|
64
|
+
print(result.get("Status"))
|
|
65
|
+
print(result.get("TS"))
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Лимит: максимум 3 запроса за 30 секунд, отдельно для каждого домена API.
|
|
69
|
+
|
|
70
|
+
### Новости портала продавцов
|
|
71
|
+
|
|
72
|
+
Для последовательного получения всех доступных новостей используйте `iter_news()`. Необходимо
|
|
73
|
+
передать ровно один из двух параметров:
|
|
74
|
+
|
|
75
|
+
- `from_date` — дата начала выборки как `datetime.date` или строка `YYYY-MM-DD`;
|
|
76
|
+
- `from_id` — неотрицательный ID новости, с которого нужно начать выборку.
|
|
77
|
+
|
|
78
|
+
Параметр `max_pages` ограничивает количество запросов:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from datetime import date
|
|
82
|
+
|
|
83
|
+
async for news in client.general.iter_news(
|
|
84
|
+
from_date=date(2026, 6, 1),
|
|
85
|
+
max_pages=10,
|
|
86
|
+
):
|
|
87
|
+
print(news.get("header"))
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Низкоуровневый метод `get_news()` выполняет один запрос
|
|
91
|
+
`GET https://common-api.wildberries.ru/api/communications/v2/news` и возвращает одну страницу в
|
|
92
|
+
исходной структуре ответа WB. Используйте его, если требуется самостоятельно управлять курсором,
|
|
93
|
+
границами страниц или обработкой отдельных запросов:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
page = await client.general.get_news(from_id=1)
|
|
97
|
+
|
|
98
|
+
for news in page.get("data", []):
|
|
99
|
+
print(news.get("id"), news.get("header"))
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Лимит для персонального токена: 1 запрос в минуту, всплеск до 10 запросов.
|
|
103
|
+
|
|
104
|
+
### Товары с ценами
|
|
105
|
+
|
|
106
|
+
`client.items.get_goods_prices()` выполняет
|
|
107
|
+
`GET https://discounts-prices-api.wildberries.ru/api/v2/list/goods/filter` и возвращает одну
|
|
108
|
+
страницу товаров с ценами и скидками в исходной структуре WB. `limit` обязателен и не должен
|
|
109
|
+
превышать 1000, `offset` задаёт смещение, а `filter_nm_id` позволяет найти один артикул WB.
|
|
110
|
+
|
|
111
|
+
Для автоматической загрузки всех страниц используйте `iter_goods_prices()`:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
async for item in client.items.iter_goods_prices(max_pages=10):
|
|
115
|
+
print(item.get("nmID"), item.get("currencyIsoCode4217"), item.get("sizes"))
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
page = await client.items.get_goods_prices(
|
|
120
|
+
limit=1000,
|
|
121
|
+
offset=0,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
for item in page.get("data", {}).get("listGoods", []):
|
|
125
|
+
print(item.get("nmID"), item.get("currencyIsoCode4217"), item.get("sizes"))
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Итератор увеличивает `offset` на `limit` после каждого запроса, останавливается на пустом
|
|
129
|
+
`listGoods` и предотвращает повтор артикулов WB. `max_pages` ограничивает количество страниц.
|
|
130
|
+
Лимит для персонального токена общий для категории «Цены и скидки»: всплеск до 5 запросов с
|
|
131
|
+
восполнением одного запроса каждые 600 мс.
|
|
132
|
+
|
|
133
|
+
### Информация о продавце
|
|
134
|
+
|
|
135
|
+
`client.general.get_seller_info()` выполняет
|
|
136
|
+
`GET https://common-api.wildberries.ru/api/v1/seller-info`. Метод не принимает аргументов и
|
|
137
|
+
возвращает наименование, ID, ИНН и торговое имя продавца.
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
seller = await client.general.get_seller_info()
|
|
141
|
+
|
|
142
|
+
print(seller.get("name"))
|
|
143
|
+
print(seller.get("sid"))
|
|
144
|
+
print(seller.get("tin"))
|
|
145
|
+
print(seller.get("tradeMark"))
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Лимит для персонального токена: 1 запрос в минуту, всплеск до 10 запросов.
|
|
149
|
+
|
|
150
|
+
### Детализация отчётов реализации
|
|
151
|
+
|
|
152
|
+
Для автоматической загрузки всех строк детализации используйте `iter_sales_report_details()`.
|
|
153
|
+
Обязательные аргументы:
|
|
154
|
+
|
|
155
|
+
- `date_from` — начало периода;
|
|
156
|
+
- `date_to` — конец периода.
|
|
157
|
+
|
|
158
|
+
Дата может быть строкой RFC 3339, `datetime.date` или `datetime.datetime`. Время WB интерпретирует
|
|
159
|
+
в часовом поясе Москвы UTC+3. Доступны необязательные аргументы:
|
|
160
|
+
|
|
161
|
+
- `limit` — количество строк, от 1 до 100 000;
|
|
162
|
+
- `rrd_id` — неотрицательный ID строки для продолжения загрузки, начальное значение — `0`;
|
|
163
|
+
- `period` — периодичность отчёта: `"daily"` или `"weekly"`;
|
|
164
|
+
- `fields` — список полей, которые нужно вернуть.
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
async for row in client.finances.iter_sales_report_details(
|
|
168
|
+
date_from="2026-03-17",
|
|
169
|
+
date_to="2026-03-20",
|
|
170
|
+
period="daily",
|
|
171
|
+
limit=1_000,
|
|
172
|
+
fields=["rrdId", "nmId", "retailAmount", "acquiringFee"],
|
|
173
|
+
max_pages=10,
|
|
174
|
+
):
|
|
175
|
+
print(row.get("rrdId"), row.get("retailAmount"))
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Итератор загружает страницы до ответа HTTP 204, передаёт `rrdId` последней строки в следующий
|
|
179
|
+
запрос и предотвращает повтор строк и отсутствие продвижения курсора. Параметр `max_pages`
|
|
180
|
+
ограничивает количество страниц. Если итератор вызывается с `fields`, список обязан содержать
|
|
181
|
+
`rrdId`.
|
|
182
|
+
|
|
183
|
+
Низкоуровневый метод `get_sales_report_details()` выполняет один запрос
|
|
184
|
+
`POST https://finance-api.wildberries.ru/api/finance/v1/sales-reports/detailed` и возвращает одну
|
|
185
|
+
страницу в исходной структуре ответа WB. Используйте его, если требуется самостоятельно управлять
|
|
186
|
+
`rrd_id`, границами страниц или обработкой HTTP 204:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
rows = await client.finances.get_sales_report_details(
|
|
190
|
+
date_from="2026-03-17",
|
|
191
|
+
date_to="2026-03-20",
|
|
192
|
+
period="daily",
|
|
193
|
+
limit=1_000,
|
|
194
|
+
rrd_id=0,
|
|
195
|
+
fields=["rrdId", "nmId", "retailAmount"],
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if rows is None: # HTTP 204 — данных нет
|
|
199
|
+
print("Данных нет")
|
|
200
|
+
else:
|
|
201
|
+
for row in rows:
|
|
202
|
+
print(row.get("rrdId"), row.get("retailAmount"))
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Лимит для персонального токена: 1 запрос в минуту. Встроенный ограничитель автоматически ожидает
|
|
206
|
+
освобождения лимита перед загрузкой следующей страницы.
|
|
207
|
+
|
|
208
|
+
### Воронка продаж по карточкам товаров
|
|
209
|
+
|
|
210
|
+
Для последовательной загрузки всех карточек используйте `iter_sales_funnel_products()`:
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
async for item in client.analytics.iter_sales_funnel_products(
|
|
214
|
+
date_from="2026-06-01",
|
|
215
|
+
date_to="2026-06-30",
|
|
216
|
+
past_date_from="2026-05-01",
|
|
217
|
+
past_date_to="2026-05-30",
|
|
218
|
+
nm_ids=[], # Пустой список означает все карточки продавца
|
|
219
|
+
order_by={"field": "orderSum", "mode": "desc"},
|
|
220
|
+
limit=1000,
|
|
221
|
+
max_pages=10,
|
|
222
|
+
):
|
|
223
|
+
product = item.get("product", {})
|
|
224
|
+
statistic = item.get("statistic", {}).get("selected", {})
|
|
225
|
+
print(product.get("nmId"), statistic.get("orderSum"))
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Дополнительно поддерживаются фильтры `brand_names`, `subject_ids`, `tag_ids` и
|
|
229
|
+
`skip_deleted_nm`. Период — максимум последние 365 дней. Параметр `limit` должен быть от 1 до
|
|
230
|
+
1000, `offset` задаёт начальное смещение, `max_pages` ограничивает количество страниц. Итератор
|
|
231
|
+
увеличивает offset на фактическое число карточек и предотвращает повтор артикулов WB.
|
|
232
|
+
|
|
233
|
+
Низкоуровневый `get_sales_funnel_products()` выполняет один запрос и сохраняет исходную структуру,
|
|
234
|
+
включая общую валюту отчёта:
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
page = await client.analytics.get_sales_funnel_products(
|
|
238
|
+
date_from="2026-06-01",
|
|
239
|
+
date_to="2026-06-30",
|
|
240
|
+
limit=100,
|
|
241
|
+
offset=0,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
print(page.get("data", {}).get("currency"))
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Метод выполняет `POST https://seller-analytics-api.wildberries.ru/api/analytics/v3/sales-funnel/products`.
|
|
248
|
+
Лимит для персонального токена: всплеск до 3 запросов с восполнением одного запроса раз в 20 секунд.
|
|
249
|
+
|
|
250
|
+
### Отчёт о платном хранении
|
|
251
|
+
|
|
252
|
+
`get_paid_storage_report()` выполняет весь сценарий: создаёт задание, проверяет его статус до
|
|
253
|
+
готовности и скачивает результат. Период может охватывать максимум 8 дней. Даты принимаются как
|
|
254
|
+
строки RFC 3339, `datetime.date` или `datetime.datetime`.
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
rows = await client.reports.get_paid_storage_report(
|
|
258
|
+
date_from="2026-07-01",
|
|
259
|
+
date_to="2026-07-08",
|
|
260
|
+
max_status_checks=120,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
if rows is None: # HTTP 204 — данных нет
|
|
264
|
+
print("Данных нет")
|
|
265
|
+
else:
|
|
266
|
+
for row in rows:
|
|
267
|
+
print(row.get("date"), row.get("nmId"), row.get("warehousePrice"))
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
По умолчанию статус проверяется раз в 5 секунд, максимум 120 раз. Интервал и предел можно изменить
|
|
271
|
+
параметрами `poll_interval` и `max_status_checks`. Статусы `canceled` и `purged` приводят к
|
|
272
|
+
`WBReportTaskFailedError`, исчерпание проверок — к `WBReportTaskTimeoutError`.
|
|
273
|
+
|
|
274
|
+
Для ручного управления этапами доступны низкоуровневые методы:
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
task = await client.reports.create_paid_storage_report(
|
|
278
|
+
date_from="2026-07-01",
|
|
279
|
+
date_to="2026-07-08",
|
|
280
|
+
)
|
|
281
|
+
task_id = task["data"]["taskId"]
|
|
282
|
+
|
|
283
|
+
status = await client.reports.get_paid_storage_report_status(task_id)
|
|
284
|
+
if status.get("data", {}).get("status") == "done":
|
|
285
|
+
rows = await client.reports.download_paid_storage_report(task_id)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Создание задания ограничено всплеском до 5 запросов с восполнением раз в минуту, проверка статуса —
|
|
289
|
+
до 5 запросов с восполнением раз в 5 секунд, скачивание — 1 запрос в минуту. Создание задания не
|
|
290
|
+
повторяется автоматически после транспортной ошибки или `5xx`, поскольку операция создаёт новый
|
|
291
|
+
ресурс и спецификация не гарантирует её идемпотентность.
|
|
292
|
+
|
|
293
|
+
## HTTP-метаданные
|
|
294
|
+
|
|
295
|
+
Обычный метод возвращает JSON-тело. Вариант с суффиксом `_detailed` дополнительно возвращает
|
|
296
|
+
`WBResponse` с HTTP-статусом, заголовками и исходным телом. Варианты доступны для каждого
|
|
297
|
+
низкоуровневого метода:
|
|
298
|
+
|
|
299
|
+
- `general.ping_detailed()`;
|
|
300
|
+
- `general.get_news_detailed()`;
|
|
301
|
+
- `general.get_seller_info_detailed()`;
|
|
302
|
+
- `items.get_goods_prices_detailed()`;
|
|
303
|
+
- `finances.get_sales_report_details_detailed()`;
|
|
304
|
+
- `analytics.get_sales_funnel_products_detailed()`;
|
|
305
|
+
- `reports.create_paid_storage_report_detailed()`;
|
|
306
|
+
- `reports.get_paid_storage_report_status_detailed()`;
|
|
307
|
+
- `reports.download_paid_storage_report_detailed()`.
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
response = await client.finances.get_sales_report_details_detailed(
|
|
311
|
+
date_from="2026-03-17",
|
|
312
|
+
date_to="2026-03-20",
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
print(response.status_code)
|
|
316
|
+
print(response.headers)
|
|
317
|
+
print(response.body)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Ошибки
|
|
321
|
+
|
|
322
|
+
Для неуспешных HTTP-ответов поднимаются исключения из `wbapi.exceptions`. Исключение сохраняет
|
|
323
|
+
`status_code`, `headers` и исходное JSON-тело в `body`.
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
from wbapi.exceptions import WBAuthenticationError
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
await client.general.ping()
|
|
330
|
+
except WBAuthenticationError as error:
|
|
331
|
+
print(error.status_code, error.body)
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## Лимиты и повторы
|
|
335
|
+
|
|
336
|
+
По умолчанию все клиенты внутри одного Python-процесса используют общий token bucket. Он учитывает
|
|
337
|
+
лимиты конкретного метода и заголовки `X-Ratelimit-*`. Ответы `429`, временные `5xx` и транспортные
|
|
338
|
+
ошибки методов, явно отмеченных как безопасные для повтора, повторяются с ограниченным числом
|
|
339
|
+
попыток.
|
|
340
|
+
|
|
341
|
+
In-memory limiter не координирует разные процессы. Для этого предусмотрен интерфейс
|
|
342
|
+
`wbapi.rate_limit.RateLimiter`, через который позднее можно подключить общий backend.
|
|
343
|
+
|
|
344
|
+
## Разработка
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
uv sync
|
|
348
|
+
uv lock --check
|
|
349
|
+
uv run pytest -q --cov=wbapi --cov-report=term-missing
|
|
350
|
+
uv run ruff check src tests
|
|
351
|
+
uv run ruff format --check src tests
|
|
352
|
+
uv run python -m compileall -q src
|
|
353
|
+
uv build --no-sources
|
|
354
|
+
uv run ty check
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
## OpenAPI SWAGGER files
|
|
359
|
+
|
|
360
|
+
- Сваггер файлы находится в этой папке: `/Users/dmitry/Documents/code/_DATA/wb_api_swagger_files/`
|
|
361
|
+
|
|
362
|
+
- Чтобы обновить Swagger файл, запустить эту команду:
|
|
363
|
+
```bash
|
|
364
|
+
uv run --directory "/Users/dmitry/Documents/code/PYTHON_SCRIPTS_v2" \
|
|
365
|
+
SCRIPTS/wb_api_swagger_files_downloading.py
|
|
366
|
+
```
|