notify-tls-client 0.1.9__tar.gz → 2.0.0__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.
- notify_tls_client-2.0.0/MANIFEST.in +37 -0
- notify_tls_client-2.0.0/PKG-INFO +38 -0
- notify_tls_client-2.0.0/notify_tls_client/config/__init__.py +18 -0
- notify_tls_client-2.0.0/notify_tls_client/config/client_config.py +76 -0
- notify_tls_client-2.0.0/notify_tls_client/config/client_configuration.py +237 -0
- notify_tls_client-2.0.0/notify_tls_client/config/recovery_config.py +66 -0
- notify_tls_client-2.0.0/notify_tls_client/config/rotation_config.py +56 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/core/client/decorators.py +9 -48
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/core/client_identifiers_manager.py +20 -21
- notify_tls_client-2.0.0/notify_tls_client/core/notifytlsclient.py +271 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/core/proxiesmanager/proxiesmanager.py +32 -20
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/tls_client/cffi.py +2 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/tls_client/response.py +2 -1
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/tls_client/sessions.py +6 -4
- notify_tls_client-2.0.0/notify_tls_client.egg-info/PKG-INFO +38 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client.egg-info/SOURCES.txt +5 -0
- notify_tls_client-2.0.0/notify_tls_client.egg-info/requires.txt +10 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client.egg-info/top_level.txt +0 -1
- notify_tls_client-2.0.0/pyproject.toml +94 -0
- notify_tls_client-0.1.9/MANIFEST.in +0 -3
- notify_tls_client-0.1.9/PKG-INFO +0 -14
- notify_tls_client-0.1.9/notify_tls_client/core/notifytlsclient.py +0 -196
- notify_tls_client-0.1.9/notify_tls_client.egg-info/PKG-INFO +0 -14
- notify_tls_client-0.1.9/notify_tls_client.egg-info/requires.txt +0 -2
- notify_tls_client-0.1.9/pyproject.toml +0 -31
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/README.md +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/__init__.py +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/core/__init__.py +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/core/client/__init__.py +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/core/proxiesmanager/__init__.py +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/core/proxiesmanager/proxiesmanagerloader.py +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/tls_client/__init__.py +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/tls_client/__version__.py +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/tls_client/cookies.py +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/tls_client/dependencies/__init__.py +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/tls_client/dependencies/tls-client-darwin-amd64-1.12.0.dylib +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/tls_client/dependencies/tls-client-linux-arm64-1.12.0.so +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/tls_client/dependencies/tls-client-linux-ubuntu-amd64-1.12.0.so +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/tls_client/dependencies/tls-client-windows-64-1.12.0.dll +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/tls_client/exceptions.py +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/tls_client/settings.py +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/tls_client/structures.py +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client.egg-info/dependency_links.txt +0 -0
- {notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/setup.cfg +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Include documentation files
|
|
2
|
+
include README.md
|
|
3
|
+
include LICENSE
|
|
4
|
+
include CHANGELOG.md
|
|
5
|
+
include CONTRIBUTING.md
|
|
6
|
+
|
|
7
|
+
# Include native dependencies
|
|
8
|
+
recursive-include notify_tls_client/tls_client/dependencies *.dll
|
|
9
|
+
recursive-include notify_tls_client/tls_client/dependencies *.dylib
|
|
10
|
+
recursive-include notify_tls_client/tls_client/dependencies *.so
|
|
11
|
+
|
|
12
|
+
# Include type stubs
|
|
13
|
+
include notify_tls_client/py.typed
|
|
14
|
+
|
|
15
|
+
# Exclude test files
|
|
16
|
+
recursive-exclude tests *
|
|
17
|
+
recursive-exclude * __pycache__
|
|
18
|
+
recursive-exclude * *.py[co]
|
|
19
|
+
|
|
20
|
+
# Exclude development files
|
|
21
|
+
exclude .gitignore
|
|
22
|
+
exclude .git
|
|
23
|
+
exclude proxies.txt
|
|
24
|
+
exclude notify_tls_client/main.py
|
|
25
|
+
exclude main.py
|
|
26
|
+
|
|
27
|
+
# Exclude IDE files
|
|
28
|
+
recursive-exclude * .vscode
|
|
29
|
+
recursive-exclude * .idea
|
|
30
|
+
recursive-exclude * *.swp
|
|
31
|
+
recursive-exclude * *.swo
|
|
32
|
+
recursive-exclude * *~
|
|
33
|
+
|
|
34
|
+
# Exclude build artifacts
|
|
35
|
+
recursive-exclude * *.egg-info
|
|
36
|
+
recursive-exclude * dist
|
|
37
|
+
recursive-exclude * build
|
|
@@ -0,0 +1,38 @@
|
|
|
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'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Módulo de configuração para NotifyTLSClient.
|
|
3
|
+
|
|
4
|
+
Fornece classes de configuração especializadas para organizar
|
|
5
|
+
os parâmetros do cliente TLS de forma mais limpa e reutilizável.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .rotation_config import RotationConfig
|
|
9
|
+
from .recovery_config import RecoveryConfig
|
|
10
|
+
from .client_config import ClientConfig
|
|
11
|
+
from .client_configuration import ClientConfiguration
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
'RotationConfig',
|
|
15
|
+
'RecoveryConfig',
|
|
16
|
+
'ClientConfig',
|
|
17
|
+
'ClientConfiguration',
|
|
18
|
+
]
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configurações do cliente TLS (identifiers, headers, protocolos).
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from notify_tls_client.tls_client.settings import ClientIdentifiers
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ClientConfig:
|
|
13
|
+
"""
|
|
14
|
+
Configurações relacionadas ao cliente TLS e fingerprinting.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
client_identifiers: Lista de client identifiers para rotação.
|
|
18
|
+
Identifiers disponíveis incluem chrome_133, firefox_120, safari_17_0, etc.
|
|
19
|
+
Se apenas um identifier for fornecido, não haverá rotação.
|
|
20
|
+
Default: ["chrome_133"]
|
|
21
|
+
|
|
22
|
+
default_headers: Headers padrão a serem enviados em todas as requisições.
|
|
23
|
+
Pode ser sobrescrito por requisição individual.
|
|
24
|
+
Default: None (usa headers padrão do tls-client)
|
|
25
|
+
|
|
26
|
+
disable_http3: Se True, força o uso de HTTP/2 ou HTTP/1.1, desabilitando HTTP/3.
|
|
27
|
+
Útil para ambientes que não suportam QUIC.
|
|
28
|
+
Default: False
|
|
29
|
+
|
|
30
|
+
debug_mode: Se True, habilita logs de debug detalhados.
|
|
31
|
+
IMPORTANTE: Pode vazar informações sensíveis nos logs.
|
|
32
|
+
Default: False
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
>>> # Configuração padrão
|
|
36
|
+
>>> config = ClientConfig()
|
|
37
|
+
|
|
38
|
+
>>> # Múltiplos identifiers para stealth
|
|
39
|
+
>>> config = ClientConfig(
|
|
40
|
+
... client_identifiers=["chrome_133", "firefox_120", "safari_17_0"]
|
|
41
|
+
... )
|
|
42
|
+
|
|
43
|
+
>>> # Headers customizados
|
|
44
|
+
>>> config = ClientConfig(
|
|
45
|
+
... default_headers={
|
|
46
|
+
... "User-Agent": "Mozilla/5.0...",
|
|
47
|
+
... "Accept-Language": "pt-BR,pt;q=0.9"
|
|
48
|
+
... }
|
|
49
|
+
... )
|
|
50
|
+
|
|
51
|
+
>>> # Forçar HTTP/2
|
|
52
|
+
>>> config = ClientConfig(disable_http3=True)
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
client_identifiers: list[ClientIdentifiers] = field(default_factory=lambda: ["chrome_133"])
|
|
56
|
+
default_headers: Optional[dict] = None
|
|
57
|
+
disable_http3: bool = False
|
|
58
|
+
debug_mode: bool = False
|
|
59
|
+
|
|
60
|
+
def __post_init__(self):
|
|
61
|
+
"""Valida os parâmetros de configuração."""
|
|
62
|
+
if not self.client_identifiers:
|
|
63
|
+
raise ValueError("client_identifiers não pode ser vazio")
|
|
64
|
+
|
|
65
|
+
if len(self.client_identifiers) != len(set(self.client_identifiers)):
|
|
66
|
+
raise ValueError("client_identifiers contém duplicatas")
|
|
67
|
+
|
|
68
|
+
# Valida default_headers se fornecido
|
|
69
|
+
if self.default_headers is not None:
|
|
70
|
+
if not isinstance(self.default_headers, dict):
|
|
71
|
+
raise ValueError("default_headers deve ser um dicionário")
|
|
72
|
+
|
|
73
|
+
# Valida que keys são strings
|
|
74
|
+
for key in self.default_headers.keys():
|
|
75
|
+
if not isinstance(key, str):
|
|
76
|
+
raise ValueError(f"Header key inválido: {key}. Deve ser string")
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuração completa do NotifyTLSClient com factory methods para casos de uso comuns.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from notify_tls_client.core.proxiesmanager import ProxiesManager
|
|
9
|
+
from .rotation_config import RotationConfig
|
|
10
|
+
from .recovery_config import RecoveryConfig
|
|
11
|
+
from .client_config import ClientConfig
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class ClientConfiguration:
|
|
16
|
+
"""
|
|
17
|
+
Configuração completa para NotifyTLSClient.
|
|
18
|
+
|
|
19
|
+
Agrupa todas as configurações específicas em um único objeto para
|
|
20
|
+
simplificar a criação e reutilização de configurações.
|
|
21
|
+
|
|
22
|
+
Attributes:
|
|
23
|
+
proxies_manager: Gerenciador de proxies para rotação automática.
|
|
24
|
+
Default: None (sem proxies)
|
|
25
|
+
|
|
26
|
+
rotation: Configurações de rotação de proxies e client identifiers.
|
|
27
|
+
Default: RotationConfig com valores padrão
|
|
28
|
+
|
|
29
|
+
recovery: Configurações de recuperação automática.
|
|
30
|
+
Default: RecoveryConfig com valores padrão
|
|
31
|
+
|
|
32
|
+
client: Configurações do cliente TLS.
|
|
33
|
+
Default: ClientConfig com valores padrão
|
|
34
|
+
|
|
35
|
+
Examples:
|
|
36
|
+
>>> # Configuração padrão
|
|
37
|
+
>>> config = ClientConfiguration()
|
|
38
|
+
|
|
39
|
+
>>> # Configuração customizada
|
|
40
|
+
>>> config = ClientConfiguration(
|
|
41
|
+
... proxies_manager=ProxiesManager([...]),
|
|
42
|
+
... rotation=RotationConfig(requests_limit_same_proxy=10),
|
|
43
|
+
... recovery=RecoveryConfig(
|
|
44
|
+
... instantiate_new_client_on_forbidden_response=True,
|
|
45
|
+
... status_codes_to_forbidden_response_handle=[403, 429]
|
|
46
|
+
... ),
|
|
47
|
+
... client=ClientConfig(
|
|
48
|
+
... client_identifiers=["chrome_133", "firefox_120"]
|
|
49
|
+
... )
|
|
50
|
+
... )
|
|
51
|
+
|
|
52
|
+
>>> # Usando factory method
|
|
53
|
+
>>> config = ClientConfiguration.aggressive(proxies_manager)
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
proxies_manager: Optional[ProxiesManager] = None
|
|
57
|
+
rotation: RotationConfig = field(default_factory=RotationConfig)
|
|
58
|
+
recovery: RecoveryConfig = field(default_factory=RecoveryConfig)
|
|
59
|
+
client: ClientConfig = field(default_factory=ClientConfig)
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def aggressive(cls, proxies_manager: Optional[ProxiesManager] = None) -> 'ClientConfiguration':
|
|
63
|
+
"""
|
|
64
|
+
Configuração agressiva: rotação rápida e recuperação automática completa.
|
|
65
|
+
|
|
66
|
+
Ideal para cenários de scraping intensivo onde detectar e contornar
|
|
67
|
+
bloqueios rapidamente é crítico.
|
|
68
|
+
|
|
69
|
+
Características:
|
|
70
|
+
- Troca proxy a cada 10 requisições
|
|
71
|
+
- Troca client identifier a cada 50 requisições
|
|
72
|
+
- Recuperação automática em erros e respostas proibidas
|
|
73
|
+
- Troca client identifier em respostas proibidas
|
|
74
|
+
- Monitora status codes 403, 429 e 503
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
proxies_manager: Gerenciador de proxies (obrigatório para rotação)
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
ClientConfiguration configurado para modo agressivo
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
>>> tls = NotifyTLSClient(ClientConfiguration.aggressive(proxies))
|
|
84
|
+
"""
|
|
85
|
+
return cls(
|
|
86
|
+
proxies_manager=proxies_manager,
|
|
87
|
+
rotation=RotationConfig(
|
|
88
|
+
requests_limit_same_proxy=10,
|
|
89
|
+
requests_limit_same_client_identifier=50,
|
|
90
|
+
random_tls_extension_order=True
|
|
91
|
+
),
|
|
92
|
+
recovery=RecoveryConfig(
|
|
93
|
+
instantiate_new_client_on_forbidden_response=True,
|
|
94
|
+
instantiate_new_client_on_exception=True,
|
|
95
|
+
change_client_identifier_on_forbidden_response=True,
|
|
96
|
+
status_codes_to_forbidden_response_handle=[403, 429, 503]
|
|
97
|
+
),
|
|
98
|
+
client=ClientConfig(
|
|
99
|
+
client_identifiers=["chrome_133", "firefox_120", "safari_17_0"]
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def stealth(cls, proxies_manager: Optional[ProxiesManager] = None) -> 'ClientConfiguration':
|
|
105
|
+
"""
|
|
106
|
+
Configuração stealth: foco em evitar detecção através de diversidade.
|
|
107
|
+
|
|
108
|
+
Ideal para cenários onde ser discreto é mais importante que velocidade,
|
|
109
|
+
utilizando múltiplos client identifiers e randomização.
|
|
110
|
+
|
|
111
|
+
Características:
|
|
112
|
+
- Múltiplos client identifiers (Chrome, Firefox, Safari, Edge)
|
|
113
|
+
- Ordem de extensões TLS randomizada
|
|
114
|
+
- Rotação moderada de proxies (100 requisições)
|
|
115
|
+
- Rotação de client identifier a cada 30 requisições
|
|
116
|
+
- Recuperação básica apenas para erros críticos
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
proxies_manager: Gerenciador de proxies (opcional mas recomendado)
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
ClientConfiguration configurado para modo stealth
|
|
123
|
+
|
|
124
|
+
Example:
|
|
125
|
+
>>> tls = NotifyTLSClient(ClientConfiguration.stealth(proxies))
|
|
126
|
+
"""
|
|
127
|
+
return cls(
|
|
128
|
+
proxies_manager=proxies_manager,
|
|
129
|
+
rotation=RotationConfig(
|
|
130
|
+
requests_limit_same_proxy=100,
|
|
131
|
+
requests_limit_same_client_identifier=30,
|
|
132
|
+
random_tls_extension_order=True
|
|
133
|
+
),
|
|
134
|
+
recovery=RecoveryConfig(
|
|
135
|
+
instantiate_new_client_on_forbidden_response=False,
|
|
136
|
+
instantiate_new_client_on_exception=True,
|
|
137
|
+
change_client_identifier_on_forbidden_response=False,
|
|
138
|
+
status_codes_to_forbidden_response_handle=[403]
|
|
139
|
+
),
|
|
140
|
+
client=ClientConfig(
|
|
141
|
+
client_identifiers=[
|
|
142
|
+
"chrome_133",
|
|
143
|
+
"firefox_120",
|
|
144
|
+
"safari_17_0",
|
|
145
|
+
"safari_ios_16_0"
|
|
146
|
+
]
|
|
147
|
+
)
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
@classmethod
|
|
151
|
+
def simple(cls, proxies_manager: Optional[ProxiesManager] = None) -> 'ClientConfiguration':
|
|
152
|
+
"""
|
|
153
|
+
Configuração simples: apenas rotação básica de proxies.
|
|
154
|
+
|
|
155
|
+
Ideal para uso geral onde não há necessidade de evasão agressiva,
|
|
156
|
+
mantendo a simplicidade e confiabilidade.
|
|
157
|
+
|
|
158
|
+
Características:
|
|
159
|
+
- Client identifier único (Chrome 133)
|
|
160
|
+
- Rotação de proxy padrão (1000 requisições)
|
|
161
|
+
- Sem rotação de client identifier
|
|
162
|
+
- Sem recuperação automática
|
|
163
|
+
- Monitora apenas status 403
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
proxies_manager: Gerenciador de proxies (opcional)
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
ClientConfiguration configurado para modo simples
|
|
170
|
+
|
|
171
|
+
Example:
|
|
172
|
+
>>> tls = NotifyTLSClient(ClientConfiguration.simple())
|
|
173
|
+
"""
|
|
174
|
+
return cls(
|
|
175
|
+
proxies_manager=proxies_manager,
|
|
176
|
+
rotation=RotationConfig(
|
|
177
|
+
requests_limit_same_proxy=1000,
|
|
178
|
+
requests_limit_same_client_identifier=-1,
|
|
179
|
+
random_tls_extension_order=False
|
|
180
|
+
),
|
|
181
|
+
recovery=RecoveryConfig(
|
|
182
|
+
instantiate_new_client_on_forbidden_response=False,
|
|
183
|
+
instantiate_new_client_on_exception=False,
|
|
184
|
+
change_client_identifier_on_forbidden_response=False,
|
|
185
|
+
status_codes_to_forbidden_response_handle=[403]
|
|
186
|
+
),
|
|
187
|
+
client=ClientConfig(
|
|
188
|
+
client_identifiers=["chrome_133"]
|
|
189
|
+
)
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
@classmethod
|
|
193
|
+
def mobile(cls, proxies_manager: Optional[ProxiesManager] = None, platform: str = "android") -> 'ClientConfiguration':
|
|
194
|
+
"""
|
|
195
|
+
Configuração mobile: simula dispositivos móveis.
|
|
196
|
+
|
|
197
|
+
Ideal para APIs ou sites que têm comportamento diferente para mobile.
|
|
198
|
+
|
|
199
|
+
Características:
|
|
200
|
+
- Client identifiers mobile (Android ou iOS)
|
|
201
|
+
- Rotação moderada
|
|
202
|
+
- Recuperação automática em erros
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
proxies_manager: Gerenciador de proxies (opcional)
|
|
206
|
+
platform: "android" ou "ios"
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
ClientConfiguration configurado para modo mobile
|
|
210
|
+
|
|
211
|
+
Example:
|
|
212
|
+
>>> tls = NotifyTLSClient(ClientConfiguration.mobile(platform="ios"))
|
|
213
|
+
"""
|
|
214
|
+
if platform.lower() == "android":
|
|
215
|
+
identifiers = ["okhttp4_android_13", "okhttp4_android_12"]
|
|
216
|
+
elif platform.lower() == "ios":
|
|
217
|
+
identifiers = ["safari_ios_16_0", "safari_ios_15_6"]
|
|
218
|
+
else:
|
|
219
|
+
raise ValueError(f"Platform inválida: {platform}. Use 'android' ou 'ios'")
|
|
220
|
+
|
|
221
|
+
return cls(
|
|
222
|
+
proxies_manager=proxies_manager,
|
|
223
|
+
rotation=RotationConfig(
|
|
224
|
+
requests_limit_same_proxy=50,
|
|
225
|
+
requests_limit_same_client_identifier=100,
|
|
226
|
+
random_tls_extension_order=True
|
|
227
|
+
),
|
|
228
|
+
recovery=RecoveryConfig(
|
|
229
|
+
instantiate_new_client_on_forbidden_response=True,
|
|
230
|
+
instantiate_new_client_on_exception=True,
|
|
231
|
+
change_client_identifier_on_forbidden_response=False,
|
|
232
|
+
status_codes_to_forbidden_response_handle=[403, 429]
|
|
233
|
+
),
|
|
234
|
+
client=ClientConfig(
|
|
235
|
+
client_identifiers=identifiers
|
|
236
|
+
)
|
|
237
|
+
)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configurações de recuperação automática em caso de erros e respostas proibidas.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class RecoveryConfig:
|
|
10
|
+
"""
|
|
11
|
+
Configurações para controlar o comportamento de recuperação automática.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
instantiate_new_client_on_forbidden_response: Se True, cria uma nova sessão TLS
|
|
15
|
+
quando receber um status code configurado como "proibido".
|
|
16
|
+
Default: False
|
|
17
|
+
|
|
18
|
+
instantiate_new_client_on_exception: Se True, cria uma nova sessão TLS
|
|
19
|
+
quando ocorrer uma exceção durante a requisição.
|
|
20
|
+
Default: False
|
|
21
|
+
|
|
22
|
+
change_client_identifier_on_forbidden_response: Se True, além de criar uma nova
|
|
23
|
+
sessão, também troca o client identifier quando receber resposta proibida.
|
|
24
|
+
Requer instantiate_new_client_on_forbidden_response=True para ter efeito.
|
|
25
|
+
Default: False
|
|
26
|
+
|
|
27
|
+
status_codes_to_forbidden_response_handle: Lista de status codes HTTP que devem
|
|
28
|
+
acionar os mecanismos de recuperação automática.
|
|
29
|
+
Default: [403]
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
>>> # Recuperação agressiva
|
|
33
|
+
>>> config = RecoveryConfig(
|
|
34
|
+
... instantiate_new_client_on_forbidden_response=True,
|
|
35
|
+
... instantiate_new_client_on_exception=True,
|
|
36
|
+
... change_client_identifier_on_forbidden_response=True,
|
|
37
|
+
... status_codes_to_forbidden_response_handle=[403, 429, 503]
|
|
38
|
+
... )
|
|
39
|
+
|
|
40
|
+
>>> # Sem recuperação automática
|
|
41
|
+
>>> config = RecoveryConfig()
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
instantiate_new_client_on_forbidden_response: bool = False
|
|
45
|
+
instantiate_new_client_on_exception: bool = False
|
|
46
|
+
change_client_identifier_on_forbidden_response: bool = False
|
|
47
|
+
status_codes_to_forbidden_response_handle: list[int] = field(default_factory=lambda: [403])
|
|
48
|
+
|
|
49
|
+
def __post_init__(self):
|
|
50
|
+
"""Valida os parâmetros de configuração."""
|
|
51
|
+
if not self.status_codes_to_forbidden_response_handle:
|
|
52
|
+
raise ValueError("status_codes_to_forbidden_response_handle não pode ser vazio")
|
|
53
|
+
|
|
54
|
+
for code in self.status_codes_to_forbidden_response_handle:
|
|
55
|
+
if not isinstance(code, int) or code < 100 or code > 599:
|
|
56
|
+
raise ValueError(f"Status code inválido: {code}. Deve estar entre 100 e 599")
|
|
57
|
+
|
|
58
|
+
# Aviso sobre configuração inconsistente
|
|
59
|
+
if self.change_client_identifier_on_forbidden_response and not self.instantiate_new_client_on_forbidden_response:
|
|
60
|
+
import warnings
|
|
61
|
+
warnings.warn(
|
|
62
|
+
"change_client_identifier_on_forbidden_response=True não terá efeito "
|
|
63
|
+
"porque instantiate_new_client_on_forbidden_response=False. "
|
|
64
|
+
"Considere habilitar ambos.",
|
|
65
|
+
UserWarning
|
|
66
|
+
)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configurações de rotação de proxies e client identifiers.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class RotationConfig:
|
|
10
|
+
"""
|
|
11
|
+
Configurações para controlar a rotação automática de proxies e client identifiers.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
requests_limit_same_proxy: Número máximo de requisições com o mesmo proxy.
|
|
15
|
+
Use -1 para desabilitar rotação automática por limite.
|
|
16
|
+
Default: 1000
|
|
17
|
+
|
|
18
|
+
requests_limit_same_client_identifier: Número máximo de requisições com o mesmo
|
|
19
|
+
client identifier. Use -1 para desabilitar rotação automática por limite.
|
|
20
|
+
Default: -1 (desabilitado)
|
|
21
|
+
|
|
22
|
+
random_tls_extension_order: Se True, randomiza a ordem das extensões TLS
|
|
23
|
+
para dificultar fingerprinting.
|
|
24
|
+
Default: True
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
>>> # Rotação agressiva
|
|
28
|
+
>>> config = RotationConfig(
|
|
29
|
+
... requests_limit_same_proxy=10,
|
|
30
|
+
... requests_limit_same_client_identifier=50
|
|
31
|
+
... )
|
|
32
|
+
|
|
33
|
+
>>> # Sem rotação automática
|
|
34
|
+
>>> config = RotationConfig(
|
|
35
|
+
... requests_limit_same_proxy=-1,
|
|
36
|
+
... requests_limit_same_client_identifier=-1
|
|
37
|
+
... )
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
requests_limit_same_proxy: int = 1000
|
|
41
|
+
requests_limit_same_client_identifier: int = -1
|
|
42
|
+
random_tls_extension_order: bool = True
|
|
43
|
+
|
|
44
|
+
def __post_init__(self):
|
|
45
|
+
"""Valida os parâmetros de configuração."""
|
|
46
|
+
if self.requests_limit_same_proxy < -1:
|
|
47
|
+
raise ValueError("requests_limit_same_proxy deve ser >= -1")
|
|
48
|
+
|
|
49
|
+
if self.requests_limit_same_client_identifier < -1:
|
|
50
|
+
raise ValueError("requests_limit_same_client_identifier deve ser >= -1")
|
|
51
|
+
|
|
52
|
+
if self.requests_limit_same_proxy == 0:
|
|
53
|
+
raise ValueError("requests_limit_same_proxy não pode ser 0 (use -1 para desabilitar)")
|
|
54
|
+
|
|
55
|
+
if self.requests_limit_same_client_identifier == 0:
|
|
56
|
+
raise ValueError("requests_limit_same_client_identifier não pode ser 0 (use -1 para desabilitar)")
|
{notify_tls_client-0.1.9 → notify_tls_client-2.0.0}/notify_tls_client/core/client/decorators.py
RENAMED
|
@@ -38,8 +38,8 @@ def request_guard_decorator(callback):
|
|
|
38
38
|
request_headers = value
|
|
39
39
|
|
|
40
40
|
try:
|
|
41
|
-
self.requests_amount += 1
|
|
42
41
|
res = callback(*args, **kwargs)
|
|
42
|
+
self.requests_amount += 1
|
|
43
43
|
_log_request_info(self, res, request_url, request_headers)
|
|
44
44
|
_requests_handler(self, res)
|
|
45
45
|
return res
|
|
@@ -63,10 +63,14 @@ def request_guard_decorator(callback):
|
|
|
63
63
|
def _recover_on_exception(self):
|
|
64
64
|
if self.instantiate_new_client_on_exception:
|
|
65
65
|
logging.info("Instantiating new TLS client due to exception...")
|
|
66
|
-
self.
|
|
67
|
-
self.
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
current_identifier = self.client_identifiers_manager.get_current_item()
|
|
67
|
+
next_proxy = self.proxies_manager.get_next() if self.proxies_manager else None
|
|
68
|
+
self._create_new_client(
|
|
69
|
+
client_identifier=current_identifier or 'chrome_133',
|
|
70
|
+
random_tls_extension_order=self.random_tls_extension_order,
|
|
71
|
+
proxy=next_proxy
|
|
72
|
+
)
|
|
73
|
+
elif self.proxies_manager:
|
|
70
74
|
logging.info("Changing proxy due to exception...")
|
|
71
75
|
self.change_proxy()
|
|
72
76
|
|
|
@@ -194,46 +198,3 @@ def _requests_handler(self, response: Optional[Response]):
|
|
|
194
198
|
if has_proxies_manager:
|
|
195
199
|
logging.info("Changing proxy due to requests limit reached...")
|
|
196
200
|
self.change_proxy()
|
|
197
|
-
|
|
198
|
-
# if client_identifier_changed_by_limit:
|
|
199
|
-
# logging.info("Requests limit reached for current client identifier")
|
|
200
|
-
# logging.info("Changing client identifier due to requests limit reached...")
|
|
201
|
-
# # self._create_new_client(self.client_identifiers_manager.get_next(),
|
|
202
|
-
# # proxy=self.proxies_manager.get_current_proxy() if self.proxies_manager else None)
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
# if self.requests_limit_with_same_proxy and self.requests_amount_with_current_proxy >= self.requests_limit_with_same_proxy:
|
|
208
|
-
# logging.info("Requests limit reached for current proxy")
|
|
209
|
-
# if self.proxies_manager:
|
|
210
|
-
# proxy_changed_by_limit = True
|
|
211
|
-
# logging.info("Changing proxy due to requests limit reached...")
|
|
212
|
-
#
|
|
213
|
-
# if response.status_code in self.status_codes_to_forbidden_response_handle and self.instantiate_new_client_on_forbidden_response:
|
|
214
|
-
# logging.info("Instantiating new TLS client due to forbidden response before changing proxy...")
|
|
215
|
-
# self.get_tls().close()
|
|
216
|
-
# if self.change_client_identifier_on_forbidden_response:
|
|
217
|
-
# self._create_new_client(self.client_identifiers_manager.get_next())
|
|
218
|
-
# else:
|
|
219
|
-
# self._create_new_client(self.client_identifiers_manager.get_current())
|
|
220
|
-
#
|
|
221
|
-
# self.change_proxy()
|
|
222
|
-
#
|
|
223
|
-
# # Metodo vai ter que ser usado para não trocar o proxy duas vezes, uma pelo status 403 e outra pelo limite de requests
|
|
224
|
-
# if not proxy_changed_by_limit:
|
|
225
|
-
# return
|
|
226
|
-
#
|
|
227
|
-
# if response.status_code in self.status_codes_to_forbidden_response_handle:
|
|
228
|
-
# if self.instantiate_new_client_on_forbidden_response:
|
|
229
|
-
# logging.info("Instantiating new TLS client due to forbidden response...")
|
|
230
|
-
# self.get_tls().close()
|
|
231
|
-
#
|
|
232
|
-
# if self.change_client_identifier_on_forbidden_response:
|
|
233
|
-
# self._create_new_client(self.client_identifiers_manager.get_next())
|
|
234
|
-
# else:
|
|
235
|
-
# self._create_new_client()
|
|
236
|
-
#
|
|
237
|
-
# if self.proxies_manager:
|
|
238
|
-
# logging.info("Changing proxy due to forbidden response...")
|
|
239
|
-
# self.change_proxy()
|
|
@@ -1,45 +1,44 @@
|
|
|
1
|
+
import threading
|
|
1
2
|
from dataclasses import dataclass, field
|
|
2
3
|
from typing import Optional
|
|
3
4
|
|
|
4
|
-
from dataclasses_json import dataclass_json
|
|
5
|
-
|
|
6
5
|
from notify_tls_client.tls_client.settings import ClientIdentifiers
|
|
7
6
|
|
|
8
7
|
|
|
9
|
-
@dataclass_json
|
|
10
8
|
@dataclass
|
|
11
9
|
class ClientIdentifiersManager:
|
|
12
10
|
_items: list[ClientIdentifiers] = field(default_factory=list)
|
|
13
11
|
_current_index: int = 0
|
|
14
12
|
_current_item: Optional[ClientIdentifiers] = None
|
|
15
13
|
|
|
14
|
+
def __post_init__(self):
|
|
15
|
+
self._lock = threading.Lock()
|
|
16
|
+
|
|
16
17
|
def get_next(self) -> ClientIdentifiers:
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
with self._lock:
|
|
19
|
+
if not self._items:
|
|
20
|
+
raise Exception('No items available')
|
|
19
21
|
|
|
20
|
-
if self._current_index < len(self._items):
|
|
21
|
-
_item = self._items[self._current_index]
|
|
22
|
-
self._current_index += 1
|
|
23
|
-
else:
|
|
24
|
-
self._current_index = 0
|
|
25
22
|
_item = self._items[self._current_index]
|
|
23
|
+
self._current_item = _item
|
|
24
|
+
self._current_index = (self._current_index + 1) % len(self._items)
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
return _item
|
|
28
27
|
|
|
29
28
|
def get_current_item(self) -> Optional[ClientIdentifiers]:
|
|
30
|
-
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
return self._items[0] if self._current_index == 0 else self._items[self._current_index - 1]
|
|
34
|
-
|
|
29
|
+
with self._lock:
|
|
30
|
+
return self._current_item
|
|
35
31
|
|
|
36
32
|
def set_items(self, items: list[ClientIdentifiers]):
|
|
37
|
-
self.
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
with self._lock:
|
|
34
|
+
self._items = items
|
|
35
|
+
self._current_index = 0
|
|
36
|
+
self._current_item = None
|
|
40
37
|
|
|
41
38
|
def get_item(self) -> list[ClientIdentifiers]:
|
|
42
|
-
|
|
39
|
+
with self._lock:
|
|
40
|
+
return self._items.copy()
|
|
43
41
|
|
|
44
42
|
def get_total_items(self) -> int:
|
|
45
|
-
|
|
43
|
+
with self._lock:
|
|
44
|
+
return len(self._items)
|