88from camoufox .async_api import AsyncCamoufox
99from human_requests import HumanBrowser , HumanContext , HumanPage
1010from human_requests .abstraction import FetchResponse , HttpMethod , Proxy
11+ from human_requests .network_analyzer .anomaly_sniffer import HeaderAnomalySniffer , WaitSource , WaitHeader
1112
1213from .endpoints .advertising import ClassAdvertising
1314from .endpoints .catalog import ClassCatalog
@@ -48,6 +49,9 @@ class PyaterochkaAPI:
4849 """Внутренний контекст сессии браузера"""
4950 page : HumanPage = field (init = False , repr = False )
5051 """Внутренний страница сессии браузера"""
52+
53+ unstandard_headers : dict [str , str ] = {}
54+ """Список нестандартных заголовков пойманных при инициализации"""
5155
5256 Geolocation : ClassGeolocation = field (init = False )
5357 """API для работы с геолокацией."""
@@ -83,10 +87,38 @@ async def _warmup(self) -> None:
8387 self .ctx = await self .session .new_context ()
8488 self .page = await self .ctx .new_page ()
8589
90+ sniffer = HeaderAnomalySniffer (
91+ # доп. вайтлист, если нужно
92+ extra_request_allow = ["x-forwarded-for" , "x-real-ip" ],
93+ extra_response_allow = [],
94+ # нормализуем URL: без фрагмента, но с query
95+ #url_normalizer=lambda u: u.split("#", 1)[0],
96+ include_subresources = True , # или False, если интересны только документы
97+ url_filter = lambda u : u .startswith ("https://5d.5ka.ru/" )
98+ )
99+ await sniffer .start (self .ctx )
100+
86101 await self .page .goto ("https://5ka.ru" , wait_until = "load" )
87102 await self .page .wait_for_selector (selector = "next-route-announcer" , state = "attached" )
88103 await self .page .wait_for_load_state ('networkidle' )
89104
105+ await sniffer .wait (
106+ tasks = [
107+ WaitHeader (
108+ source = WaitSource .REQUEST ,
109+ headers = [
110+ "x-app-version" ,
111+ "x-device-id" ,
112+ "x-platform"
113+ ]
114+ )
115+ ],
116+ timeout_ms = self .timeout_ms
117+ )
118+
119+ result = await sniffer .complete ()
120+ self .unstandard_headers = result .get ("https://5d.5ka.ru" )
121+
90122 async def __aexit__ (self , * exc ):
91123 """Выход из контекстного менеджера с закрытием сессии."""
92124 await self .close ()
@@ -99,12 +131,18 @@ async def delivery_panel_store(self) -> dict:
99131 """Текущий адрес доставке (при инициализации проставляется автоматически)"""
100132 return json .loads ((await self .page .local_storage ()).get ("DeliveryPanelStore" ))
101133
134+ async def device_id (self ) -> str :
135+ """Анонимный (так как в библиотеке нет возможности авторизации) индефекатор пользователя,
136+ который отправляется на сервер почти с каждым запросом (изменить нельзя)."""
137+ return str ((await self .page .local_storage ()).get ("deviceId" ))
138+
102139 async def _request (
103140 self ,
104141 method : HttpMethod ,
105142 url : str ,
106143 * ,
107144 json_body : Any | None = None ,
145+ add_unstandard_headers : bool = True
108146 ) -> FetchResponse :
109147 """Выполнить HTTP-запрос через внутреннюю сессию.
110148
@@ -116,6 +154,7 @@ async def _request(
116154 json_body: Тело запроса в формате JSON (опционально)
117155 """
118156 # Единая точка входа в чужую библиотеку для удобства
157+ # TODO: пройтись по библиотеке и проверить где add_unstandard_headers должен быть false
119158 print (url )
120159 resp : FetchResponse = await self .page .fetch (
121160 url = url ,
@@ -124,7 +163,7 @@ async def _request(
124163 mode = "cors" ,
125164 timeout_ms = self .timeout_ms ,
126165 referrer = self .MAIN_SITE_URL ,
127- headers = {"Accept" : "application/json, text/plain, */*" },
166+ headers = {"Accept" : "application/json, text/plain, */*" }. update ( self . unstandard_headers if add_unstandard_headers else {}) ,
128167 )
129168
130169 return resp
0 commit comments