-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbasic_usage.py
More file actions
114 lines (89 loc) · 3.58 KB
/
Copy pathbasic_usage.py
File metadata and controls
114 lines (89 loc) · 3.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
"""
Basic usage examples for curlx.
Every request here goes to httpbin.org and needs no proxy or credentials.
"""
import asyncio
from curlx import (
AsyncHttpClient,
CurlxError,
HttpStatusError,
Response,
SyncHttpClient,
)
URLS = [
"https://httpbin.org/get",
"https://httpbin.org/ip",
"https://httpbin.org/user-agent",
]
def report(url: str, result: Response | BaseException) -> None:
"""
Print one ``fetch_all`` result.
``fetch_all`` defaults to ``return_exceptions=True``, so the list it returns
holds a mix of :class:`Response` objects and exceptions. Printing an element
without this check is how a crawl ends up reporting failures as successes.
"""
if isinstance(result, BaseException):
print(f"{url} -> FAILED {type(result).__name__}: {result}")
else:
print(f"{url} -> {result.status_code} {result.reason} in {result.elapsed}")
def sync_example() -> None:
# "chrome" is the default profile: an alias curl_cffi resolves to its newest
# Chrome target, so it moves forward automatically on a curl_cffi upgrade.
# Pin a version ("chrome146") instead when the fingerprint must stay stable.
with SyncHttpClient(impersonate="chrome", timeout=10) as client:
resp = client.get("https://httpbin.org/get")
print("Status:", resp.status_code, resp.reason)
print("Elapsed:", resp.elapsed)
print("JSON:", resp.json())
async def async_example() -> None:
async with AsyncHttpClient(
impersonate="chrome",
max_concurrent=20,
timeout=10,
) as client:
resp = await client.get("https://httpbin.org/get")
print("Status:", resp.status_code, resp.reason)
print("Elapsed:", resp.elapsed)
print("JSON:", resp.json())
async def async_concurrent_fetch() -> None:
"""Fan out with the async client; ``max_concurrent`` bounds requests in flight."""
async with AsyncHttpClient(max_concurrent=5, timeout=10) as client:
results = await client.fetch_all(URLS)
for url, result in zip(URLS, results, strict=True):
report(url, result)
def sync_concurrent_fetch() -> None:
"""
The sync client fans out too, over a thread pool.
``max_concurrent`` caps the worker count. The proxy rotator and rate limiter
are lock-guarded, so sharing one client across those threads is safe.
"""
with SyncHttpClient(max_concurrent=3, timeout=10) as client:
results = client.fetch_all(URLS)
for url, result in zip(URLS, results, strict=True):
report(url, result)
def error_handling() -> None:
"""
curl_cffi's exceptions are translated into the curlx tree.
One ``except CurlxError`` therefore covers transport, TLS, proxy and HTTP
status failures without importing anything from curl_cffi.
"""
# raise_for_status=True turns a 4xx/5xx response into an HttpStatusError
# instead of returning it.
with SyncHttpClient(timeout=10, raise_for_status=True) as client:
try:
client.get("https://httpbin.org/status/503")
except HttpStatusError as exc:
print(f"HTTP {exc.status_code} | retry_after={exc.retry_after}")
except CurlxError as exc:
print(f"Request failed: {type(exc).__name__}: {exc}")
if __name__ == "__main__":
print("=== Sync ===")
sync_example()
print("\n=== Async ===")
asyncio.run(async_example())
print("\n=== Async fetch_all ===")
asyncio.run(async_concurrent_fetch())
print("\n=== Sync fetch_all ===")
sync_concurrent_fetch()
print("\n=== Error handling ===")
error_handling()