notify-tls-client 2.0.0__py3-none-any.whl → 2.0.2__py3-none-any.whl
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.
- notify_tls_client/config/recovery_config.py +6 -1
- notify_tls_client/core/client/decorators.py +52 -27
- notify_tls_client/core/client_identifiers_manager.py +12 -9
- notify_tls_client/core/notifytlsclient.py +21 -20
- notify_tls_client/core/proxiesmanager/proxiesmanager.py +22 -13
- notify_tls_client/tls_client/response.py +2 -2
- notify_tls_client-2.0.2.dist-info/METADATA +338 -0
- {notify_tls_client-2.0.0.dist-info → notify_tls_client-2.0.2.dist-info}/RECORD +10 -10
- notify_tls_client-2.0.0.dist-info/METADATA +0 -38
- {notify_tls_client-2.0.0.dist-info → notify_tls_client-2.0.2.dist-info}/WHEEL +0 -0
- {notify_tls_client-2.0.0.dist-info → notify_tls_client-2.0.2.dist-info}/top_level.txt +0 -0
|
@@ -44,10 +44,15 @@ class RecoveryConfig:
|
|
|
44
44
|
instantiate_new_client_on_forbidden_response: bool = False
|
|
45
45
|
instantiate_new_client_on_exception: bool = False
|
|
46
46
|
change_client_identifier_on_forbidden_response: bool = False
|
|
47
|
-
status_codes_to_forbidden_response_handle:
|
|
47
|
+
status_codes_to_forbidden_response_handle: tuple[int, ...] = field(default_factory=lambda: (403,))
|
|
48
48
|
|
|
49
49
|
def __post_init__(self):
|
|
50
50
|
"""Valida os parâmetros de configuração."""
|
|
51
|
+
# Converter lista para tuple se necessário (compatibilidade)
|
|
52
|
+
if isinstance(self.status_codes_to_forbidden_response_handle, list):
|
|
53
|
+
object.__setattr__(self, 'status_codes_to_forbidden_response_handle',
|
|
54
|
+
tuple(self.status_codes_to_forbidden_response_handle))
|
|
55
|
+
|
|
51
56
|
if not self.status_codes_to_forbidden_response_handle:
|
|
52
57
|
raise ValueError("status_codes_to_forbidden_response_handle não pode ser vazio")
|
|
53
58
|
|
|
@@ -3,6 +3,8 @@ from datetime import datetime
|
|
|
3
3
|
from typing import Optional
|
|
4
4
|
from urllib.parse import urlparse
|
|
5
5
|
|
|
6
|
+
import orjson
|
|
7
|
+
|
|
6
8
|
from notify_tls_client.tls_client.response import Response
|
|
7
9
|
|
|
8
10
|
logger = logging.getLogger(__name__)
|
|
@@ -79,41 +81,65 @@ def _log_request_info(self,
|
|
|
79
81
|
response: Optional[Response],
|
|
80
82
|
request_url: Optional[str],
|
|
81
83
|
request_headers: dict):
|
|
84
|
+
if not logger.isEnabledFor(logging.DEBUG):
|
|
85
|
+
return
|
|
86
|
+
|
|
82
87
|
if response:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
proxy = self.client.proxies['http'] if self.client.proxies else None
|
|
89
|
+
client_id = self.get_current_client_identifier()
|
|
90
|
+
response_headers_dict = dict(response.headers)
|
|
91
|
+
|
|
92
|
+
# Serializar dados complexos com orjson (mais rápido)
|
|
93
|
+
try:
|
|
94
|
+
request_headers_json = orjson.dumps(request_headers).decode('utf-8')
|
|
95
|
+
response_headers_json = orjson.dumps(response_headers_dict).decode('utf-8')
|
|
96
|
+
except Exception:
|
|
97
|
+
request_headers_json = str(request_headers)
|
|
98
|
+
response_headers_json = str(response_headers_dict)
|
|
99
|
+
|
|
100
|
+
logger.debug("Request finished\n"
|
|
101
|
+
" client_identifier=%s\n"
|
|
102
|
+
" request_url=%s\n"
|
|
103
|
+
" request_headers=%s\n"
|
|
104
|
+
" response_url=%s\n"
|
|
105
|
+
" status_code=%s\n"
|
|
106
|
+
" response_time=%sms\n"
|
|
107
|
+
" response_headers=%s\n"
|
|
108
|
+
" proxy=%s",
|
|
109
|
+
client_id, request_url, request_headers_json, response.url,
|
|
110
|
+
response.status_code, response.elapsed, response_headers_json, proxy,
|
|
93
111
|
extra={
|
|
94
112
|
"date": datetime.now().strftime("%d-%m-%Y %H:%M:%S.%f")[:-3],
|
|
95
113
|
"request_url": request_url,
|
|
96
114
|
"request_headers": request_headers,
|
|
97
115
|
"response_url": response.url,
|
|
98
116
|
"status_code": response.status_code,
|
|
99
|
-
"response_headers":
|
|
117
|
+
"response_headers": response_headers_dict,
|
|
100
118
|
"response_elapsed_ms": response.elapsed,
|
|
101
|
-
"proxy":
|
|
102
|
-
"client_identifier":
|
|
103
|
-
|
|
119
|
+
"proxy": proxy,
|
|
120
|
+
"client_identifier": client_id
|
|
104
121
|
})
|
|
105
122
|
|
|
106
123
|
if not response:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
124
|
+
proxy = self.client.proxies['http'] if self.client.proxies else None
|
|
125
|
+
client_id = self.get_current_client_identifier()
|
|
126
|
+
|
|
127
|
+
# Serializar request_headers com orjson
|
|
128
|
+
try:
|
|
129
|
+
request_headers_json = orjson.dumps(request_headers).decode('utf-8')
|
|
130
|
+
except Exception:
|
|
131
|
+
request_headers_json = str(request_headers)
|
|
132
|
+
|
|
133
|
+
logger.debug("Request failed before getting a response\n"
|
|
134
|
+
" client_identifier=%s\n"
|
|
135
|
+
" request_url=%s\n"
|
|
136
|
+
" request_headers=%s\n"
|
|
137
|
+
" response_url=None\n"
|
|
138
|
+
" status_code=None\n"
|
|
139
|
+
" response_headers=None\n"
|
|
140
|
+
" response_time=0\n"
|
|
141
|
+
" proxy=%s",
|
|
142
|
+
client_id, request_url, request_headers_json, proxy,
|
|
117
143
|
extra={
|
|
118
144
|
"date": datetime.now().strftime("%d-%m-%Y %H:%M:%S.%f")[:-3],
|
|
119
145
|
"request_url": request_url,
|
|
@@ -122,9 +148,8 @@ def _log_request_info(self,
|
|
|
122
148
|
"status_code": None,
|
|
123
149
|
"response_headers": None,
|
|
124
150
|
"response_elapsed_ms": 0,
|
|
125
|
-
"proxy":
|
|
126
|
-
"client_identifier":
|
|
127
|
-
|
|
151
|
+
"proxy": proxy,
|
|
152
|
+
"client_identifier": client_id
|
|
128
153
|
})
|
|
129
154
|
|
|
130
155
|
|
|
@@ -7,12 +7,15 @@ from notify_tls_client.tls_client.settings import ClientIdentifiers
|
|
|
7
7
|
|
|
8
8
|
@dataclass
|
|
9
9
|
class ClientIdentifiersManager:
|
|
10
|
-
_items:
|
|
10
|
+
_items: tuple[ClientIdentifiers, ...] = field(default_factory=tuple)
|
|
11
11
|
_current_index: int = 0
|
|
12
12
|
_current_item: Optional[ClientIdentifiers] = None
|
|
13
13
|
|
|
14
14
|
def __post_init__(self):
|
|
15
15
|
self._lock = threading.Lock()
|
|
16
|
+
# Converter lista para tuple se necessário
|
|
17
|
+
if isinstance(self._items, list):
|
|
18
|
+
object.__setattr__(self, '_items', tuple(self._items))
|
|
16
19
|
|
|
17
20
|
def get_next(self) -> ClientIdentifiers:
|
|
18
21
|
with self._lock:
|
|
@@ -26,19 +29,19 @@ class ClientIdentifiersManager:
|
|
|
26
29
|
return _item
|
|
27
30
|
|
|
28
31
|
def get_current_item(self) -> Optional[ClientIdentifiers]:
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
# Lock-free read - safe para strings imutáveis
|
|
33
|
+
return self._current_item
|
|
31
34
|
|
|
32
35
|
def set_items(self, items: list[ClientIdentifiers]):
|
|
33
36
|
with self._lock:
|
|
34
|
-
self._items = items
|
|
37
|
+
self._items = tuple(items) if isinstance(items, list) else items
|
|
35
38
|
self._current_index = 0
|
|
36
39
|
self._current_item = None
|
|
37
40
|
|
|
38
|
-
def get_item(self) ->
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
def get_item(self) -> tuple[ClientIdentifiers, ...]:
|
|
42
|
+
# Lock-free read - tuple é imutável
|
|
43
|
+
return self._items
|
|
41
44
|
|
|
42
45
|
def get_total_items(self) -> int:
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
# Lock-free read - len é thread-safe para objetos imutáveis
|
|
47
|
+
return len(self._items)
|
|
@@ -143,34 +143,35 @@ class NotifyTLSClient:
|
|
|
143
143
|
random_tls_extension_order: bool = False,
|
|
144
144
|
proxy: Optional[Proxy] = None):
|
|
145
145
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
# Preparar novo cliente fora do lock
|
|
147
|
+
new_client = Session(client_identifier=client_identifier,
|
|
148
|
+
random_tls_extension_order=random_tls_extension_order,
|
|
149
|
+
disable_http3=self.disable_http3)
|
|
150
|
+
|
|
151
|
+
# Aplicar configurações ao novo cliente
|
|
152
|
+
if proxy:
|
|
153
|
+
new_client.proxies = proxy.to_proxy_dict()
|
|
149
154
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
old_client = self.client
|
|
155
|
+
if self.headers:
|
|
156
|
+
new_client.headers = CaseInsensitiveDict(self.headers)
|
|
153
157
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
158
|
+
# Lock apenas para swap atômico
|
|
159
|
+
with self._lock:
|
|
160
|
+
old_client = self.client
|
|
161
|
+
old_client_identifier = old_client.client_identifier if old_client else None
|
|
157
162
|
|
|
158
|
-
|
|
159
|
-
old_client.close()
|
|
163
|
+
self.client = new_client
|
|
160
164
|
|
|
161
165
|
if old_client_identifier != client_identifier:
|
|
162
166
|
self.requests_amount_with_current_client_identifier = 0
|
|
163
167
|
|
|
164
|
-
if proxy:
|
|
165
|
-
self.
|
|
166
|
-
|
|
167
|
-
if self.current_proxy != proxy:
|
|
168
|
-
self.current_proxy = proxy
|
|
169
|
-
self.requests_amount_with_current_proxy = 0
|
|
168
|
+
if proxy and self.current_proxy != proxy:
|
|
169
|
+
self.current_proxy = proxy
|
|
170
|
+
self.requests_amount_with_current_proxy = 0
|
|
170
171
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
# Fechar cliente antigo fora do lock
|
|
173
|
+
if old_client:
|
|
174
|
+
old_client.close()
|
|
174
175
|
|
|
175
176
|
|
|
176
177
|
def set_requests_limit_same_proxy(self, requests_limit_same_proxy: int):
|
|
@@ -6,24 +6,30 @@ from dataclasses_json import dataclass_json
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
@dataclass_json
|
|
9
|
-
@dataclass
|
|
9
|
+
@dataclass(slots=True)
|
|
10
10
|
class Proxy:
|
|
11
11
|
host: str
|
|
12
12
|
port: int
|
|
13
13
|
username: Optional[str] = None
|
|
14
14
|
password: Optional[str] = None
|
|
15
|
+
_proxy_dict_cache: Optional[dict] = field(default=None, init=False, repr=False)
|
|
15
16
|
|
|
16
17
|
def to_proxy_dict(self):
|
|
18
|
+
if self._proxy_dict_cache is not None:
|
|
19
|
+
return self._proxy_dict_cache
|
|
20
|
+
|
|
17
21
|
if self.username is None:
|
|
18
|
-
|
|
19
|
-
'http':
|
|
20
|
-
'https':
|
|
22
|
+
self._proxy_dict_cache = {
|
|
23
|
+
'http': 'http://' + self.host + ':' + str(self.port),
|
|
24
|
+
'https': 'https://' + self.host + ':' + str(self.port)
|
|
25
|
+
}
|
|
26
|
+
else:
|
|
27
|
+
self._proxy_dict_cache = {
|
|
28
|
+
'http': 'http://' + self.username + ':' + self.password + '@' + self.host + ':' + str(self.port),
|
|
29
|
+
'https': 'https://' + self.username + ':' + self.password + '@' + self.host + ':' + str(self.port)
|
|
21
30
|
}
|
|
22
31
|
|
|
23
|
-
return
|
|
24
|
-
'http': f'http://{self.username}:{self.password}@{self.host}:{self.port}',
|
|
25
|
-
'https': f'https://{self.username}:{self.password}@{self.host}:{self.port}'
|
|
26
|
-
}
|
|
32
|
+
return self._proxy_dict_cache
|
|
27
33
|
|
|
28
34
|
def __eq__(self, other):
|
|
29
35
|
if not isinstance(other, Proxy):
|
|
@@ -58,8 +64,8 @@ class ProxiesManager:
|
|
|
58
64
|
return proxy
|
|
59
65
|
|
|
60
66
|
def get_current_proxy(self) -> Optional[Proxy]:
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
# Lock-free read - Proxy é imutável (frozen dataclass)
|
|
68
|
+
return self._current_proxy
|
|
63
69
|
|
|
64
70
|
def set_proxies(self, proxies: list[Proxy]):
|
|
65
71
|
with self._lock:
|
|
@@ -69,11 +75,14 @@ class ProxiesManager:
|
|
|
69
75
|
|
|
70
76
|
def add_proxy(self, proxy: Proxy):
|
|
71
77
|
with self._lock:
|
|
72
|
-
|
|
78
|
+
# Criar nova lista ao invés de modificar in-place
|
|
79
|
+
new_proxies = self._proxies.copy()
|
|
80
|
+
new_proxies.append(proxy)
|
|
81
|
+
self._proxies = new_proxies
|
|
73
82
|
|
|
74
83
|
def get_proxies(self) -> list[Proxy]:
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
# Lock-free read - retornar referência direta (lista é copiada pelo caller se necessário)
|
|
85
|
+
return self._proxies
|
|
77
86
|
|
|
78
87
|
|
|
79
88
|
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import orjson
|
|
2
2
|
|
|
3
3
|
from .cookies import cookiejar_from_dict, RequestsCookieJar
|
|
4
4
|
from .structures import CaseInsensitiveDict
|
|
5
5
|
|
|
6
6
|
from typing import Union
|
|
7
|
-
import json
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class Response:
|
|
11
10
|
"""object, which contains the response to an HTTP request."""
|
|
11
|
+
__slots__ = ('elapsed', 'url', 'status_code', 'text', 'headers', 'cookies', '_content', '_content_consumed', '__weakref__')
|
|
12
12
|
|
|
13
13
|
def __init__(self):
|
|
14
14
|
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: notify-tls-client
|
|
3
|
+
Version: 2.0.2
|
|
4
|
+
Summary: Cliente HTTP avançado com TLS fingerprinting, rotação automática de proxies e recuperação inteligente para web scraping profissional
|
|
5
|
+
Author-email: Jeferson Albara <jeferson.albara@example.com>
|
|
6
|
+
Maintainer-email: Jeferson Albara <jeferson.albara@example.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/jefersonAlbara/notify-tls-client
|
|
9
|
+
Project-URL: Documentation, https://github.com/jefersonAlbara/notify-tls-client#readme
|
|
10
|
+
Project-URL: Repository, https://github.com/jefersonAlbara/notify-tls-client
|
|
11
|
+
Project-URL: Issues, https://github.com/jefersonAlbara/notify-tls-client/issues
|
|
12
|
+
Project-URL: Changelog, https://github.com/jefersonAlbara/notify-tls-client/blob/main/CHANGELOG.md
|
|
13
|
+
Keywords: tls-client,http-client,web-scraping,proxy-rotation,tls-fingerprinting,browser-emulation,http2,http3,requests,automation
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: Internet :: Proxy Servers
|
|
23
|
+
Classifier: Operating System :: OS Independent
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.12
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
Requires-Dist: dataclasses-json>=0.6.0
|
|
28
|
+
Requires-Dist: typing-extensions>=4.8.0
|
|
29
|
+
Requires-Dist: orjson>=3.9.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
33
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: mypy>=1.5.0; extra == "dev"
|
|
35
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
36
|
+
|
|
37
|
+
# Notify TLS Client
|
|
38
|
+
|
|
39
|
+
[](https://badge.fury.io/py/notify-tls-client)
|
|
40
|
+
[](https://pypi.org/project/notify-tls-client/)
|
|
41
|
+
[](https://opensource.org/licenses/MIT)
|
|
42
|
+
|
|
43
|
+
Cliente HTTP avançado em Python com suporte a TLS/SSL customizado, fingerprinting de navegadores e rotação automática de proxies. Construído sobre a biblioteca `tls-client` com funcionalidades adicionais para web scraping e automação resiliente.
|
|
44
|
+
|
|
45
|
+
## 🚀 Características Principais
|
|
46
|
+
|
|
47
|
+
- **Fingerprinting TLS Avançado**: Emula múltiplos navegadores (Chrome, Firefox, Safari, Edge, Mobile)
|
|
48
|
+
- **Rotação Automática**: Proxies e client identifiers com políticas configuráveis
|
|
49
|
+
- **Recuperação Automática**: Reconexão inteligente em erros e respostas proibidas
|
|
50
|
+
- **Thread-Safe**: Uso seguro em ambientes multi-threaded
|
|
51
|
+
- **Configuração Modular**: Sistema de configuração baseado em objetos reutilizáveis
|
|
52
|
+
- **Presets Prontos**: Configurações pré-definidas para casos de uso comuns
|
|
53
|
+
- **HTTP/3 Support**: Suporte opcional a QUIC/HTTP3
|
|
54
|
+
|
|
55
|
+
## 📦 Instalação
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install notify-tls-client
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Requisitos
|
|
62
|
+
|
|
63
|
+
- Python >= 3.12
|
|
64
|
+
- Sistema operacional: Windows, macOS, Linux (x86_64, ARM64)
|
|
65
|
+
|
|
66
|
+
## 🎯 Quick Start
|
|
67
|
+
|
|
68
|
+
### Uso Básico
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from notify_tls_client import NotifyTLSClient
|
|
72
|
+
|
|
73
|
+
# Cliente com configuração padrão
|
|
74
|
+
client = NotifyTLSClient()
|
|
75
|
+
|
|
76
|
+
# Fazer requisição
|
|
77
|
+
response = client.get("https://api.example.com/data")
|
|
78
|
+
print(response.status_code)
|
|
79
|
+
print(response.json())
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Usando Presets (Recomendado)
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from notify_tls_client import NotifyTLSClient
|
|
86
|
+
from notify_tls_client.config import ClientConfiguration
|
|
87
|
+
from notify_tls_client.core.proxiesmanager import ProxiesManagerLoader
|
|
88
|
+
|
|
89
|
+
# Carregar proxies
|
|
90
|
+
proxies = ProxiesManagerLoader().from_txt("proxies.txt")
|
|
91
|
+
|
|
92
|
+
# Preset para scraping agressivo
|
|
93
|
+
config = ClientConfiguration.aggressive(proxies)
|
|
94
|
+
client = NotifyTLSClient(config)
|
|
95
|
+
|
|
96
|
+
# Fazer múltiplas requisições
|
|
97
|
+
for i in range(100):
|
|
98
|
+
response = client.get("https://example.com/api/endpoint")
|
|
99
|
+
print(f"Request {i}: {response.status_code}")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Configuração Customizada
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from notify_tls_client import NotifyTLSClient
|
|
106
|
+
from notify_tls_client.config import (
|
|
107
|
+
ClientConfiguration,
|
|
108
|
+
RotationConfig,
|
|
109
|
+
RecoveryConfig,
|
|
110
|
+
ClientConfig
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
config = ClientConfiguration(
|
|
114
|
+
proxies_manager=proxies,
|
|
115
|
+
rotation=RotationConfig(
|
|
116
|
+
requests_limit_same_proxy=50,
|
|
117
|
+
requests_limit_same_client_identifier=200,
|
|
118
|
+
random_tls_extension_order=True
|
|
119
|
+
),
|
|
120
|
+
recovery=RecoveryConfig(
|
|
121
|
+
instantiate_new_client_on_forbidden_response=True,
|
|
122
|
+
instantiate_new_client_on_exception=True,
|
|
123
|
+
change_client_identifier_on_forbidden_response=True,
|
|
124
|
+
status_codes_to_forbidden_response_handle=[403, 429, 503]
|
|
125
|
+
),
|
|
126
|
+
client=ClientConfig(
|
|
127
|
+
client_identifiers=["chrome_133", "firefox_120", "safari_17_0"],
|
|
128
|
+
disable_http3=False,
|
|
129
|
+
debug_mode=False
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
client = NotifyTLSClient(config)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## 📚 Presets Disponíveis
|
|
137
|
+
|
|
138
|
+
### Simple
|
|
139
|
+
Uso básico com rotação de proxies padrão.
|
|
140
|
+
```python
|
|
141
|
+
config = ClientConfiguration.simple(proxies)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Aggressive
|
|
145
|
+
Para scraping intensivo com recuperação automática completa.
|
|
146
|
+
```python
|
|
147
|
+
config = ClientConfiguration.aggressive(proxies)
|
|
148
|
+
```
|
|
149
|
+
- Troca proxy a cada 10 requisições
|
|
150
|
+
- Troca client identifier a cada 50 requisições
|
|
151
|
+
- Recuperação automática em erros e 403/429/503
|
|
152
|
+
- Múltiplos client identifiers
|
|
153
|
+
|
|
154
|
+
### Stealth
|
|
155
|
+
Foco em evitar detecção através de diversidade.
|
|
156
|
+
```python
|
|
157
|
+
config = ClientConfiguration.stealth(proxies)
|
|
158
|
+
```
|
|
159
|
+
- 4 client identifiers diferentes
|
|
160
|
+
- Ordem de extensões TLS randomizada
|
|
161
|
+
- Rotação moderada (100 req/proxy)
|
|
162
|
+
|
|
163
|
+
### Mobile
|
|
164
|
+
Simula dispositivos móveis.
|
|
165
|
+
```python
|
|
166
|
+
# Android
|
|
167
|
+
config = ClientConfiguration.mobile(proxies, platform="android")
|
|
168
|
+
|
|
169
|
+
# iOS
|
|
170
|
+
config = ClientConfiguration.mobile(proxies, platform="ios")
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## 🔧 Funcionalidades Avançadas
|
|
174
|
+
|
|
175
|
+
### Rotação de Proxies
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from notify_tls_client.core.proxiesmanager import ProxiesManagerLoader
|
|
179
|
+
|
|
180
|
+
# Carregar de arquivo
|
|
181
|
+
proxies = ProxiesManagerLoader().from_txt("proxies.txt")
|
|
182
|
+
|
|
183
|
+
# Formato do arquivo (um por linha):
|
|
184
|
+
# host:port
|
|
185
|
+
# host:port:username:password
|
|
186
|
+
# http://username:password@host:port
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Client Identifiers Suportados
|
|
190
|
+
|
|
191
|
+
**Desktop:**
|
|
192
|
+
- Chrome: `chrome_133`, `chrome_131`, `chrome_120`, etc.
|
|
193
|
+
- Firefox: `firefox_120`, `firefox_117`, `firefox_110`, etc.
|
|
194
|
+
- Safari: `safari_17_0`, `safari_16_0`, etc.
|
|
195
|
+
- Edge, Opera
|
|
196
|
+
|
|
197
|
+
**Mobile:**
|
|
198
|
+
- Android: `okhttp4_android_13`, `okhttp4_android_12`, etc.
|
|
199
|
+
- iOS: `safari_ios_16_0`, `safari_ios_15_6`, etc.
|
|
200
|
+
|
|
201
|
+
### Recuperação Automática
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
config = ClientConfiguration(
|
|
205
|
+
recovery=RecoveryConfig(
|
|
206
|
+
# Criar nova sessão em respostas proibidas
|
|
207
|
+
instantiate_new_client_on_forbidden_response=True,
|
|
208
|
+
|
|
209
|
+
# Criar nova sessão em exceções
|
|
210
|
+
instantiate_new_client_on_exception=True,
|
|
211
|
+
|
|
212
|
+
# Trocar identifier em respostas proibidas
|
|
213
|
+
change_client_identifier_on_forbidden_response=True,
|
|
214
|
+
|
|
215
|
+
# Status codes que acionam recuperação
|
|
216
|
+
status_codes_to_forbidden_response_handle=[403, 429, 503]
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Headers Customizados
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
config = ClientConfiguration(
|
|
225
|
+
client=ClientConfig(
|
|
226
|
+
default_headers={
|
|
227
|
+
"User-Agent": "Mozilla/5.0...",
|
|
228
|
+
"Accept-Language": "pt-BR,pt;q=0.9",
|
|
229
|
+
"Custom-Header": "value"
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Ou por requisição
|
|
235
|
+
response = client.get(
|
|
236
|
+
"https://example.com",
|
|
237
|
+
headers={"Authorization": "Bearer token"}
|
|
238
|
+
)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Cookies
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
# Obter todos os cookies
|
|
245
|
+
cookies = client.get_cookies()
|
|
246
|
+
|
|
247
|
+
# Obter cookie específico
|
|
248
|
+
value = client.get_cookie_by_name("session_id")
|
|
249
|
+
|
|
250
|
+
# Definir cookie
|
|
251
|
+
client.set_cookie("name", "value")
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## 🔒 Thread Safety
|
|
255
|
+
|
|
256
|
+
A biblioteca é thread-safe e pode ser usada em ambientes multi-threaded:
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
import concurrent.futures
|
|
260
|
+
|
|
261
|
+
client = NotifyTLSClient(ClientConfiguration.aggressive(proxies))
|
|
262
|
+
|
|
263
|
+
def make_request(url):
|
|
264
|
+
return client.get(url)
|
|
265
|
+
|
|
266
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
|
|
267
|
+
urls = ["https://example.com"] * 100
|
|
268
|
+
results = list(executor.map(make_request, urls))
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## 📊 Logging
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
import logging
|
|
275
|
+
|
|
276
|
+
# Habilitar logs de debug
|
|
277
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
278
|
+
|
|
279
|
+
# Ou configurar apenas para notify_tls_client
|
|
280
|
+
logger = logging.getLogger("notify_tls_client")
|
|
281
|
+
logger.setLevel(logging.DEBUG)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## 🛠️ Métodos HTTP Suportados
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
# GET
|
|
288
|
+
response = client.get(url, params={"key": "value"})
|
|
289
|
+
|
|
290
|
+
# POST
|
|
291
|
+
response = client.post(url, json={"data": "value"})
|
|
292
|
+
response = client.post(url, data="form data")
|
|
293
|
+
|
|
294
|
+
# PUT
|
|
295
|
+
response = client.put(url, json={"data": "value"})
|
|
296
|
+
|
|
297
|
+
# PATCH
|
|
298
|
+
response = client.patch(url, json={"data": "value"})
|
|
299
|
+
|
|
300
|
+
# DELETE
|
|
301
|
+
response = client.delete(url)
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## 📖 Documentação Completa
|
|
305
|
+
|
|
306
|
+
Para documentação detalhada sobre arquitetura, componentes internos e exemplos avançados, consulte:
|
|
307
|
+
- [CLAUDE.md](CLAUDE.md) - Guia completo de desenvolvimento
|
|
308
|
+
- [examples/](examples/) - Exemplos de código
|
|
309
|
+
|
|
310
|
+
## 🤝 Contribuindo
|
|
311
|
+
|
|
312
|
+
Contribuições são bem-vindas! Por favor, leia [CONTRIBUTING.md](CONTRIBUTING.md) para detalhes sobre nosso código de conduta e processo de submissão de pull requests.
|
|
313
|
+
|
|
314
|
+
## 📝 Changelog
|
|
315
|
+
|
|
316
|
+
Veja [CHANGELOG.md](CHANGELOG.md) para histórico de versões e mudanças.
|
|
317
|
+
|
|
318
|
+
## 📄 Licença
|
|
319
|
+
|
|
320
|
+
Este projeto está licenciado sob a Licença MIT - veja o arquivo [LICENSE](LICENSE) para detalhes.
|
|
321
|
+
|
|
322
|
+
## ⚠️ Aviso Legal
|
|
323
|
+
|
|
324
|
+
Esta biblioteca é fornecida apenas para fins educacionais e de pesquisa. O uso desta ferramenta para violar termos de serviço de websites, realizar scraping não autorizado ou qualquer atividade ilegal é de sua responsabilidade. Os desenvolvedores não se responsabilizam pelo uso indevido desta biblioteca.
|
|
325
|
+
|
|
326
|
+
## 🙏 Agradecimentos
|
|
327
|
+
|
|
328
|
+
- [tls-client](https://github.com/bogdanfinn/tls-client) - Biblioteca Go subjacente para TLS fingerprinting
|
|
329
|
+
- Comunidade Python por ferramentas e bibliotecas incríveis
|
|
330
|
+
|
|
331
|
+
## 📞 Suporte
|
|
332
|
+
|
|
333
|
+
- **Issues**: [GitHub Issues](https://github.com/jefersonAlbara/notify-tls-client/issues)
|
|
334
|
+
- **Discussões**: [GitHub Discussions](https://github.com/jefersonAlbara/notify-tls-client/discussions)
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
**Desenvolvido com ❤️ para a comunidade Python**
|
|
@@ -2,22 +2,22 @@ notify_tls_client/__init__.py,sha256=APA8wxvkGYZiVMMbTWGo7prB3iKE-OzUajd0bfc8PwE
|
|
|
2
2
|
notify_tls_client/config/__init__.py,sha256=2xsw7M2E8-ncpIDySYcgH500xAnYyO3qy3PRXkP2inQ,495
|
|
3
3
|
notify_tls_client/config/client_config.py,sha256=Fwg6LS1zP8XFRPbTxNj2svCqeWh2GJcqrq7hnpQtYRQ,2837
|
|
4
4
|
notify_tls_client/config/client_configuration.py,sha256=Zl71lxZSKU7oVJwnHPSYV9uufk2IgFtHW3u9deqo5HU,9103
|
|
5
|
-
notify_tls_client/config/recovery_config.py,sha256=
|
|
5
|
+
notify_tls_client/config/recovery_config.py,sha256=zKBjSdCHJ4iF0PXUawPh3EktOxGdn-8bzpjl7LOIoa4,3202
|
|
6
6
|
notify_tls_client/config/rotation_config.py,sha256=Av_K6BzgPknFYZXxe9WmDra8VGI9JEpDJSm0dDs8QqM,2089
|
|
7
7
|
notify_tls_client/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
notify_tls_client/core/client_identifiers_manager.py,sha256=
|
|
9
|
-
notify_tls_client/core/notifytlsclient.py,sha256=
|
|
8
|
+
notify_tls_client/core/client_identifiers_manager.py,sha256=D8xJ89aaxZ_8Pssx9VqEH05G8cp3bMez14-KrTMtJiU,1646
|
|
9
|
+
notify_tls_client/core/notifytlsclient.py,sha256=JZSQ0frcYTuoLeiaWt7rwJh8IqgFtxZ6QLj1HG4MaD8,12650
|
|
10
10
|
notify_tls_client/core/client/__init__.py,sha256=VKjpq02hC-r9ER6mbfkwG6W2ybv_jND9d9bidte0CjU,51
|
|
11
|
-
notify_tls_client/core/client/decorators.py,sha256=
|
|
11
|
+
notify_tls_client/core/client/decorators.py,sha256=oCmB_9vSyW3C0GEv1AiMIj_rJOwsZ2bHDVRD_QgJmQs,9693
|
|
12
12
|
notify_tls_client/core/proxiesmanager/__init__.py,sha256=emF4vnZvSfb9zlHkt9dDdTcGwkfs1DADi7XVw_DxsWs,105
|
|
13
|
-
notify_tls_client/core/proxiesmanager/proxiesmanager.py,sha256=
|
|
13
|
+
notify_tls_client/core/proxiesmanager/proxiesmanager.py,sha256=hcSw27wbt6UerJ5F8hfLjrIS8H1K-XwgZEa60lvGhXg,2902
|
|
14
14
|
notify_tls_client/core/proxiesmanager/proxiesmanagerloader.py,sha256=7xr3SVdRnr95KWOdk15iehOCXG2huA-rY1j9VIe30YQ,1179
|
|
15
15
|
notify_tls_client/tls_client/__init__.py,sha256=sThiIAzA650rfBlqZ_xalTjgTysUsjKua5ODnqyvhUE,669
|
|
16
16
|
notify_tls_client/tls_client/__version__.py,sha256=32ZZ-ufGC9Yo6oPk5a1xig8YKJ2ZkRXnXoVqiqO0ptg,395
|
|
17
17
|
notify_tls_client/tls_client/cffi.py,sha256=pedwBcQOwJvI66yp5GpyNU6zoqrQhTv3ocM1-1PtUm0,1291
|
|
18
18
|
notify_tls_client/tls_client/cookies.py,sha256=1fIOnFDMWMeXuAjQybSrUJXnyjhP-_jJ68AxBUZYgYU,15609
|
|
19
19
|
notify_tls_client/tls_client/exceptions.py,sha256=xIoFb5H3Suk7XssQ-yw3I1DBkPLqnDXsiAe2MMp1qNQ,80
|
|
20
|
-
notify_tls_client/tls_client/response.py,sha256=
|
|
20
|
+
notify_tls_client/tls_client/response.py,sha256=oMXdnbyppX5B1pYsk8h1UqZG01qHVOQCcnYrMG79ThI,2722
|
|
21
21
|
notify_tls_client/tls_client/sessions.py,sha256=1xeUDR9H42wS900mCzkpu4At7Lx1O07QGoDuThbLG0M,19233
|
|
22
22
|
notify_tls_client/tls_client/settings.py,sha256=x_2Vrph-QebbCjkxmnX8UPd5oYYU4ClqPOpfoqelno4,1485
|
|
23
23
|
notify_tls_client/tls_client/structures.py,sha256=md-tJmo8X5bad0KrMUTVN8jxUIvui7NiBxaf10bLULU,2517
|
|
@@ -26,7 +26,7 @@ notify_tls_client/tls_client/dependencies/tls-client-darwin-amd64-1.12.0.dylib,s
|
|
|
26
26
|
notify_tls_client/tls_client/dependencies/tls-client-linux-arm64-1.12.0.so,sha256=fQvdtCiRRS228WrFUE_ucq4OPC4Z7QU4_KI4B3Gf97Y,14404088
|
|
27
27
|
notify_tls_client/tls_client/dependencies/tls-client-linux-ubuntu-amd64-1.12.0.so,sha256=UTvtZa93fWLWaSBINC_Cu8mNoLwsVdcQbQZsdQnZrJM,15210112
|
|
28
28
|
notify_tls_client/tls_client/dependencies/tls-client-windows-64-1.12.0.dll,sha256=uk80UEGW8WepYMglh1Yo6VSrBSNDwon-OyFqE_1bWmM,24849278
|
|
29
|
-
notify_tls_client-2.0.
|
|
30
|
-
notify_tls_client-2.0.
|
|
31
|
-
notify_tls_client-2.0.
|
|
32
|
-
notify_tls_client-2.0.
|
|
29
|
+
notify_tls_client-2.0.2.dist-info/METADATA,sha256=gf8TM2izyYQ_8Cs7BMPJvTHZlKLDmM1mPWwC-MuuQFI,10565
|
|
30
|
+
notify_tls_client-2.0.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
31
|
+
notify_tls_client-2.0.2.dist-info/top_level.txt,sha256=fq9YA0cFdpCuUO7cdMFN7oxm1zDfZm_m1KPXehUqA5o,18
|
|
32
|
+
notify_tls_client-2.0.2.dist-info/RECORD,,
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: notify-tls-client
|
|
3
|
-
Version: 2.0.0
|
|
4
|
-
Summary: Cliente HTTP avançado com TLS fingerprinting, rotação de proxies e recuperação automática para web scraping resiliente
|
|
5
|
-
Author-email: Jeferson Albara <jeferson.albara@example.com>
|
|
6
|
-
Maintainer-email: Jeferson Albara <jeferson.albara@example.com>
|
|
7
|
-
License: MIT
|
|
8
|
-
Project-URL: Homepage, https://github.com/jefersonAlbara/notify-tls-client
|
|
9
|
-
Project-URL: Documentation, https://github.com/jefersonAlbara/notify-tls-client#readme
|
|
10
|
-
Project-URL: Repository, https://github.com/jefersonAlbara/notify-tls-client
|
|
11
|
-
Project-URL: Issues, https://github.com/jefersonAlbara/notify-tls-client/issues
|
|
12
|
-
Project-URL: Changelog, https://github.com/jefersonAlbara/notify-tls-client/blob/main/CHANGELOG.md
|
|
13
|
-
Keywords: tls-client,http-client,web-scraping,proxy-rotation,tls-fingerprinting,browser-emulation,http2,http3,requests,automation
|
|
14
|
-
Classifier: Development Status :: 4 - Beta
|
|
15
|
-
Classifier: Intended Audience :: Developers
|
|
16
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
-
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
-
Classifier: Topic :: Internet :: WWW/HTTP
|
|
21
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
-
Classifier: Topic :: Internet :: Proxy Servers
|
|
23
|
-
Classifier: Operating System :: OS Independent
|
|
24
|
-
Classifier: Typing :: Typed
|
|
25
|
-
Requires-Python: >=3.12
|
|
26
|
-
Description-Content-Type: text/markdown
|
|
27
|
-
Requires-Dist: dataclasses-json>=0.6.0
|
|
28
|
-
Requires-Dist: typing-extensions>=4.8.0
|
|
29
|
-
Requires-Dist: orjson>=3.9.0
|
|
30
|
-
Provides-Extra: dev
|
|
31
|
-
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
32
|
-
Requires-Dist: pytest-cov>=4.1.0; extra == "dev"
|
|
33
|
-
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
34
|
-
Requires-Dist: mypy>=1.5.0; extra == "dev"
|
|
35
|
-
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
36
|
-
|
|
37
|
-
# MY TLS Client
|
|
38
|
-
Pacote python privado com modificações na classe 'tls client'
|
|
File without changes
|
|
File without changes
|