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.
- s_browserkit-0.0.1/.gitignore +8 -0
- s_browserkit-0.0.1/CHANGELOG.md +43 -0
- s_browserkit-0.0.1/LICENSE +21 -0
- s_browserkit-0.0.1/PKG-INFO +149 -0
- s_browserkit-0.0.1/README.md +115 -0
- s_browserkit-0.0.1/browserkit/__init__.py +185 -0
- s_browserkit-0.0.1/browserkit/antibot/__init__.py +131 -0
- s_browserkit-0.0.1/browserkit/antibot/_helpers.py +68 -0
- s_browserkit-0.0.1/browserkit/antibot/cdp.py +294 -0
- s_browserkit-0.0.1/browserkit/antibot/curl_cffi.py +231 -0
- s_browserkit-0.0.1/browserkit/antibot/page_evaluate.py +301 -0
- s_browserkit-0.0.1/browserkit/antibot/warmup_minter.py +255 -0
- s_browserkit-0.0.1/browserkit/browser/__init__.py +1002 -0
- s_browserkit-0.0.1/browserkit/browser/_cdp_state.py +43 -0
- s_browserkit-0.0.1/browserkit/browser/_engine.py +150 -0
- s_browserkit-0.0.1/browserkit/browser/camoufox.py +330 -0
- s_browserkit-0.0.1/browserkit/browser/capture_tap.py +719 -0
- s_browserkit-0.0.1/browserkit/browser/geometry.py +114 -0
- s_browserkit-0.0.1/browserkit/browser/launchers.py +229 -0
- s_browserkit-0.0.1/browserkit/browser/nodriver.py +284 -0
- s_browserkit-0.0.1/browserkit/browser/persistent.py +185 -0
- s_browserkit-0.0.1/browserkit/browser/state.py +308 -0
- s_browserkit-0.0.1/browserkit/capabilities.py +308 -0
- s_browserkit-0.0.1/browserkit/ports.py +105 -0
- s_browserkit-0.0.1/browserkit/proc.py +313 -0
- s_browserkit-0.0.1/browserkit/profile_health.py +340 -0
- s_browserkit-0.0.1/docs/README.md +28 -0
- s_browserkit-0.0.1/docs/prove_librarykit_without_browserkit.py +166 -0
- s_browserkit-0.0.1/pyproject.toml +92 -0
- s_browserkit-0.0.1/tests/test_capabilities.py +257 -0
- s_browserkit-0.0.1/tests/test_layering.py +209 -0
|
@@ -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
|
+
]
|