Порівнюємо Python-бібліотеки для обходу бот-захисту

💡 Усі статті, обговорення, новини про Python — в одному місці. Приєднуйтесь до Python спільноти!

Мене звати Дмитро, я займаюсь збором даних з інтернету та автоматизацією процесів, пов’язаних з цим. Перед тим, як розпочати, хочу наголосити: важливо цінувати чужу працю, уникати зайвих і непотрібних запитів, не спамити вебсайти та завжди діяти відповідно до законодавства.

Штучний інтелект стає дедалі важливішим у нашому житті, впливаючи на різні аспекти суспільства. Його роль також суттєво змінила світ вебскрапінгу. Через агресивні стратегії збору даних, які використовують команди, що працюють над ШІ (оскільки всі намагаються зібрати більше даних для покращення своїх моделей), вебсайти посилили захист від ботів. У результаті традиційні інструменти для збору даних з вебсайтів або вебскрапінгу, як-от scrapy і requests, стають менш ефективними.

Для обходу цього захисту були розроблені спеціалізовані бібліотеки, такі як tls_client та curl_cffi. Обидві бібліотеки дозволяють імітувати поведінку реального користувача, обходячи тим самим фільтри, що блокують ботів. Однак між ними є низка відмінностей...

tls_client

На сторінці PyPI є лише мінімальний опис бібліотеки tls_client:


## Python-TLS-Client
Python-TLS-Client is an advanced HTTP library based on requests and tls-client.

## Examples
The syntax is inspired by requests, so its very similar and there are only very few things that are different.
  

Не багато, чесно кажучи, тому я вирішив запитати у бота Gemini: «What is the tls_client Python library?». Ось фрагмент відповіді:


The `tls_client` library in Python is a specialized tool designed for making HTTP requests with a strong focus on bypassing bot protection mechanisms. It achieves this by:

- **TLS Fingerprinting**: The library can mimic the TLS fingerprint of specific browsers, making it appear as if a real browser is making the request. This helps to evade bot detection systems that rely on analyzing TLS characteristics.
- **Browser-like Behavior**: `tls_client` can simulate various aspects of browser behavior, such as:
  - User-Agent headers  
  - HTTP request methods  
  - Request timing and intervals  
  - Other relevant browser-specific features  
- **Stealth**: By mimicking real browser behavior, `tls_client` can help your requests remain undetected by bot protection systems, allowing you to bypass these mechanisms effectively.

Загалом все зрозуміло. Не хотілося б зупинятися на деталях бібліотеки. Краще перевіримо, як вона працює з вебсайтами (запитами). Нижче наведено скрипт, який я використав для цього експерименту. Подивімося, які саме headers передає ця бібліотека за замовчуванням.


import tls_client
session = tls_client.Session(
    client_identifier="chrome112",
    random_tls_extension_order=True
)

res = session.get(
    "https://postman-echo.com/headers",
)

print(res.text)
    

Pезультат:


[{
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t1737710233.424",
    "connection": "close",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-67935a99-606f44314d13eab1020b3628"
  }
},
{
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t1737710233.424",
    "connection": "close",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-67935a99-606f44314d13eab1020b3628",
    "host": "postman-echo.com",
    "x-request-start": "t1737710233.424",
    "connection": "close",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-67935a99-606f44314d13eab1020b3628",
    "connection": "close",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-67935a99-606f44314d13eab1020b3628",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-67935a99-606f44314d13eab1020b3628",
    "x-amzn-trace-id": "Root=1-67935a99-606f44314d13eab1020b3628",
    "accept": "*/*",
    "accept-encoding": "gzip, deflate, br",
    "user-agent": "tls-client/1.0.1"
  }
}]  
    

Мене зацікавив headers user-agent, який за замовчуванням встановлено на «tls-client/1.0.1». Хоча я використовую параметр client_identifier під час створення сесії, це значення залишилося незмінним. Щоб дізнатися, на що впливає цей параметр, скористаємося іншим API: tls.browserleaks.com/json

