diff --git a/README.md b/README.md index 0d2fc0e..bf51b08 100644 --- a/README.md +++ b/README.md @@ -9,15 +9,15 @@ pip install tls-client # Examples The syntax is inspired by [requests](https://github.com/psf/requests), so its very similar and there are only very few things that are different. -Example 1 - Preset: +Example - Preset: ```python import tls_client -# `client_identifier` picks the browser profile to impersonate, e.g. chrome_120, firefox_120, safari_ios_17_0 or +# `client_identifier` picks the browser profile to impersonate, e.g. chrome_152, firefox_148, safari_ios_26_0 or # okhttp4_android_13. Every supported name is listed in tls_client/settings.py; unknown names raise a ValueError. session = tls_client.Session( - client_identifier="chrome_112", + client_identifier="chrome_152", random_tls_extension_order=True ) @@ -30,116 +30,14 @@ res = session.get( ) ``` -Example 2 - Custom: -```python -import tls_client - -session = tls_client.Session( - # None switches from a preset to the settings below. Without it they are all ignored. - client_identifier=None, - ja3_string="771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0", - h2_settings={ - "HEADER_TABLE_SIZE": 65536, - "MAX_CONCURRENT_STREAMS": 1000, - "INITIAL_WINDOW_SIZE": 6291456, - "MAX_HEADER_LIST_SIZE": 262144 - }, - h2_settings_order=[ - "HEADER_TABLE_SIZE", - "MAX_CONCURRENT_STREAMS", - "INITIAL_WINDOW_SIZE", - "MAX_HEADER_LIST_SIZE" - ], - supported_signature_algorithms=[ - "ECDSAWithP256AndSHA256", - "PSSWithSHA256", - "PKCS1WithSHA256", - "ECDSAWithP384AndSHA384", - "PSSWithSHA384", - "PKCS1WithSHA384", - "PSSWithSHA512", - "PKCS1WithSHA512", - ], - supported_versions=["GREASE", "1.3", "1.2"], - key_share_curves=["GREASE", "X25519"], - cert_compression_algo="brotli", - # The JA3 contains ALPN (16) and ALPS (17513), these fill them in. - alpn_protocols=["h2", "http/1.1"], - alps_protocols=["h2"], - pseudo_header_order=[ - ":method", - ":authority", - ":scheme", - ":path" - ], - connection_flow=15663105, - header_order=[ - "accept", - "user-agent", - "accept-encoding", - "accept-language" - ] -) - -res = session.post( - "https://www.example.com/", - headers={ - "key1": "value1", - }, - json={ - "key1": "key2" - } -) -``` - -Example 3 - Async: -```python -import asyncio -import tls_client - -async def main(): - # Takes the same arguments as tls_client.Session. - async with tls_client.AsyncSession(client_identifier="chrome_120") as session: - responses = await asyncio.gather( - *(session.get(f"https://www.example.com/{i}") for i in range(10)) - ) - print([res.status_code for res in responses]) - -asyncio.run(main()) -``` - -# Pyinstaller / Pyarmor -**If you want to pack the library with Pyinstaller or Pyarmor, make sure to add this to your command:** - -Linux with glibc (e.g. Ubuntu, Debian) / x86_64: -``` ---add-binary '{path_to_library}/tls_client/dependencies/tls-client-linux-ubuntu-amd64.so:tls_client/dependencies' -``` - -Linux / ARM64: -``` ---add-binary '{path_to_library}/tls_client/dependencies/tls-client-linux-arm64.so:tls_client/dependencies' -``` - -macOS on Intel x86 based Macs (pre-M1): -``` ---add-binary '{path_to_library}/tls_client/dependencies/tls-client-darwin-amd64.dylib:tls_client/dependencies' -``` - -macOS on Apple Silicon (M1 and later): -``` ---add-binary '{path_to_library}/tls_client/dependencies/tls-client-darwin-arm64.dylib:tls_client/dependencies' -``` - -Windows / 64-bit: -``` ---add-binary '{path_to_library}/tls_client/dependencies/tls-client-windows-64.dll;tls_client/dependencies' -``` - -Windows / 32-bit: -``` ---add-binary '{path_to_library}/tls_client/dependencies/tls-client-windows-32.dll;tls_client/dependencies' -``` +More examples: +- [Custom TLS fingerprint](https://github.com/FlorianREGAZ/Python-Tls-Client/blob/main/examples/example2%20-%20custom.py) +- [Certificate pinning](https://github.com/FlorianREGAZ/Python-Tls-Client/blob/main/examples/example3-%20certificate%20pinning.py) +- [File upload](https://github.com/FlorianREGAZ/Python-Tls-Client/blob/main/examples/example4%20-%20file%20upload.py) +- [Async](https://github.com/FlorianREGAZ/Python-Tls-Client/blob/main/examples/example5%20-%20async.py) +- [Redirects and cookies](https://github.com/FlorianREGAZ/Python-Tls-Client/blob/main/examples/example6%20-%20redirects%20and%20cookies.py) +- [Downloads and encodings](https://github.com/FlorianREGAZ/Python-Tls-Client/blob/main/examples/example7%20-%20download.py) +- [Multiprocessing](https://github.com/FlorianREGAZ/Python-Tls-Client/blob/main/examples/example8%20-%20multiprocessing.py) # Acknowledgements Big shout out to [Bogdanfinn](https://github.com/bogdanfinn) for open sourcing his [tls-client](https://github.com/bogdanfinn/tls-client) in Golang. diff --git a/examples/example1 - preset.py b/examples/example1 - preset.py index e59758c..4a91897 100644 --- a/examples/example1 - preset.py +++ b/examples/example1 - preset.py @@ -1,9 +1,9 @@ import tls_client -# `client_identifier` picks the browser profile to impersonate, e.g. chrome_120, firefox_120, safari_ios_17_0 or +# `client_identifier` picks the browser profile to impersonate, e.g. chrome_152, firefox_148, safari_ios_26_0 or # okhttp4_android_13. Every supported name is listed in tls_client/settings.py; unknown names raise a ValueError. -session = tls_client.Session(client_identifier="chrome_112", random_tls_extension_order=True) +session = tls_client.Session(client_identifier="chrome_152", random_tls_extension_order=True) res = session.get( "https://www.example.com/", diff --git a/examples/example3- certificate pinning.py b/examples/example3- certificate pinning.py index f2c9c3c..ce48849 100644 --- a/examples/example3- certificate pinning.py +++ b/examples/example3- certificate pinning.py @@ -1,13 +1,14 @@ import tls_client session = tls_client.Session( + client_identifier="chrome_152", certificate_pinning={ "example.com": [ "NQvyabcS99nBqk/nZCUF44hFhshrkvxqYtfrZq3i+Ww=", "4a6cdefI7OG6cuDZka5NDZ7FR8a60d3auda+sKfg4Ng=", "x4QzuiC810K5/cMjb05Qm4k3Bw5zBn4lTdO/nEW/Td4=", ] - } + }, ) res = session.get( diff --git a/examples/example4 - file upload.py b/examples/example4 - file upload.py index 808f3f3..7e60541 100644 --- a/examples/example4 - file upload.py +++ b/examples/example4 - file upload.py @@ -1,6 +1,6 @@ import tls_client -session = tls_client.Session(client_identifier="chrome_120") +session = tls_client.Session(client_identifier="chrome_152") with open("report.pdf", "rb") as f: res = session.post( diff --git a/examples/example5 - async.py b/examples/example5 - async.py new file mode 100644 index 0000000..fbfa8f2 --- /dev/null +++ b/examples/example5 - async.py @@ -0,0 +1,13 @@ +import asyncio + +import tls_client + + +async def main() -> None: + # Takes the same arguments as tls_client.Session. + async with tls_client.AsyncSession(client_identifier="chrome_152") as session: + responses = await asyncio.gather(*(session.get(f"https://httpbin.org/get?page={i}") for i in range(10))) + print([res.status_code for res in responses]) + + +asyncio.run(main()) diff --git a/examples/example6 - redirects and cookies.py b/examples/example6 - redirects and cookies.py new file mode 100644 index 0000000..2a72d76 --- /dev/null +++ b/examples/example6 - redirects and cookies.py @@ -0,0 +1,15 @@ +import tls_client + +session = tls_client.Session(client_identifier="chrome_152") + +# Redirects are only followed with allow_redirects=True. +res = session.get("https://httpbin.org/cookies/set?flavour=chocolate", allow_redirects=True) +print(res.url) # https://httpbin.org/cookies +print([r.status_code for r in res.history]) # [302] + +# Cookies set on any hop are kept in the session and sent with later requests. +print(session.cookies.get_dict()) # {'flavour': 'chocolate'} + +# Cookies removed from the session are no longer sent. +session.cookies.clear() +print(session.get("https://httpbin.org/cookies").json()) # {'cookies': {}} diff --git a/examples/example7 - download.py b/examples/example7 - download.py new file mode 100644 index 0000000..e352b81 --- /dev/null +++ b/examples/example7 - download.py @@ -0,0 +1,14 @@ +import tls_client + +session = tls_client.Session(client_identifier="chrome_152") + +# .content is the exact bytes the server sent. timeout is in seconds and can be a float. +res = session.get("https://httpbin.org/image/png", timeout=10) +with open("image.png", "wb") as f: + f.write(res.content) + +# .text is decoded with the charset from the Content-Type header or the page itself. +# Set .encoding when a page declares the wrong one. +res = session.get("https://httpbin.org/html", timeout=2.5) +res.encoding = "iso-8859-1" +print(res.text[:100]) diff --git a/examples/example8 - multiprocessing.py b/examples/example8 - multiprocessing.py new file mode 100644 index 0000000..7d73f40 --- /dev/null +++ b/examples/example8 - multiprocessing.py @@ -0,0 +1,18 @@ +import multiprocessing + +import tls_client + + +def fetch(url: str) -> int: + session = tls_client.Session(client_identifier="chrome_152") + return session.get(url).status_code + + +if __name__ == "__main__": + session = tls_client.Session(client_identifier="chrome_152") + print(session.get("https://www.example.com/").status_code) + + # The parent has made a request, so the native library is loaded and a fork()ed child would hang. + # Use "spawn" or "forkserver" instead. + with multiprocessing.get_context("spawn").Pool(4) as pool: + print(pool.map(fetch, ["https://www.example.com/"] * 8))