11from __future__ import annotations
22
3- import os
3+ import asyncio
4+ from collections import defaultdict
45from dataclasses import dataclass , field
56from typing import Any
67
1314 api_child_field ,
1415)
1516from human_requests .abstraction import FetchResponse , HttpMethod , Proxy
16- from playwright . async_api import TimeoutError as PWTimeoutError
17+ from human_requests . network_analyzer . anomaly_sniffer import HeaderAnomalySniffer
1718
1819from .endpoints .advertising import ClassAdvertising
1920from .endpoints .catalog import ClassCatalog
@@ -31,12 +32,15 @@ class ChizhikAPI(ApiParent):
3132 """Время ожидания ответа от сервера в миллисекундах."""
3233 headless : bool = True
3334 """Запускать браузер в headless режиме?"""
35+ test_mode : bool = False
36+ """Режим тестирования предполагает более глубокий _warmup который не требуется для обычного использования"""
3437 proxy : str | dict | Proxy | None = field (default_factory = Proxy .from_env )
3538 """Прокси-сервер для всех запросов (если нужен). По умолчанию берет из окружения (если есть).
3639 Принимает как формат Playwright, так и строчный формат."""
3740 browser_opts : dict [str , Any ] = field (default_factory = dict )
3841 """Дополнительные опции для браузера (см. https://camoufox.com/python/installation/)"""
39- CATALOG_URL : str = "https://app.chizhik.club/api/v1"
42+ API_URL : str = "https://app.chizhik.club/api"
43+ DELIVERY_API_URL : str = "https://app.chizhik.club/delivery/api"
4044 """URL для работы с каталогом."""
4145 MAIN_SITE_URL : str = "https://chizhik.club/catalog/"
4246 """URL главной страницы сайта."""
@@ -51,6 +55,11 @@ class ChizhikAPI(ApiParent):
5155 page : HumanPage = field (init = False , repr = False )
5256 """Внутренний страница сессии браузера"""
5357
58+ unstandard_headers : dict [str , str ] = field (init = False , repr = False )
59+ """Список нестандартных заголовков пойманных при инициализации"""
60+ unstandard_urls : dict [str , list [str ]] = field (init = False , repr = False )
61+ """Список нестандартных заголовков пойманных при инициализации"""
62+
5463 Geolocation : ClassGeolocation = api_child_field (ClassGeolocation )
5564 """API для работы с геолокацией."""
5665 Catalog : ClassCatalog = api_child_field (ClassCatalog )
@@ -74,30 +83,63 @@ async def _warmup(self) -> None:
7483 proxy = px .as_dict (),
7584 ** self .browser_opts ,
7685 block_images = True ,
86+ i_know_what_im_doing = True ,
7787 ).start ()
7888
7989 self .session = HumanBrowser .replace (br )
8090 self .ctx = await self .session .new_context ()
8191 self .page = await self .ctx .new_page ()
8292 self .page .on_error_screenshot_path = "screenshot.png"
83- await self .page .goto (self .CATALOG_URL , wait_until = "networkidle" )
84-
85- ok = False
86- try_count = 3
87- while not ok and try_count > 0 :
88- try_count -= 1
89- try :
90- await self .page .wait_for_selector (
91- "pre" , timeout = self .timeout_ms , state = "attached"
92- )
93- ok = True
94- except PWTimeoutError :
95- await self .page .reload ()
96- if not ok :
97- raise RuntimeError (await self .page .content ())
98-
99- # await self.page.wait_for_load_state("networkidle")
100- # await asyncio.sleep(3)
93+
94+ if self .test_mode :
95+ sniffer = HeaderAnomalySniffer (
96+ include_subresources = True , # или False, если интересны только документы
97+ url_filter = lambda u : u .startswith (self .API_URL ),
98+ )
99+ await sniffer .start (self .ctx )
100+
101+ collected = {}
102+
103+ def on_request (request ):
104+ if request .url .startswith (self .API_URL ):
105+ collected [request .url ] = request .headers
106+
107+ self .ctx .on ("request" , on_request )
108+
109+ await self .page .goto (self .MAIN_SITE_URL , wait_until = "networkidle" )
110+ await self .page .wait_for_selector ("next-route-announcer" , state = "attached" )
111+ await asyncio .sleep (1 )
112+ await self .page .locator (
113+ 'main a[data-qa^="sidebar-sub-category-"][data-qa$="-link"]'
114+ ).first .click ()
115+ await self .page .locator (
116+ 'main div[itemtype="https://schema.org/Product"]'
117+ ).first .click ()
118+ await asyncio .sleep (1 )
119+ await self .page .wait_for_load_state ("load" )
120+
121+ await self .ctx .unroute ("**/api/**" , on_request )
122+ result_sniffer = await sniffer .complete ()
123+
124+ # Результат: {заголовок: [уникальные значения]}
125+ result = defaultdict (set )
126+
127+ # Проходим по всем URL в 'request'
128+ for _url , headers in result_sniffer ["request" ].items ():
129+ for header , values in headers .items ():
130+ result [header ].update (
131+ values
132+ ) # добавляем значения, set уберёт дубли
133+
134+ # Преобразуем set обратно в list
135+ self .unstandard_headers = {k : list (v )[0 ] for k , v in result .items ()}
136+ self .unstandard_urls = collected
137+
138+ await self .page .goto (f"{ self .API_URL } /v1" , wait_until = "networkidle" )
139+
140+ await self .page .wait_for_selector (
141+ "pre" , timeout = self .timeout_ms , state = "attached"
142+ )
101143
102144 async def __aexit__ (self , * exc ):
103145 """Выход из контекстного менеджера с закрытием сессии."""
0 commit comments