Приклад із ‘chrome112’:


# chrome112
import tls_client
session = tls_client.Session(
    client_identifier="chrome112",
    random_tls_extension_order=True
)

res = session.get(
    "https://tls.browserleaks.com/json",
)

print(res.text)  
    

Результат:


{
  "user_agent": "tls-client/1.0.1",
  "ja3_hash": "957c782e99776836574ba40d58efef94",
  "ja3_text": "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-17513-43-16-13-51-10-11-35-65037-23-45-18-65281-5-27,29-23-24,0",
  "ja3n_hash": "473f0e7c0b6a0f7b049072f4e683068b",
  "ja3n_text": "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-23-27-35-43-45-51-17513-65037-65281,29-23-24,0",
  "ja4": "t13d1516h2_8daaf6152771_02713d6af862",
  "ja4_r": "t13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,4469,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601",
  "ja4_o": "t13d1516h2_acb858a92679_28a27801816f",
  "ja4_ro": "t13d1516h2_1301,1302,1303,c02b,c02f,c02c,c030,cca9,cca8,c013,c014,009c,009d,002f,0035_0000,4469,002b,0010,000d,0033,000a,000b,0023,fe0d,0017,002d,0012,ff01,0005,001b_0403,0804,0401,0503,0805,0501,0806,0601",
  "akamai_hash": "52d84b11737d980aef856699f885ca86",
  "akamai_text": "1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p"
}
    

Приклад із firefox_117:


# firefox_117
import tls_client
session = tls_client.Session(
    client_identifier="firefox_117",
    random_tls_extension_order=True
)

res = session.get(
    "https://tls.browserleaks.com/json",
)

print(res.text)
    

Результат:


{
  "user_agent": "tls-client/1.0.1",
  "ja3_hash": "d519c3b590dc914c42d977672aa9c08b",
  "ja3_text": "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-156-157-47-53,35-16-23-11-13-10-5-0-43-51-34-45-28-65281-21,29-23-24-25-256-257,0",
  "ja3n_hash": "b1efda11c805621e0f9cdc311958cb8c",
  "ja3n_text": "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-156-157-47-53,0-5-10-11-13-16-21-23-28-34-35-43-45-51-65281,29-23-24-25-256-257,0",
  "ja4": "t13d1715h2_5b57614c22b0_3d5424432f57",
  "ja4_r": "t13d1715h2_002f,0035,009c,009d,1301,1302,1303,c009,c00a,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0015,0017,001c,0022,0023,002b,002d,0033,ff01_0403,0503,0603,0804,0805,0806,0401,0501,0601,0203,0201",
  "ja4_o": "t13d1715h2_5b234860e130_3f89fa4fa9d8",
  "ja4_ro": "t13d1715h2_1301,1303,1302,c02b,c02f,cca9,cca8,c02c,c030,c00a,c009,c013,c014,009c,009d,002f,0035_0023,0010,0017,000b,000d,000a,0005,0000,002b,0033,0022,002d,001c,ff01,0015_0403,0503,0603,0804,0805,0806,0401,0501,0601,0203,0201",
  "akamai_hash": "3d9132023bf26a71d40fe766e5c24c9d",
  "akamai_text": "1:65536;4:131072;5:16384|12517377|3:0:0:201,5:0:0:101,7:0:0:1,9:0:7:1,11:0:3:1,13:0:0:241|m,p,a,s"
}
    

Тотбо:

  • client_identifier не впливає на user-agent.
  • А от ja3_hash, ja3_text та akamai_hash суттєво відрізняються між chrome112 і firefox_117.

Я не заглиблюватимусь у деталі про те, що таке ja3_hash, ja3_text та akamai_hash, оскільки це не є темою цієї статті. Але ці параметри відіграють важливу роль в обході захисту від ботів.

curl_cffi

У README.md на GitHub ми знаходимо такий опис:


# curl_cffi
...

