s-browserkit 0.0.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.
Files changed (31) hide show
  1. s_browserkit-0.0.1/.gitignore +8 -0
  2. s_browserkit-0.0.1/CHANGELOG.md +43 -0
  3. s_browserkit-0.0.1/LICENSE +21 -0
  4. s_browserkit-0.0.1/PKG-INFO +149 -0
  5. s_browserkit-0.0.1/README.md +115 -0
  6. s_browserkit-0.0.1/browserkit/__init__.py +185 -0
  7. s_browserkit-0.0.1/browserkit/antibot/__init__.py +131 -0
  8. s_browserkit-0.0.1/browserkit/antibot/_helpers.py +68 -0
  9. s_browserkit-0.0.1/browserkit/antibot/cdp.py +294 -0
  10. s_browserkit-0.0.1/browserkit/antibot/curl_cffi.py +231 -0
  11. s_browserkit-0.0.1/browserkit/antibot/page_evaluate.py +301 -0
  12. s_browserkit-0.0.1/browserkit/antibot/warmup_minter.py +255 -0
  13. s_browserkit-0.0.1/browserkit/browser/__init__.py +1002 -0
  14. s_browserkit-0.0.1/browserkit/browser/_cdp_state.py +43 -0
  15. s_browserkit-0.0.1/browserkit/browser/_engine.py +150 -0
  16. s_browserkit-0.0.1/browserkit/browser/camoufox.py +330 -0
  17. s_browserkit-0.0.1/browserkit/browser/capture_tap.py +719 -0
  18. s_browserkit-0.0.1/browserkit/browser/geometry.py +114 -0
  19. s_browserkit-0.0.1/browserkit/browser/launchers.py +229 -0
  20. s_browserkit-0.0.1/browserkit/browser/nodriver.py +284 -0
  21. s_browserkit-0.0.1/browserkit/browser/persistent.py +185 -0
  22. s_browserkit-0.0.1/browserkit/browser/state.py +308 -0
  23. s_browserkit-0.0.1/browserkit/capabilities.py +308 -0
  24. s_browserkit-0.0.1/browserkit/ports.py +105 -0
  25. s_browserkit-0.0.1/browserkit/proc.py +313 -0
  26. s_browserkit-0.0.1/browserkit/profile_health.py +340 -0
  27. s_browserkit-0.0.1/docs/README.md +28 -0
  28. s_browserkit-0.0.1/docs/prove_librarykit_without_browserkit.py +166 -0
  29. s_browserkit-0.0.1/pyproject.toml +92 -0
  30. s_browserkit-0.0.1/tests/test_capabilities.py +257 -0
  31. s_browserkit-0.0.1/tests/test_layering.py +209 -0
