HTTPX 是一个新的强大的 Python HTTP 客户端库。当涉及到网络抓取中的 HTTP 连接时,它正迅速成为最受欢迎的选项,因为它提供异步客户端和 http2 支持。
在这个重点教程中,我们将了解是什么让 Python 的 httpx 如此适合网络抓取以及如何有效地使用它。
安装 httpx
HTTPX 是一个纯 Python 包,因此可以使用pip
控制台命令轻松安装:
$ pip install httpx
或者,可以使用 poetry 项目包管理器安装它:
$ poetry init -d httpx # or $ poetry add httpx
使用 HTTPX
HTTPX 可以直接用于单个请求,支持大多数流行的 HTTP 函数,如 GET、POST 请求,并且可以直接将 JSON 响应解包为 python 字典:
import httpx # GET request response = httpx.get("https://httpbin.dev/get") print(response) data = response.json() print(data['url']) # POST requests payload = {"query": "foo"} # application/json content: response = httpx.post("https://httpbin.dev/post", json=payload) # or formdata: response = httpx.post("https://httpbin.dev/post", data=payload) print(response) data = response.json() print(data['url'])
.json()
这里我们使用响应的方法使用 httpx 进行 JSON 加载。Httpx 带有许多方便且可访问的快捷方式,使其成为一个非常易于访问的 Web 抓取 HTTP 客户端。
使用 httpx 客户端
对于 Web 抓取,最好使用httpx.Client
可以为整个 httpx 会话应用自定义设置(例如标头、cookie 和代理以及配置)的工具:
import httpx with httpx.Client( # enable HTTP2 support http2=True, # set headers for all requests headers={"x-secret": "foo"}, # set cookies cookies={"language": "en"}, # set proxxies proxies={ # set proxy for all http:// connections: "http": "http://222.1.1.1:8000", # set proxy for all https:// connections: "https": "http://222.1.1.1:8000", # socks5, socks4 and socks4a proxies can be used as well: "https": "socks5://222.1.1.1:8000", } ) as session:
httpx 客户端对所有请求应用一组配置,甚至跟踪服务器设置的 cookie。
异步使用 httpx
要与 Python 异步使用 httpx,可以使用对象asyncio
:httpx.AsyncClient()
import asyncio import httpx async def main(): async with httpx.AsyncClient( # to limit asynchronous concurrent connections limits can be applied: limits=httpx.Limits(max_connections=10), # tip: increase timeouts for concurrent connections: timeout=httpx.Timeout(60.0), # seconds # note: asyncClient takes in the same arguments like Client (like headers, cookies etc.) ) as client: # to make concurrent requests asyncio.gather can be used: urls = [ "https://httpbin.dev/get", "https://httpbin.dev/get", "https://httpbin.dev/get", ] responses = asyncio.gather(*[client.get(url) for url in urls]) # or asyncio.as_completed: for result in asyncio.as_completed([client.get(url) for url in urls]): response = await result print(response) asyncio.run(main())
请注意,当使用async with
所有连接时,应在关闭语句之前完成async with
,否则将引发异常:
RuntimeError: Cannot send a request, as the client has been closed.
除了该async with
语句之外,还可以手动打开/关闭 httpx AsyncClient:
import asyncio import httpx async def main(): client = httpx.AsyncClient() # do some scraping ... # close client await client.aclose() asyncio.run(main())
HTTPX 故障排除
虽然 Python 的 httpx 是一个很棒的库,但它很容易遇到一些常见的问题。以下是使用 httpx 进行网页抓取时可能遇到的一些常见问题以及如何解决这些问题:
httpx.TimeoutException
httpx.TimeoutExcception
当请求花费的时间超过指定/默认超时持续时间时,会发生错误。尝试提高超时参数:
httpx.get("https://httpbin.org/delay/10", timeout=httpx.Timeout(60.0))
httpx.ConnectError
httpx.ConnectError
当检测到可能由以下原因引起的连接问题时会引发异常:
- 互联网连接不稳定。
- 服务器无法访问。
- URL 参数错误。
httpx.TooManyRedirects
httpx.TooManyRedirects
当请求超过允许的最大重定向次数时引发。
这可能是由抓取的 Web 服务器或 httpx 重定向解析逻辑的问题引起的。它可以通过手动解析重定向来修复:
response = httpx.get( "https://httpbin.dev/redirect/3", allow_redirects=False, # disable automatic redirect handling ) # then we can check whether we want to handle redirecting ourselves: redirect_location = response.headers["Location"]
httpx.HTTPStatusError
httpx.HTTPStatusError
使用raise_for_status=True
不在 200-299 范围内的参数和服务器响应状态代码(如 404)时会引发错误:
response = httpx.get( "https://httpbin.dev/redirect/3", raise_for_status=True, )
当 200-299 范围之外的网络抓取状态代码可能意味着抓取器被阻止。
httpx.UnsupportedProtocol
httpx.UnsupportedProtocol
当协议中提供的 URL 丢失或不属于http://
、https://
或file://
的一部分时,将引发错误ftp://
。当 URL 缺少该部分时,最常遇到这种情况https://
。
重试 HTTPX 请求
HTTPX 客户端没有任何重试功能,但它可以轻松地与 Python 中流行的重试包集成,如tenacity ( pip install tenacity
)。
使用tenacity
我们可以添加重试逻辑,以重试 200-299 范围之外的状态代码、httpx 异常,甚至通过检查响应主体中的失败关键字:
import httpx from tenacity import retry, stop_after_attempt, wait_fixed, retry_if_exception_type, retry_if_result # Define the conditions for retrying based on exception types def is_retryable_exception(exception): return isinstance(exception, (httpx.TimeoutException, httpx.ConnectError)) # Define the conditions for retrying based on HTTP status codes def is_retryable_status_code(response): return response.status_code in [500, 502, 503, 504] # Define the conditions for retrying based on response content def is_retryable_content(response): return "you are blocked" in response.text.lower() # Decorate the function with retry conditions and parameters @retry( retry=(retry_if_exception_type(is_retryable_exception) | retry_if_result(is_retryable_status_code) | retry_if_result(is_retryable_content)), stop=stop_after_attempt(3), wait=wait_fixed(5), ) def fetch_url(url): try: response = httpx.get(url) response.raise_for_status() return response except httpx.RequestError as e: print(f"Request error: {e}") raise e url = "https://httpbin.org/get" try: response = fetch_url(url) print(f"Successfully fetched URL: {url}") print(response.text) except Exception as e: print(f"Failed to fetch URL: {url}") print(f"Error: {e}")
上面我们使用了 tenacity 的retry
装饰器并为常见的 httpx 错误定义了我们的重试规则。
轮换代理重试
当涉及到处理阻塞时,当使用 httpx 代理轮换的 Web 抓取可以与tenacity
重试功能一起使用时。
在这个例子中,我们将看看在抓取块上旋转代理和标头的常见网络抓取模式。我们将添加一个重试:
- 重试状态代码 403 和 404
- 最多重试 5 次
- 在重试之间随机休眠 1-5 秒
- 为每次重试更改随机代理
- 为每次重试更改随机用户代理请求标头
使用 httpx 和韧性:
import httpx import random from tenacity import retry, stop_after_attempt, wait_random, retry_if_result import asyncio PROXY_POOL = [ "http://2.56.119.93:5074", "http://185.199.229.156:7492", "http://185.199.228.220:7300", "http://185.199.231.45:8382", "http://188.74.210.207:6286", "http://188.74.183.10:8279", "http://188.74.210.21:6100", "http://45.155.68.129:8133", "http://154.95.36.199:6893", "http://45.94.47.66:8110", ] USER_AGENT_POOL = [ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:54.0) Gecko/20100101 Firefox/54.0", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0.1 Safari/604.3.5", ] # Define the conditions for retrying based on HTTP status codes def is_retryable_status_code(response): return response.status_code in [403, 404] # callback to modify scrape after each retry def update_scrape_call(retry_state): # change to random proxy on each retry new_proxy = random.choice(PROXY_POOL) new_user_agent = random.choice(USER_AGENT_POOL) print( "retry {attempt_number}: {url} @ {proxy} with a new proxy {new_proxy}".format( attempt_number=retry_state.attempt_number, new_proxy=new_proxy, **retry_state.kwargs ) ) retry_state.kwargs["proxy"] = new_proxy retry_state.kwargs["client_kwargs"]["headers"]["User-Agent"] = new_user_agent @retry( # retry on bad status code retry=retry_if_result(is_retryable_status_code), # max 5 retries stop=stop_after_attempt(5), # wait randomly 1-5 seconds between retries wait=wait_random(min=1, max=5), # update scrape call on each retry before_sleep=update_scrape_call, ) async def scrape(url, proxy, **client_kwargs): async with httpx.AsyncClient( proxies={"http://": proxy, "https://": proxy}, **client_kwargs, ) as client: response = await client.get(url) return response
以上是如何应用重试逻辑的简短演示,该逻辑可以在每次重试时轮换代理和用户代理字符串。
首先,我们定义我们的代理和用户代理池,然后使用@retry
装饰器将我们的抓取功能与坚韧的重试逻辑包装起来。
为了修改每次重试,我们使用的before_sleep
参数可以在每次重试时使用新参数更新我们的抓取函数调用。
这是一个示例测试运行:
async def example_run(): urls = [ "https://httpbin.dev/ip", "https://httpbin.dev/ip", "https://httpbin.dev/ip", "https://httpbin.dev/status/403", ] to_scrape = [scrape(url=url, proxy=random.choice(PROXY_POOL), headers={"User-Agent": "foo"}) for url in urls] for result in asyncio.as_completed(to_scrape): response = await result print(response.json()) asyncio.run(example_run())
常问问题
为了总结这个 Python httpx 介绍,让我们看一下与使用 httpx 进行网络抓取相关的一些常见问题。
HTTPX 与请求
Requests 是最流行的 Python HTTP 客户端,以易于访问和使用着称。它也是 HTTPX 的灵感来源,HTTPX 是具有现代 Python 功能(如异步支持和 http2)的请求的自然继承者。
HTTPX 与 Aiohttp
Aiohttp 是最早支持 asyncio 的 HTTP 客户端之一,也是 HTTPX 的灵感来源之一。这两个包非常相似,虽然 aiohttp 更成熟,而 httpx 更新但功能更丰富。因此,当谈到 aiohttp 与 httpx 时,后者在网络抓取中更受欢迎,因为它支持 http2。
如何将 HTTP2 与 httpx 一起使用?
Httpx 支持 http2 版本,推荐用于网络抓取,因为它可以大大降低抓取器块率。默认情况下不启用 HTTP2,并且必须在对象http2=True
中使用该参数。httpx.Client(http2=True)
httpx.AsyncClient(http2=True)
如何自动跟随 httpx 中的重定向?
与请求等其他 Python 库不同,Httpx 默认不遵循重定向。allow_redirects=True
在 httpx 请求方法httpx.get(url, allow_redirects=True)
或 httpx 客户端对象中使用参数后启用自动重定向httpx.Client(allow_redirects=True)
概 括
HTTPX 是一个出色的新 HTTP 客户端库,它正在迅速成为 Python 网络抓取社区中的事实标准。它提供了 http2 和 asyncio 支持等功能,可降低阻塞风险并允许并发网络抓取。
与坚韧一起,httpx 使请求 Web 资源变得轻而易举,具有强大的重试逻辑,如代理和用户代理头旋转。