[Documentation](https://curl-cffi.readthedocs.io)

Python binding for [curl-impersonate fork](https://github.com/lexiforest/curl-impersonate)
via [cffi](https://cffi.readthedocs.io/en/latest/).

Unlike other pure python http clients like `httpx` or `requests`, `curl_cffi` can
impersonate browsers' TLS/JA3 and HTTP/2 fingerprints. If you are blocked by some
website for no obvious reason, you can give `curl_cffi` a try.

Only Python 3.8 and above are supported. Python 3.7 has reached its end of life.

...

Хмм, цікаво. Запитаємо у Gemini більше інформації.


`curl_cffi` is a Python library that provides a powerful and efficient interface to the `libcurl` library, a widely-used open-source library for transferring data using various protocols (HTTP, FTP, etc.).

## Key Features

- High Performance: Leverages the speed and robustness of libcurl for fast and reliable HTTP requests.
- Low-Level Control: Offers fine-grained control over the request and response process, including:
    - Custom headers
    - SSL/TLS configuration
    - Proxy settings
    - HTTP m  ethods (GET, POST, PUT, etc.)
    - Handling cookies and authentication
- Thread Safety: Safe to use in multi-threaded applications.
- Cross-Platform Compatibility: Works across various operating systems.
- CFFI-based: Built on top of C Foreign Function Interface (CFFI), enabling efficient interaction with C libraries.
...

З опису видно, що curl_cffi підтримує не тільки HTTP/HTTPS (тоді як tls_client зосереджується на імітації поведінки браузера для обходу захисту від ботів), але й інші протоколи, наприклад FTP. Однак у контексті вебскрапінгу ця різниця може бути несуттєвою.

Перевіримо, що повертає бібліотека curl_cffi при виконанні запиту.


from curl_cffi import requests

r = requests.get("https://postman-echo.com/headers", impersonate="chrome")

print(r.text)
    

Результат:


{
  "headers": {
    "host": "postman-echo.com",
    "x-request-start": "t1737713426.623",
    "connection": "close",
    "x-forwarded-proto": "https",
    "x-forwarded-port": "443",
    "x-amzn-trace-id": "Root=1-67936712-115e75d03764be6e18a2df05",
    "sec-ch-ua": "\"Chromium\";v=\"124\", \"Google Chrome\";v=\"124\", \"Not-A.Brand\";v=\"99\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"macOS\"",
    "upgrade-insecure-requests": "1",
    "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
    "sec-fetch-site": "none",
    "sec-fetch-mode": "navigate",
    "sec-fetch-user": "?1",
    "sec-fetch-dest": "document",
    "accept-encoding": "gzip, deflate, br, zstd",
    "accept-language": "en-US,en;q=0.9",
    "priority": "u=0, i"
  }
}
    

Звідси видно, що багато headers встановлюються без нашої участі, і не потрібно турбуватися про відповідність impersonate/client_identifier із headers (наприклад, user-agent). Це зручно, хоча й не завжди. Як і все в житті...

Перевіримо різницю у fingerprint для різних impersonate.

Для chrome:


from curl_cffi import requests

r = requests.get("https://tls.browserleaks.com/json", impersonate="chrome")

print(r.text)
class="sourceCode python"><a href="#cb13-1" aria-hidden="true"></a><code>from curl_cffi import requests
</code><a href="#cb13-2" aria-hidden="true"></a><code>
</code><a href="#cb13-3" aria-hidden="true"></a><code>r = requests.get("https://tls.browserleaks.com/json", impersonate="chrome")
</code><a href="#cb13-4" aria-hidden="true"></a><code>
</code><a href="#cb13-5" aria-hidden="true"></a><code>print(r.text)</code>

Результат:


{
  "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
  "ja3_hash": "f1bfb5be52bd682e3aa5b4f1aa6aff4b",
  "ja3_text": "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,23-13-45-5-35-18-17513-27-43-65037-11-65281-51-16-0-10,25497-29-23-24,0",
  "ja3n_hash": "4c9ce26028c11d7544da00d3f7e4f45c",
  "ja3n_text": "771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-5-10-11-13-16-18-23-27-35-43-45-51-17513-65037-65281,25497-29-23-24,0",
  "ja4": "t13d1516h2_8daaf6152771_02713d6af862",
  "ja4_r": "t13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,4469,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601",
  "ja4_o": "t13d1516h2_acb858a92679_a605c1873949",
  "ja4_ro": "t13d1516h2_1301,1302,1303,c02b,c02f,c02c,c030,cca9,cca8,c013,c014,009c,009d,002f,0035_0017,000d,002d,0005,0023,0012,4469,001b,002b,fe0d,000b,ff01,0033,0010,0000,000a_0403,0804,0401,0503,0805,0501,0806,0601",
  "akamai_hash": "52d84b11737d980aef856699f885ca86",
  "akamai_text": "1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p"
}
    

Для safari17_0:


from curl_cffi import requests

r = requests.get("https://tls.browserleaks.com/json", impersonate="safari17_0")

print(r.text)
    

Результат:


{
  "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
  "ja3_hash": "773906b0efdefa24a7f2b8eb6985bf37",
  "ja3_text": "771,4865-4866-4867-49196-49195-52393-49200-49199-52392-49162-49161-49172-49171-157-156-53-47-49160-49170-10,0-23-65281-10-11-16-5-13-18-51-45-43-27-21,29-23-24-25,0",
  "ja3n_hash": "44f7ed5185d22c92b96da72dbe68d307",
  "ja3n_text": "771,4865-4866-4867-49196-49195-52393-49200-49199-52392-49162-49161-49172-49171-157-156-53-47-49160-49170-10,0-5-10-11-13-16-18-21-23-27-43-45-51-65281,29-23-24-25,0",        
  "ja4": "t13d2014h2_a09f3c656075_14788d8d241b",
  "ja4_r": "t13d2014h2_000a,002f,0035,009c,009d,1301,1302,1303,c008,c009,c00a,c012,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0015,0017,001b,002b,002d,0033,ff01_0403,0804,0401,0503,0203,0805,0805,0501,0806,0601,0201",
  "ja4_o": "t13d2014h2_de3eb69493ac_65135c5c1a6b",
  "ja4_ro": "t13d2014h2_1301,1302,1303,c02c,c02b,cca9,c030,c02f,cca8,c00a,c009,c014,c013,009d,009c,0035,002f,c008,c012,000a_0000,0017,ff01,000a,000b,0010,0005,000d,0012,0033,002d,002b,001b,0015_0403,0804,0401,0503,0203,0805,0805,0501,0806,0601,0201",
  "akamai_hash": "959a7e813b79b909a1a0b00a38e8bba3",
  "akamai_text": "2:0;4:4194304;3:100|10485760|0|m,s,p,a"
}
    

Результати для сhrome та safari17_0 показують різні фінгерпринти (ja3_hash, ja3_text, akamai_hash тощо). Ця різниця демонструє, як проста зміна impersonate може суттєво вплинути на фінгерпринт HTTP-запиту, що допомагає обходити захист від ботів.

Висновок

Я не стверджую, що curl_cffi краща чи гірша за tls_client; моєю метою було показати, як працюють обидві бібліотеки, та розглянути ключові концепції. Остаточний вибір залежить від ваших потреб. Якщо вас цікавить порівняння швидкості цих бібліотек, ознайомтеся з цим топіком.

👍ПодобаєтьсяСподобалось6
До обраногоВ обраному4
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Ціль яка? Простий обхід чи антидетект потрібен? Бо якщо останнє, то все це не допоможе

подивитися в чому відміність в бібліотеках

Питати в ші дані про ліби? Воно тобі розкаже, і потім в самий не підходящий момент прийдеться лапшу з вух знімати.

Як на мене Virtaul Box SDK ще кращий спосіб імітувати поведінку реального користувача.

Підписатись на коментарі