@@ -0,0 +1,8 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ dist/
7
+ build/
8
+ *.egg-info/
@@ -0,0 +1,43 @@
1
+ # Changelog — s-browserkit
2
+
3
+ Формат: [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/), версии — SemVer.
4
+
5
+ ## [0.0.1] — 2026-08-03
6
+
7
+ Выделение браузерного слоя из `s-librarykit`. Кода не написано
8
+ заново — он ПЕРЕЕХАЛ; по старым путям остались алиасы на те же модули.
9
+
10
+ ### Добавлено
11
+
12
+ * **Фасад возможностей** `browserkit.capabilities` — потребитель просит ЦЕЛЬ
13
+ (`Capability.STEALTH_SESSION` / `VISIBLE_LOGIN` / `PERSISTENT_WORK` /
14
+ `STEALTH_REQUEST` / `IN_PAGE_REQUEST` / `TRAFFIC_CAPTURE`), а не движок.
15
+ Соответствие «цель → движок» живёт в одной таблице `_DEFAULT_PLAN` внутри кита;
16
+ выбор — лестница кандидатов (первый установленный выигрывает), отсутствие всех
17
+ → `CapabilityUnavailable` с рецептом установки.
18
+ * `browserkit.ports` — `Transport` / `SyncTransport` / `BrowserEngineRole`:
19
+ контракты, которые киту нужны снаружи. Структурно совпадают с контрактами
20
+ сетевого слоя (проверено тестом), поэтому транспорты взаимозаменяемы.
21
+ * Ленивый корень (PEP 562): `import browserkit` не тянет ни подпакеты-реализации,
22
+ ни движки — можно спрашивать «умеешь ли ты стелс?», не платя импортом.
23
+
24
+ ### Переехало из librarykit (без изменения поведения)
25
+
26
+ * `browser/` — `_engine`, `geometry`, `launchers`, `persistent`, `camoufox`,
27
+ `nodriver`, `capture_tap`, `state`, `_cdp_state`;
28
+ * `antibot/` — `curl_cffi`, `cdp`, `page_evaluate`, `warmup_minter`, `_helpers`;
29
+ * `profile_health.py` — предстартовая проверка профиля и карантин;
30
+ * `proc.py` — запуск подпроцессов без всплывающих консолей.
31
+
32
+ ### Известный долг
33
+
34
+ * `browserkit/proc.py` — ДУБЛЬ `librarykit/proc.py`. Настоящий дом слоя — основание
35
+ (`corekit`), перенос отложен, чтобы не драться за файл с параллельным выделением
36
+ `netkit`/`authkit-client`. Дрейф копий ловится тестом
37
+ `tests/test_layering.py::test_proc_layer_has_not_drifted_from_the_orchestrator`.
38
+ * `browserkit/ports.py` — своя копия Protocol'ов транспорта; станет реэкспортом из
39
+ `netkit.contract`, когда сетевой кит будет опубликован.
40
+ * `librarykit/ephemeral.py` (минт без дискового профиля) ОСТАЛСЯ в оркестраторе:
41
+ он тянет `librarykit.session_fingerprint` (инвариант «один egress + один
42
+ профиль»), чей дом — session-слой, а не браузерный. Браузер он поднимает лениво,
43
+ через этот кит, поэтому слои не нарушены.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dmitry
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,149 @@
1
+ Metadata-Version: 2.4
2
+ Name: s-browserkit
3
+ Version: 0.0.1
4
+ Summary: Браузерный слой для Python-автоматизации: стелс-сессии, антибот-запросы и capture трафика через единый фасад возможностей, а не имя движка.
5
+ Author: Dmitry
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: antibot,automation,browser,cdp,http,playwright,stealth
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Requires-Python: >=3.11
19
+ Requires-Dist: httpx>=0.27
20
+ Requires-Dist: s-corekit>=0.0.1
21
+ Provides-Extra: antibot
22
+ Requires-Dist: curl-cffi>=0.7; extra == 'antibot'
23
+ Provides-Extra: browser
24
+ Requires-Dist: playwright>=1.40; extra == 'browser'
25
+ Provides-Extra: camoufox
26
+ Requires-Dist: camoufox>=0.4; extra == 'camoufox'
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
29
+ Requires-Dist: pytest>=8; extra == 'dev'
30
+ Requires-Dist: ruff>=0.8; extra == 'dev'
31
+ Provides-Extra: nodriver
32
+ Requires-Dist: nodriver>=0.50; extra == 'nodriver'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # s-browserkit — браузерный слой китов
36
+
37
+ Живой браузер, антибот и стелс — одним китом, который ставится **отдельно**.
38
+ Чистой API-интеграции (белый REST/GraphQL) он не нужен и больше не навязывается:
39
+ `librarykit` без `browserkit` полностью работоспособен.
40
+
41
+ ```
42
+ clikit adapterkit ветки-оболочки
43
+ \ /
44
+ \ /
45
+ librarykit ОРКЕСТРАТОР (сессии, лимиты, фабрики)
46
+ / \
47
+ browserkit … СЛОЙ-КИТ (браузер и антибот)
48
+ \ /
49
+ corekit ОСНОВАНИЕ (значения и чистые правила)
50
+ ```
51
+
52
+ ## Главное: просят ВОЗМОЖНОСТЬ, а не движок
53
+
54
+ ```python
55
+ import browserkit
56
+
57
+ backend = browserkit.open_browser(
58
+ browserkit.Capability.STEALTH_SESSION, # ЦЕЛЬ, а не «camoufox»
59
+ profile_dir=profile,
60
+ )
61
+ http = browserkit.make_requester(browserkit.Capability.STEALTH_REQUEST)
62
+ ```
63
+
64
+ Соответствие «цель → движок» живёт ровно в одной таблице внутри кита
65
+ (`browserkit/capabilities.py`). Anti-detect движок начал палиться — правится одна
66
+ строка, **ни один навык не меняется**, потому что ни один навык движок не назвал.
67
+
68
+ Возможности:
69
+
70
+ | Capability | Что нужно потребителю |
71
+ |---|---|
72
+ | `STEALTH_SESSION` | поднять браузер там, где обычный палится antibot'ом |
73
+ | `VISIBLE_LOGIN` | видимое окно: пароль/2FA человек вводит руками |
74
+ | `PERSISTENT_WORK` | обычная автоматизация в персистентном профиле |
75
+ | `STEALTH_REQUEST` | один HTTP-запрос с браузерным TLS-отпечатком, БЕЗ браузера |
76
+ | `IN_PAGE_REQUEST` | запрос изнутри живой страницы (её cookies/TLS/заголовки) |
77
+ | `TRAFFIC_CAPTURE` | нормализованный сетевой трафик страницы (реверс скрытого API) |
78
+
79
+ Диагностика — «чем закрыта каждая цель прямо сейчас»:
80
+
81
+ Сразу после `pip install s-browserkit` (без единого extra) все цели пусты — это и
82
+ есть нормальный ответ, а не поломка:
83
+
84
+ ```python
85
+ >>> import browserkit
86
+ >>> browserkit.describe_capabilities()
87
+ {'stealth_session': None, 'visible_login': None, 'persistent_work': None,
88
+ 'stealth_request': None, 'in_page_request': None, 'traffic_capture': None}
89
+ ```
90
+
91
+ `None` означает «не установлен ни один исполнитель». Попытка воспользоваться такой
92
+ целью даёт внятную ошибку с рецептом установки, а не голый `ModuleNotFoundError`:
93
+
94
+ ```python
95
+ >>> browserkit.make_requester(browserkit.Capability.STEALTH_REQUEST)
96
+ Traceback (most recent call last):
97
+ ...
98
+ browserkit.CapabilityUnavailable: Возможность 'stealth_request' недоступна:
99
+ ни один исполнитель не установлен. Поставь один из: `pip install 's-browserkit[antibot]'`.
100
+ ```
101
+
102
+ После `pip install 's-browserkit[antibot]'` та же строка вернёт готовый
103
+ исполнитель (`CurlCffiTransport`), а `describe_capabilities()['stealth_request']`
104
+ станет `'tls_impersonate'` — имя ИСПОЛНИТЕЛЯ цели, не пакета. Какие исполнители
105
+ стоят в очереди за каждой целью, видно заранее:
106
+
107
+ ```python
108
+ >>> browserkit.capability_plan(browserkit.Capability.STEALTH_SESSION)
109
+ ('camoufox', 'nodriver', 'cdp')
110
+ ```
111
+
112
+ Побеждает первый установленный; порядок переопределяется `set_capability_plan()`.
113
+
114
+ ## Состав
115
+
116
+ * `browserkit.browser` — лаунчеры (системный Chromium по CDP, персистентный
117
+ контекст), anti-detect / undetected бэкенды, `capture_tap` (нормализованный
118
+ event-tap над любым движком), `state` (storage-state, тёплый вход, детекторы
119
+ протухания), геометрия окна, брони портов;
120
+ * `browserkit.antibot` — исполнители HTTP-запроса, ходящие «как браузер»;
121
+ * `browserkit.profile_health` — предстартовая проверка профиля и карантин;
122
+ * `browserkit.proc` — запуск подпроцессов без всплывающих консолей;
123
+ * `browserkit.ports` — Protocol'ы, которых киту не хватает снаружи.
124
+
125
+ ## Установка
126
+
127
+ ```bash
128
+ pip install s-browserkit # без движков: фасад, антибот-контракты, здоровье профиля
129
+ pip install 's-browserkit[browser]' # + Playwright (CDP-attach, персистентный контекст)
130
+ pip install 's-browserkit[camoufox]' # + anti-detect Firefox (затем `camoufox fetch`)
131
+ pip install 's-browserkit[nodriver]' # + undetected Chromium
132
+ pip install 's-browserkit[antibot]' # + TLS/JA3-импersonate без браузера
133
+ ```
134
+
135
+ `import browserkit` не поднимает браузер и не импортирует ни одного движка:
136
+ подпакеты резолвятся лениво (PEP 562), вендоры — ещё позже, внутри бэкендов.
137
+
138
+ ## Совместимость
139
+
140
+ Прежние пути (`librarykit.browser.*`, `librarykit.antibot.*`,
141
+ `librarykit.profile_health`) продолжают работать: там оставлены **алиасы на те же
142
+ модули** (`sys.modules`), а не копии. Поэтому `monkeypatch`, `isinstance` и
143
+ `is`-сравнения ведут себя ровно как до переноса.
144
+
145
+ ## Тесты
146
+
147
+ ```bash
148
+ uv run --no-sync pytest -q # либо .venv\Scripts\python.exe -m pytest -q
149
+ ```
@@ -0,0 +1,115 @@
1
+ # s-browserkit — браузерный слой китов
2
+
3
+ Живой браузер, антибот и стелс — одним китом, который ставится **отдельно**.
4
+ Чистой API-интеграции (белый REST/GraphQL) он не нужен и больше не навязывается:
5
+ `librarykit` без `browserkit` полностью работоспособен.
6
+
7
+ ```
8
+ clikit adapterkit ветки-оболочки
9
+ \ /
10
+ \ /
11
+ librarykit ОРКЕСТРАТОР (сессии, лимиты, фабрики)
12
+ / \
13
+ browserkit … СЛОЙ-КИТ (браузер и антибот)
14
+ \ /
15
+ corekit ОСНОВАНИЕ (значения и чистые правила)
16
+ ```
17
+
18
+ ## Главное: просят ВОЗМОЖНОСТЬ, а не движок
19
+
20
+ ```python
21
+ import browserkit
22
+
23
+ backend = browserkit.open_browser(
24
+ browserkit.Capability.STEALTH_SESSION, # ЦЕЛЬ, а не «camoufox»
25
+ profile_dir=profile,
26
+ )
27
+ http = browserkit.make_requester(browserkit.Capability.STEALTH_REQUEST)
28
+ ```
29
+
30
+ Соответствие «цель → движок» живёт ровно в одной таблице внутри кита
31
+ (`browserkit/capabilities.py`). Anti-detect движок начал палиться — правится одна
32
+ строка, **ни один навык не меняется**, потому что ни один навык движок не назвал.
33
+
34
+ Возможности:
35
+
36
+ | Capability | Что нужно потребителю |
37
+ |---|---|
38
+ | `STEALTH_SESSION` | поднять браузер там, где обычный палится antibot'ом |
39
+ | `VISIBLE_LOGIN` | видимое окно: пароль/2FA человек вводит руками |
40
+ | `PERSISTENT_WORK` | обычная автоматизация в персистентном профиле |
41
+ | `STEALTH_REQUEST` | один HTTP-запрос с браузерным TLS-отпечатком, БЕЗ браузера |
42
+ | `IN_PAGE_REQUEST` | запрос изнутри живой страницы (её cookies/TLS/заголовки) |
43
+ | `TRAFFIC_CAPTURE` | нормализованный сетевой трафик страницы (реверс скрытого API) |
44
+
45
+ Диагностика — «чем закрыта каждая цель прямо сейчас»:
46
+
47
+ Сразу после `pip install s-browserkit` (без единого extra) все цели пусты — это и
48
+ есть нормальный ответ, а не поломка:
49
+
50
+ ```python
51
+ >>> import browserkit
52
+ >>> browserkit.describe_capabilities()
53
+ {'stealth_session': None, 'visible_login': None, 'persistent_work': None,
54
+ 'stealth_request': None, 'in_page_request': None, 'traffic_capture': None}
55
+ ```
56
+
57
+ `None` означает «не установлен ни один исполнитель». Попытка воспользоваться такой
58
+ целью даёт внятную ошибку с рецептом установки, а не голый `ModuleNotFoundError`:
59
+
60
+ ```python
61
+ >>> browserkit.make_requester(browserkit.Capability.STEALTH_REQUEST)
62
+ Traceback (most recent call last):
63
+ ...
64
+ browserkit.CapabilityUnavailable: Возможность 'stealth_request' недоступна:
65
+ ни один исполнитель не установлен. Поставь один из: `pip install 's-browserkit[antibot]'`.
66
+ ```
67
+
68
+ После `pip install 's-browserkit[antibot]'` та же строка вернёт готовый
69
+ исполнитель (`CurlCffiTransport`), а `describe_capabilities()['stealth_request']`
70
+ станет `'tls_impersonate'` — имя ИСПОЛНИТЕЛЯ цели, не пакета. Какие исполнители
71
+ стоят в очереди за каждой целью, видно заранее:
72
+
73
+ ```python
74
+ >>> browserkit.capability_plan(browserkit.Capability.STEALTH_SESSION)
75
+ ('camoufox', 'nodriver', 'cdp')
76
+ ```
77
+
78
+ Побеждает первый установленный; порядок переопределяется `set_capability_plan()`.
79
+
80
+ ## Состав
81
+
82
+ * `browserkit.browser` — лаунчеры (системный Chromium по CDP, персистентный
83
+ контекст), anti-detect / undetected бэкенды, `capture_tap` (нормализованный
84
+ event-tap над любым движком), `state` (storage-state, тёплый вход, детекторы
85
+ протухания), геометрия окна, брони портов;
86
+ * `browserkit.antibot` — исполнители HTTP-запроса, ходящие «как браузер»;
87
+ * `browserkit.profile_health` — предстартовая проверка профиля и карантин;
88
+ * `browserkit.proc` — запуск подпроцессов без всплывающих консолей;
89
+ * `browserkit.ports` — Protocol'ы, которых киту не хватает снаружи.
90
+
91
+ ## Установка
92
+
93
+ ```bash
94
+ pip install s-browserkit # без движков: фасад, антибот-контракты, здоровье профиля
95
+ pip install 's-browserkit[browser]' # + Playwright (CDP-attach, персистентный контекст)
96
+ pip install 's-browserkit[camoufox]' # + anti-detect Firefox (затем `camoufox fetch`)
97
+ pip install 's-browserkit[nodriver]' # + undetected Chromium
98
+ pip install 's-browserkit[antibot]' # + TLS/JA3-импersonate без браузера
99
+ ```
100
+
101
+ `import browserkit` не поднимает браузер и не импортирует ни одного движка:
102
+ подпакеты резолвятся лениво (PEP 562), вендоры — ещё позже, внутри бэкендов.
103
+
104
+ ## Совместимость
105
+
106
+ Прежние пути (`librarykit.browser.*`, `librarykit.antibot.*`,
107
+ `librarykit.profile_health`) продолжают работать: там оставлены **алиасы на те же
108
+ модули** (`sys.modules`), а не копии. Поэтому `monkeypatch`, `isinstance` и
109
+ `is`-сравнения ведут себя ровно как до переноса.
110
+
111
+ ## Тесты
112
+
113
+ ```bash
114
+ uv run --no-sync pytest -q # либо .venv\Scripts\python.exe -m pytest -q
115
+ ```
@@ -0,0 +1,185 @@
1
+ """browserkit — БРАУЗЕРНЫЙ СЛОЙ экосистемы китов: живой браузер, антибот, стелс.
2
+
3
+ ФОРМА ГРАФА::
4
+
5
+ clikit adapterkit <- ветки-оболочки
6
+ \\ /
7
+ \\ /
8
+ librarykit <- ОРКЕСТРАТОР (сессии, лимиты, фабрики)
9
+ / \\
10
+ browserkit … <- СЛОЙ-КИТ (браузер и антибот)
11
+ \\ /
12
+ corekit <- ОСНОВАНИЕ (значения и чистые правила)
13
+
14
+ Что сюда переехало из `librarykit` (реальный код, не копия):
15
+
16
+ * :mod:`browserkit.browser` — лаунчеры (системный Chromium по CDP, персистентный
17
+ контекст), anti-detect и undetected-бэкенды, `capture_tap` (нормализованный
18
+ event-tap над любым движком), `state` (storage-state, тёплый вход, детекторы
19
+ протухания), геометрия окна и брони портов;
20
+ * :mod:`browserkit.antibot` — исполнители запроса, которые ходят «как браузер»:
21
+ TLS/JA3-импersonate без браузера, запрос через CDP-бэкенд, in-page fetch,
22
+ warmup-минтер одноразовых токенов;
23
+ * :mod:`browserkit.profile_health` — предстартовая проверка браузерного профиля
24
+ (битые Cookies/Preferences, распухший кэш) и карантин;
25
+ * :mod:`browserkit.proc` — запуск подпроцессов без всплывающих консолей;
26
+ * :mod:`browserkit.ports` — Protocol'ы, которых киту не хватает снаружи.
27
+
28
+ ★ ГЛАВНОЕ — ФАСАД ВОЗМОЖНОСТЕЙ (:mod:`browserkit.capabilities`). Потребитель
29
+ просит ЦЕЛЬ, а не движок::
30
+
31
+ import browserkit
32
+ backend = browserkit.open_browser(browserkit.Capability.STEALTH_SESSION,
33
+ profile_dir=profile)
34
+ http = browserkit.make_requester(browserkit.Capability.STEALTH_REQUEST)
35
+
36
+ Замена движка (anti-detect Firefox начал палиться) — правка ОДНОЙ таблицы внутри
37
+ кита; ни один навык не меняется, потому что ни один навык не назвал движок.
38
+
39
+ ЛЕНИВОСТЬ. `import browserkit` НЕ поднимает браузер и НЕ импортирует ни одного
40
+ тяжёлого движка: `browser`/`antibot` — реальные подпакеты, но резолвятся через
41
+ PEP 562 по факту обращения; сами вендоры (playwright/camoufox/nodriver/curl_cffi)
42
+ тянутся ещё позже — внутри бэкендов, где уже бросается внятное
43
+ `BrowserUnavailable` с инструкцией по установке.
44
+
45
+ СОВМЕСТИМОСТЬ. Прежние пути импорта (`librarykit.browser.*`,
46
+ `librarykit.antibot.*`, `librarykit.profile_health`) продолжают работать: там
47
+ оставлены АЛИАСЫ НА ТЕ ЖЕ модули (`sys.modules`), а не копии, поэтому
48
+ monkeypatch, `isinstance` и `is`-сравнения ведут себя как до переноса.
49
+ """
50
+ from __future__ import annotations
51
+
52
+ from importlib import import_module as _import_module
53
+ from importlib.util import find_spec as _find_spec
54
+ from typing import TYPE_CHECKING
55
+
56
+ from browserkit.capabilities import (
57
+ Capability,
58
+ CapabilityUnavailable,
59
+ capability_plan,
60
+ describe_capabilities,
61
+ is_capability_available,
62
+ make_requester,
63
+ open_browser,
64
+ set_capability_plan,
65
+ )
66
+ from browserkit.ports import (
67
+ BrowserEngineRole,
68
+ HttpRequestExecutor,
69
+ SyncHttpRequestExecutor,
70
+ SyncTransport,
71
+ Transport,
72
+ )
73
+
74
+ __version__ = "0.0.1"
75
+
76
+ if TYPE_CHECKING: # pragma: no cover - только статическая видимость для IDE/mypy
77
+ from typing import Any
78
+
79
+ from browserkit.antibot import (
80
+ CurlCffiTransport as CurlCffiTransport,
81
+ )
82
+ from browserkit.antibot import (
83
+ TokenHarvest as TokenHarvest,
84
+ )
85
+ from browserkit.antibot import (
86
+ WarmupTokenMinter as WarmupTokenMinter,
87
+ )
88
+ from browserkit.browser import (
89
+ BrowserUnavailable as BrowserUnavailable,
90
+ )
91
+ from browserkit.profile_health import (
92
+ ProfileHealth as ProfileHealth,
93
+ )
94
+ from browserkit.profile_health import (
95
+ check_profile as check_profile,
96
+ )
97
+ from browserkit.profile_health import (
98
+ ensure_healthy_profile as ensure_healthy_profile,
99
+ )
100
+
101
+ #: Ленивые ИМЕНА корня: имя → модуль-источник. Держим их ленивыми по той же
102
+ #: причине, что и в `librarykit`: `import browserkit` обязан стоить дёшево, чтобы
103
+ #: фасад возможностей можно было спрашивать («умеешь ли ты стелс?»), не поднимая
104
+ #: ни одного движка.
105
+ _LAZY_NAMES: dict[str, str] = {
106
+ # browser
107
+ "BrowserUnavailable": "browserkit.browser",
108
+ "build_capture_backend": "browserkit.browser",
109
+ "attach_capture_tap": "browserkit.browser",
110
+ "camoufox_available": "browserkit.browser",
111
+ "nodriver_available": "browserkit.browser",
112
+ "playwright_available": "browserkit.browser",
113
+ # antibot
114
+ "CurlCffiTransport": "browserkit.antibot",
115
+ "TokenHarvest": "browserkit.antibot",
116
+ "WarmupTokenMinter": "browserkit.antibot",
117
+ "page_fetch": "browserkit.antibot",
118
+ # profile_health
119
+ "ProfileHealth": "browserkit.profile_health",
120
+ "ProfileProblem": "browserkit.profile_health",
121
+ "check_profile": "browserkit.profile_health",
122
+ "ensure_healthy_profile": "browserkit.profile_health",
123
+ "quarantine_profile": "browserkit.profile_health",
124
+ # proc
125
+ "ProcResult": "browserkit.proc",
126
+ }
127
+
128
+ __all__ = [
129
+ # ★ фасад возможностей — рекомендуемый вход
130
+ "Capability",
131
+ "CapabilityUnavailable",
132
+ "open_browser",
133
+ "make_requester",
134
+ "is_capability_available",
135
+ "describe_capabilities",
136
+ "capability_plan",
137
+ "set_capability_plan",
138
+ # порты (контракты)
139
+ "Transport",
140
+ "SyncTransport",
141
+ "HttpRequestExecutor",
142
+ "SyncHttpRequestExecutor",
143
+ "BrowserEngineRole",
144
+ # ленивые имена реализаций (совместимость с прежним корнем librarykit)
145
+ "BrowserUnavailable",
146
+ "build_capture_backend",
147
+ "attach_capture_tap",
148
+ "camoufox_available",
149
+ "nodriver_available",
150
+ "playwright_available",
151
+ "CurlCffiTransport",
152
+ "TokenHarvest",
153
+ "WarmupTokenMinter",
154
+ "page_fetch",
155
+ "ProfileHealth",
156
+ "ProfileProblem",
157
+ "check_profile",
158
+ "ensure_healthy_profile",
159
+ "quarantine_profile",
160
+ "ProcResult",
161
+ ]
162
+
163
+
164
+ def __getattr__(name: str) -> Any: # `Any` — только под TYPE_CHECKING
165
+ """Ленивый резолв (PEP 562): сперва карта имён, затем РЕАЛЬНЫЙ подмодуль."""
166
+ target = _LAZY_NAMES.get(name)
167
+ if target is None:
168
+ if not name.startswith("_"):
169
+ try:
170
+ found = _find_spec(f"{__name__}.{name}") is not None
171
+ except (ImportError, ValueError):
172
+ found = False
173
+ if found:
174
+ module = _import_module(f"{__name__}.{name}")
175
+ globals()[name] = module
176
+ return module
177
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
178
+ value = getattr(_import_module(target), name)
179
+ globals()[name] = value
180
+ return value
181
+
182
+
183
+ def __dir__() -> list[str]:
184
+ """Полная поверхность корня: жадные атрибуты + `__all__` + ленивые имена."""
185
+ return sorted(set(globals()) | set(__all__) | set(_LAZY_NAMES))
@@ -0,0 +1,131 @@
1
+ """Антибот-стратегия как ВЫБОР ТРАНСПОРТА, а не переписывание кода вызова.
2
+
3
+ ДОМ ПАКЕТА — БРАУЗЕРНЫЙ КИТ. Раньше код жил в `librarykit`
4
+ (а до того в `adapterkit.antibot`); по обоим старым путям остались АЛИАСЫ на эти
5
+ же модули. curl-cffi — ОПЦИОНАЛЬНЫЙ extra `[antibot]` (ленивый импорт внутри
6
+ транспорта), кит без extras не падает на import пакета.
7
+
8
+ Идея: антибот — это не «headless vs
9
+ residential proxy», а пять уровней архитектуры (Tier 0-4). Сервис «опт-инится» в
10
+ нужный уровень, просто выбрав транспорт у эндпоинта (`Endpoint.transport`):
11
+
12
+ - `TransportKind.HTTP` → `HttpxTransport` (Tier 0, обычный httpx) — другой модуль;
13
+ - `TransportKind.CURL_CFFI` → `CurlCffiTransport` (Tier 1-3, JA3-имитация Chrome);
14
+ - `TransportKind.BROWSER` → `CdpTransport` (Tier 0-1, реальный системный Edge/Chrome).
15
+
16
+ Каждый из них реализует ОДИН И ТОТ ЖЕ контракт `ports.Transport`
17
+ (`async request(method, url, *, ...) -> httpx.Response`), поэтому вышестоящий код
18
+ (`HttpClient`/`ErrorMapper`/адаптер) не различает транспорты — меняется только
19
+ TLS-fingerprint и источник заголовков, но НЕ форма вызова. Маппинг кодов в
20
+ доменные ошибки — НЕ здесь (это `HttpClient`/`ErrorMapper`); транспорт бросает
21
+ `TransportError` только на сетевых сбоях и возвращает сырой `httpx.Response`.
22
+
23
+ Этот пакет — ТОНКИЙ ФАСАД: механически разнесён по ответственности на подмодули
24
+ (`._helpers` — обёртка ошибок + нормализация ответа; `.curl_cffi` —
25
+ `CurlCffiTransport`/`pick_chrome_impersonate`; `.cdp` — `CdpTransport`/
26
+ `SyncCdpTransport` + backend-контракты; `.page_evaluate` — `PageEvaluateTransport`/
27
+ `page_fetch`/`_build_fetch_spec`). Публичный API прежний — все прежние имена
28
+ реэкспортированы.
29
+
30
+ Опциональные зависимости (`curl_cffi`, `playwright`/`patchright`) импортируются
31
+ ЛЕНИВО — модуль импортируется без них (graceful), а понятная ошибка о
32
+ недостающем пакете возникает только при реальной попытке использовать транспорт.
33
+
34
+ ────────────────────────────────────────────────────────────────────────────────
35
+ Tier 0-4 — какой транспорт выбрать под нагрузку и характер защиты
36
+ ────────────────────────────────────────────────────────────────────────────────
37
+ Антибот эскалируется уровнями; не «беги за прокси первым», а подбирай по симптому.
38
+
39
+ - **Tier 0** — видимый браузер (headed) / обычный httpx. RAM ~800 МБ. dev / debug /
40
+ первый bootstrap-логин. Здесь: `CdpTransport(headless=False)` / `HttpxTransport`.
41
+ - **Tier 1** — headless реальный браузер (CDP→Edge `--headless=new`), ~700 мс,
42
+ 500-800 МБ; prod 1-100 calls/day, антибот средний. Здесь: `CdpTransport(headless=True)`.
43
+ Вариант того же уровня — curl-cffi (JA3-имитация Chrome), ~10 МБ: JA3/TLS-детект
44
+ (Akamai, Google) БЕЗ signing. Здесь: `CurlCffiTransport`.
45
+ - **Tier 2** — jsdom one-shot subprocess, 3-5 с, 30-50 МБ; init-флоу известен,
46
+ sign-only без рендера. Вне этого модуля.
47
+ - **Tier 3** — jsdom persistent daemon (RPC), ~50 мс, ~80 МБ; prod sweet-spot
48
+ 100-10K calls/day. Вне этого модуля.
49
+ - **Tier 4** — pure native (Python/Go/Rust), ~1 мс, ~10 МБ; bundle pinned forever
50
+ + есть команда сопровождения. Вне этого модуля.
51
+
52
+ Дерево решений:
53
+ - read-only без подписи запроса → Tier 0/1 (или официальный REST, если есть);
54
+ - мутирующий вызов с подписью (X-Sign/X-Bogus/_signature/msToken):
55
+ * bootstrap-логин → `CdpTransport` (реальный Edge ставит нативные
56
+ `sec-fetch-*` / Client-Hints / TLS — patchright их НЕ шлёт);
57
+ * массовая prod-нагрузка → Tier 3 jsdom-daemon (вне adapterkit), а Python-сторона
58
+ шлёт уже подписанный URL через `CurlCffiTransport` (JA3 совпадает).
59
+
60
+ Симптом → направление (три ОРТОГОНАЛЬНЫХ оси, выбирай по симптому, не «по простоте»):
61
+ - `403`/`Access denied` на любом IP, RU-регион заблокирован → IP/proxy (дорого, last resort);
62
+ - видимый captcha-виджет (hCaptcha/Turnstile) → внешний solver (reCAPTCHA v3 — score-based,
63
+ solver НЕ работает, только реальный браузер `CdpTransport`);
64
+ - `signatures don't match` / `isTTwidDecryptedFail` / «unusual activity» / нет
65
+ `sec-fetch-*` → архитектура: `CdpTransport` (реальный Edge);
66
+ - `ECONNRESET` / другой контент на JA3-detection endpoint → архитектура: `CurlCffiTransport`.
67
+
68
+ Железные правила (анти-паттерны):
69
+ - версию Chrome НЕ пинить: захардкоженный `chrome131` через 4+ версии — сигнал «бот»
70
+ (`CurlCffiTransport` подбирает свежайший доступный профиль автоматически);
71
+ - UA major == sec-ch-ua major == curl-cffi `impersonate` major — рассинхрон палится;
72
+ - `CdpTransport` берёт версию браузера сам — держи системный Edge/Chrome обновлённым.
73
+ """
74
+ from __future__ import annotations
75
+
76
+ from ._helpers import ( # noqa: F401 # seam: тесты/потребители импортируют приватные
77
+ _as_transport_error,
78
+ _to_httpx_response,
79
+ )
80
+ from .cdp import (
81
+ CdpBrowserBackend,
82
+ CdpTransport,
83
+ SyncCdpBrowserBackend,
84
+ SyncCdpTransport,
85
+ )
86
+ from .curl_cffi import CurlCffiTransport, pick_chrome_impersonate
87
+ from .page_evaluate import (
88
+ PageEvaluateTransport,
89
+ PageFetchResult,
90
+ _build_fetch_spec, # noqa: F401 # seam: browser+тесты импортируют
91
+ page_fetch,
92
+ sync_page_fetch,
93
+ )
94
+ from .warmup_minter import TokenHarvest, WarmupTokenMinter
95
+
96
+ # Первичные ролевые имена исполнителей запроса (роль снаружи, вендор — внутри):
97
+ # ImpersonatedHttpExecutor — TLS/JA3-impersonate (curl-cffi) [было CurlCffiTransport];
98
+ # BrowserRequestExecutor — запрос «руками браузера» по CDP/CamouFox [было CdpTransport];
99
+ # BorrowedPageRequestExecutor — запрос в ЗАИМСТВОВАННОЙ странице [было PageEvaluateTransport];
100
+ # BrowserRequester — роль «умеет выполнить запрос в браузере» ПОВЕРХ живой
101
+ # contract.BrowserSession (fetch, не ЖЦ) [было CdpBrowserBackend].
102
+ # Старые имена остаются deprecated-псевдонимами (ничего не удалено).
103
+ ImpersonatedHttpExecutor = CurlCffiTransport
104
+ BrowserRequestExecutor = CdpTransport
105
+ SyncBrowserRequestExecutor = SyncCdpTransport
106
+ BorrowedPageRequestExecutor = PageEvaluateTransport
107
+ BrowserRequester = CdpBrowserBackend
108
+ SyncBrowserRequester = SyncCdpBrowserBackend
109
+
110
+ __all__ = [
111
+ "CurlCffiTransport",
112
+ "CdpTransport",
113
+ "CdpBrowserBackend",
114
+ "SyncCdpTransport",
115
+ "SyncCdpBrowserBackend",
116
+ "PageEvaluateTransport",
117
+ "PageFetchResult",
118
+ "page_fetch",
119
+ "sync_page_fetch",
120
+ "pick_chrome_impersonate",
121
+ # warmup-минт одноразовых токенов тёплой страницей (генерик chatgpt-паттерна)
122
+ "WarmupTokenMinter",
123
+ "TokenHarvest",
124
+ # первичные ролевые имена (псевдонимы вендор-классов выше)
125
+ "ImpersonatedHttpExecutor",
126
+ "BrowserRequestExecutor",
127
+ "SyncBrowserRequestExecutor",
128
+ "BorrowedPageRequestExecutor",
129
+ "BrowserRequester",
130
+ "SyncBrowserRequester",
131
+ ]