curl_cffi从入门到实战

文章目录

    • [一、为什么需要 curl_cffi](#一、为什么需要 curl_cffi)
    • [二、curl_cffi 是什么:原理与特性](#二、curl_cffi 是什么:原理与特性)
      • [2.1 一句话原理](#2.1 一句话原理)
      • [2.2 与主流 HTTP 库对比](#2.2 与主流 HTTP 库对比)
    • 三、安装与环境准备
      • [3.1 环境要求](#3.1 环境要求)
      • [3.2 安装](#3.2 安装)
    • 四、五分钟上手:第一个请求
      • [4.1 最简单的 GET](#4.1 最简单的 GET)
      • [4.2 带查询参数](#4.2 带查询参数)
      • [4.3 读取响应](#4.3 读取响应)
    • [五、核心参数速查与 POST 实战](#五、核心参数速查与 POST 实战)
      • [5.1 参数速查表](#5.1 参数速查表)
      • [5.2 POST 的三种形态](#5.2 POST 的三种形态)
    • 六、文件上传与下载
      • [6.1 文件上传:multipart + CurlMime](#6.1 文件上传:multipart + CurlMime)
      • [6.2 文件下载](#6.2 文件下载)
    • [七、会话与 Cookie 管理](#七、会话与 Cookie 管理)
      • [7.1 Session:自动维持 Cookie 与连接复用](#7.1 Session:自动维持 Cookie 与连接复用)
      • [7.2 Cookie 持久化到本地](#7.2 Cookie 持久化到本地)
      • [7.3 两个容易踩的坑](#7.3 两个容易踩的坑)
    • [八、指纹伪装实战:TLS 指纹检测对比](#八、指纹伪装实战:TLS 指纹检测对比)
      • [8.1 对比实验](#8.1 对比实验)
      • [8.2 impersonate 支持哪些目标?](#8.2 impersonate 支持哪些目标?)
      • [8.3 default_headers:指纹附带的浏览器请求头](#8.3 default_headers:指纹附带的浏览器请求头)
      • [8.4 进阶:自定义指纹](#8.4 进阶:自定义指纹)
    • 九、异步并发提速
      • [9.1 基础用法](#9.1 基础用法)
      • [9.2 并发 N 个请求](#9.2 并发 N 个请求)
      • [9.3 控制并发度(信号量限流)](#9.3 控制并发度(信号量限流))
    • 十、工程化最佳实践
      • [10.1 超时](#10.1 超时)
      • [10.2 原生重试](#10.2 原生重试)
      • [10.3 代理配置](#10.3 代理配置)
      • [10.4 异常处理](#10.4 异常处理)
      • [10.5 响应缓存(可选)](#10.5 响应缓存(可选))
    • 十一、完整实战项目:并发批量下载图片
    • 十二、避坑指南
    • 十三、参考链接

Python 爬虫反检测利器 curl_cffi 从入门到实战:TLS 指纹伪装、异步并发与工程化封装(基于 0.16.x)

本文所有代码均基于 curl_cffi 0.16.3(2026 年最新稳定版)实测通过,示例站点使用 httpbin.org ,可放心复制运行。


一、为什么需要 curl_cffi

写爬虫的朋友大概率遇到过这种情况:同样的代码,用浏览器打开一切正常,用 requests一请求就被封、被拦、返回验证码或 403。

你的第一反应可能是 "加个 User-Agent"、"加个 Cookie",然后发现没用。原因很简单:现代网站的反爬检测早已不是看 User-Agent 这么简单了,它们会通过 TLS 握手指纹(JA3/JA4)、HTTP/2 指纹、Header 顺序、扩展协议等几十个特征来判断 "你是不是一个真实浏览器"。

requestshttpx 这些纯 Python HTTP 库,用的都是 Python 自带的 OpenSSL 做 TLS 握手,特征跟真实浏览器完全不同,一眼假。

curl_cffi 就是为解决这个问题而生的。

二、curl_cffi 是什么:原理与特性

2.1 一句话原理

curl_cfficurl-impersonate(一个能 "模仿浏览器" 的 curl 分支)的 Python 绑定,通过 cffi 直接调用打包进 wheel 里的 libcurl-impersonate。它把真实浏览器的 TLS/JA3 指纹和 HTTP/2 指纹原样复刻出来,让服务器认为你就是一个 Chrome 或 Safari。

2.2 与主流 HTTP 库对比

能力 requests aiohttp httpx pycurl curl_cffi
HTTP/2
HTTP/3 ⚠️
同步 API
异步 API
WebSocket
原生重试
浏览器指纹伪装
速度 🐇 🐇🐇 🐇 🐇🐇 🐇🐇

(数据来源:curl_cffi 官方 README;HTTP/3 自 v0.11.4 起支持,v0.15.0 起支持 HTTP/3 指纹与 UDP 代理)

核心优势总结:

  1. 指纹伪装impersonate="chrome" 一行搞定 TLS + HTTP/2 双指纹;

  2. requests 风格 API:迁移成本几乎为零;

  3. :官方 benchmark 显示明显快于 requests/httpx;

  4. 同步异步通吃:一套 API 两种写法;

  5. 免编译:Windows / Linux /macOS 均有预编译 wheel,pip 直接装。

三、安装与环境准备

3.1 环境要求

  • Python 3.10+(v0.14 起最低要求 3.10)

  • Windows / Linux /macOS 均可

3.2 安装

复制代码
pip install curl_cffi --upgrade

国内网络建议使用镜像源加速:

复制代码
pip install curl_cffi --upgrade -i https://pypi.tuna.tsinghua.edu.cn/simple

验证安装:

复制代码
import curl_cffi

print(curl_cffi.__version__)   # 例如 0.16.3

版本说明 :本文基于 0.16.3 实测。curl_cffi 迭代很快,写法上注意两点 ------ 新版推荐直接 import curl_cffi 用顶层 API( curl_cffi.get()curl_cffi.Session() ),旧的 from curl_cffi import requests 写法仍兼容;老教程里常见的 files= 参数 在新版已被移除 ,上传文件请用 multipart= (见第六节)。

四、五分钟上手:第一个请求

4.1 最简单的 GET

复制代码
import curl_cffi

url = "https://httpbin.org/get"

r = curl_cffi.get(url, timeout=30)

print(r.status_code)   # 200

print(r.text)          # 响应文本

4.2 带查询参数

复制代码
import curl_cffi

# 普通参数

r = curl_cffi.get("https://httpbin.org/get", params={"foo": "bar"}, timeout=30)

print(r.url)   # https://httpbin.org/get?foo=bar

# 列表参数(同一个 key 多个值)

params = {"kw": ["python", "爬虫"]}

r = curl_cffi.get("https://httpbin.org/get", params=params, timeout=30)

print(r.url)   # https://httpbin.org/get?kw=python&kw=爬虫(自动 URL 编码)

4.3 读取响应

复制代码
import curl_cffi

r = curl_cffi.get("https://httpbin.org/get", timeout=30)

r.text              # str:自动解码后的文本(按响应头 charset / utf-8)

r.content           # bytes:二进制内容

r.json()            # dict:解析 JSON,失败抛异常

r.status_code       # 200

r.headers           # 大小写不敏感响应头

r.encoding          # 编码,可手动指定 r.encoding = "gbk"

r.raise_for_status()  # 状态码非 2xx 时抛 HTTPError

小知识:

curl_cffi

默认能解码

gzip / brotli / zstd

压缩的响应,

r.text

直接就是解压后的内容,不用手动处理。

五、核心参数速查与 POST 实战

5.1 参数速查表

get() / post() / Session.request() 通用的核心参数:

参数 类型 说明
params dict / list URL 查询参数
data dict / str / bytes 表单数据(自动加 application/x-www-form-urlencoded)或原始数据
content str /bytes/ 可迭代 原始请求体(新版推荐用它传裸数据)
json dict JSON 请求体(自动加 application/json
headers dict 自定义请求头(会覆盖指纹默认头)
cookies dict 手动指定 Cookie
auth tuple HTTP Basic 认证 (user, pass)
timeout float 超时秒数,如 30
allow_redirects bool 是否跟随重定向,默认 True
max_redirects int 最大重定向次数,默认 30-1 表示不限
proxies dict 代理 {"http": ..., "https": ...},支持 http/socks
proxy str 单代理,如 "http://user:pass@host:port"(与 proxies 二选一)
verify bool 是否校验 HTTPS 证书,默认 True
impersonate str / Fingerprint 浏览器指纹目标,核心参数
default_headers bool 是否自动加浏览器默认头,默认 True
multipart CurlMime 文件上传(见第六节)
stream bool 是否流式接收响应
http_version str 限流 HTTP 版本,默认 http2,可传 "v3" 用 HTTP/3
referer str 快捷设置 Referer 头

5.2 POST 的三种形态

python 复制代码
import curl_cffi

# ① 表单提交(application/x-www-form-urlencoded)

r = curl_cffi.post("https://httpbin.org/post", data={"name": "Luke", "age": 30}, timeout=30)

print(r.json()["form"])   # {'name': 'Luke', 'age': '30'}

# ② JSON 提交(application/json)

r = curl_cffi.post("https://httpbin.org/post", json={"name": "Luke", "age": 30}, timeout=30)

print(r.json()["json"])   # {'name': 'Luke', 'age': 30}

# ③ 原始字节体

r = curl_cffi.post("https://httpbin.org/post", content=b"raw payload", timeout=30)

print(r.json()["data"])   # raw payload

六、文件上传与下载

6.1 文件上传:multipart + CurlMime

⚠️重要 :新版 curl_cffi 不支持 requests风格的 files= 参数!请使用multipart= + CurlMime()

python 复制代码
import curl_cffi

mp = curl_cffi.CurlMime()

mp.addpart(

  name="attachment",        # 表单字段名

  content_type="image/png", # MIME 类型

  filename="avatar.png",    # 服务器端看到的文件名

  local_path="./avatar.png",# 本地文件路径(二选一:local_path 或 data)

  # data=open("avatar.png", "rb").read(),  # 也可以直接传内存字节

)

r = curl_cffi.post(

  "https://httpbin.org/post",

  data={"note": "头像上传"},   # 可同时带普通表单字段

  multipart=mp,

  timeout=30,

)

print(r.json()["files"])   # {'attachment': '...'}

6.2 文件下载

python 复制代码
import curl_cffi

r = curl_cffi.get("https://httpbin.org/image/png", timeout=30)

with open("demo.png", "wb") as f:

  f.write(r.content)

print(len(r.content), "bytes 已保存")   # 实测约 8090 bytes

大文件建议用 stream=True 边下边写,避免一次性占满内存:

python 复制代码
import curl_cffi

r = curl_cffi.get("https://httpbin.org/stream/100", stream=True, timeout=60)

with open("stream.txt", "wb") as f:

  for chunk in r.iter_content():

      f.write(chunk)

写爬虫能用 Session 就尽量用 Session:Cookie 自动存取、TCP 连接复用,性能和正确性双提升。

python 复制代码
import curl_cffi

# 推荐用上下文管理器

with curl_cffi.Session(impersonate="chrome") as s:

  # 服务器种下 Cookie

  s.get("https://httpbin.org/cookies/set/foo/bar", timeout=30)

  # Session 自动记住了它

  print(s.cookies)   # <Cookies[<Cookie foo=bar for httpbin.org /]

  # 下一次请求自动带上

  r = s.get("https://httpbin.org/cookies", timeout=30)

  print(r.json())    # {'cookies': {'foo': 'bar'}}

Session 还可以统一设置全局参数,比如所有请求共用的 headers、指纹、代理:

python 复制代码
with curl_cffi.Session(

  impersonate="chrome",

  headers={"X-Token": "my-token"},

  timeout=30,

  proxies={"https": "http://127.0.0.1:7890"},

) as s:

  r = s.get("https://httpbin.org/get")

Session 关闭后 Cookie 就没了。要跨程序会话保留登录态,官方推荐用 pickle 序列化 (不要用 get_dict() 导出,Cookie 不只是键值对,还带域、路径、有效期等属性):

python 复制代码
import pickle

import curl_cffi

def save_cookies(session: curl_cffi.Session, path="cookies.pk"):

  with open(path, "wb") as f:

      pickle.dump(session.cookies.jar._cookies, f)

def load_cookies(path="cookies.pk"):

  import os

  if not os.path.isfile(path):

      return None

  with open(path, "rb") as f:

      return pickle.load(f)

# 第一次:登录/访问后保存

with curl_cffi.Session() as s:

  s.get("https://httpbin.org/cookies/set/foo/bar", timeout=30)

  save_cookies(s)

# 第二次:加载回来直接用

with curl_cffi.Session() as s:

  if (jar := load_cookies()) is not None:

      s.cookies.jar._cookies.update(jar)

  r = s.get("https://httpbin.org/cookies", timeout=30)

  print(r.json())   # {'cookies': {'foo': 'bar'}}  ← Cookie 回来了

7.3 两个容易踩的坑

  1. response.cookies session.cookies不一样 :响应上的 cookies 只包含当前这次请求的;遇到重定向时可能会丢。永远优先用 session.cookies

  2. 想丢弃 Cookie?discard_cookies=True(Session 构造参数或请求参数均可),适合异步高并发场景下防止 Cookie 串号:

python 复制代码
s = curl_cffi.Session(discard_cookies=True)   # 这个 Session 不再累积服务器 Cookie

八、指纹伪装实战:TLS 指纹检测对比

这是 curl_cffi 的 "灵魂章节"。我们用公开的 TLS 指纹检测站 https://tls.browserleaks.com/json 来直观展示效果。

8.1 对比实验

python 复制代码
import curl_cffi

DETECT_URL = "https://tls.browserleaks.com/json"

# ① 不带指纹伪装

r1 = curl_cffi.get(DETECT_URL, timeout=30)

print("① 无伪装    ja3n_hash:", r1.json().get("ja3n_hash"))

# ② 伪装成 Chrome(跟随最新版)

r2 = curl_cffi.get(DETECT_URL, impersonate="chrome", timeout=30)

print("② chrome    ja3n_hash:", r2.json().get("ja3n_hash"))

# ③ 伪装成 Safari

r3 = curl_cffi.get(DETECT_URL, impersonate="safari", timeout=30)

print("③ safari    ja3n_hash:", r3.json().get("ja3n_hash"))

运行结果(实测):

方案 ja3n_hash(TLS 指纹哈希)
无伪装(requests 类) 4fd7cab6c51893a22b46123125be5bae
impersonate="chrome" 8e19337e7524d2573be54efb2b0784c9
impersonate="safari" 63eaa93caec132011d68ceb96955c1ee

三个哈希各不相同,说明每换一个伪装目标,你在服务器眼里就是一个不同的 "浏览器";而无伪装时则是典型的 Python HTTP 客户端指纹,很容易被识别拦截。

指纹对上了,服务器才 "相信" 你是浏览器。这就是 curl_cffi 能突破 TLS 指纹检测的根本原因。

8.2 impersonate 支持哪些目标?

curl_cffi 0.16.3 内置 44 个浏览器指纹预设,覆盖 Chrome、Edge、Safari、Firefox 及其 iOS/Android 变体。常用示例:

写法 含义
"chrome" 跟随库内置的最新 Chrome(当前为 Chrome 150)
"chrome124" / "chrome131" / "chrome150" 固定某个 Chrome 版本
"chrome99_android" / "chrome131_android" Chrome Android 版
"safari" 跟随最新 Safari(当前为 Safari 26.0.1)
"safari180_ios" / "safari172_ios" Safari iOS 版
"edge101" Edge
"firefox133" / "firefox147" Firefox

查看本机完整清单:

python 复制代码
curl-cffi list

或在 Python 中查看:

python 复制代码
from curl_cffi.requests import BrowserType

print([m.name for m in BrowserType])

8.3 default_headers:指纹附带的浏览器请求头

设置 impersonate 后,默认会自动带上对应浏览器的请求头 (UA、Sec-Ch-Ua、Sec-Fetch-* 等全套)。实测 impersonate="chrome" 发出的请求头:

python 复制代码
import curl_cffi

r = curl_cffi.get("https://httpbin.org/headers", impersonate="chrome", timeout=30)

h = r.json()["headers"]

print(h["User-Agent"])     # Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ... Chrome/150.0.0.0 ...

print(h["Sec-Ch-Ua"])      # "Not;A=Brand";v="8", "Chromium";v="150", "Google Chrome";v="150"

print(h["Sec-Fetch-Mode"]) # navigate

三种控制方式:

python 复制代码
# 1. 用 headers= 覆盖个别字段

r = curl_cffi.get(url, impersonate="chrome",

                headers={"User-Agent": "我的自定义UA"}, timeout=30)

# 2. default_headers=False 完全关闭默认头(指纹只影响 TLS 层)

r = curl_cffi.get(url, impersonate="chrome", default_headers=False, timeout=30)

# 3. 拿指纹对象改完再传(最灵活)

fingerprint = curl_cffi.get_fingerprint("chrome150")

fingerprint.headers["User-Agent"] = "我的自定义UA"

r = curl_cffi.get(url, impersonate=fingerprint, timeout=30)

8.4 进阶:自定义指纹

不想用内置预设?可以直接传自己的 JA3 / Akamai 字符串:

python 复制代码
r = curl_cffi.get(

  "https://tls.browserleaks.com/json",

  ja3="771,4865-4866-4867-...",   # 你的 JA3 字符串

  akamai="...",                    # Akamai 指纹(可选)

  extra_fp={},                     # 额外指纹选项(可选)

  timeout=30,

)

九、异步并发提速

同步方式一个个发请求太慢?curl_cffi 提供与同步 API 几乎一致的 AsyncSession

9.1 基础用法

python 复制代码
import asyncio

import curl_cffi

async def main():

  async with curl_cffi.AsyncSession(impersonate="chrome") as s:

      r = await s.get("https://httpbin.org/get", timeout=30)

      print(r.status_code)

asyncio.run(main())

9.2 并发 N 个请求

python 复制代码
import asyncio

import time

import curl_cffi

async def main():

  urls = [f"https://httpbin.org/delay/1" for _ in range(10)]  # 每个接口故意延迟 1 秒

  t0 = time.perf_counter()

  async with curl_cffi.AsyncSession(impersonate="chrome") as s:

      tasks = [s.get(url, timeout=60) for url in urls]   # 注意:不用 await,直接建任务

      results = await asyncio.gather(*tasks)

  cost = time.perf_counter() - t0

  print(f"10 个请求总耗时:{cost:.2f}s")

  print("状态码:", [r.status_code for r in results])

asyncio.run(main())

实测结果: 10 个各延迟 1 秒的请求,同步串行需要约 10 秒,异步并发仅约 3.4 秒完成(含连接建立开销,已接近并发理论下限)。任务数越多、单请求耗时越长,提速越明显。

提示:

task = s.get(url)

返回的是协程对象,

不要

在循环里

await

,否则又变回串行;收集所有任务后统一

asyncio.gather()

9.3 控制并发度(信号量限流)

一次性并发几百个请求容易被封或打爆自己,用 asyncio.Semaphore 限流:

python 复制代码
import asyncio

import curl_cffi

sem = asyncio.Semaphore(10)   # 最多同时 10 个请求

async def fetch(session, url):

  async with sem:

      r = await session.get(url, timeout=60)

      return r.status_code

async def main():

  urls = [f"https://httpbin.org/delay/0.5" for _ in range(50)]

  async with curl_cffi.AsyncSession(impersonate="chrome") as s:

      codes = await asyncio.gather(*[fetch(s, u) for u in urls])

  print("成功数:", codes.count(200), "/", len(codes))

asyncio.run(main())

十、工程化最佳实践

10.1 超时

永远设超时,否则一个卡死的请求能挂住整个程序:

python 复制代码
r = curl_cffi.get(url, timeout=30)          # 数字 = 总超时秒数

10.2 原生重试

curl_cffi 从新版本开始内置重试能力(这是 requests 没有的):

python 复制代码
from curl_cffi import Session

from curl_cffi.requests import RetryStrategy   # 注意:RetryStrategy 从 curl_cffi.requests 导入

# 简单计数重试

with Session(retry=3, timeout=30) as s:

  r = s.get("https://httpbin.org/get")

# 自定义策略:延迟 + 抖动 + 指数退避

strategy = RetryStrategy(count=3, delay=0.2, jitter=0.1, backoff="exponential")

with Session(retry=strategy, timeout=30) as s:

  r = s.get("https://httpbin.org/get")

参数说明:count 重试次数、delay 基础延迟秒数、jitter 随机抖动(防雪崩)、backoff 退避方式("linear" 线性 / "exponential" 指数)。

注意:内置 retry 针对的是

网络层失败

(连接失败、传输中断等)。实测对httpbin.org/status/500 这类 "请求成功但返回 5xx" 并不会重试。如果需要按状态码重试,请自行写循环或配合 raise_for_status()+ try/except 实现。

10.3 代理配置

python 复制代码
# 字典形式(HTTP 代理)

proxies = {"http": "http://127.0.0.1:7890", "https": "http://127.0.0.1:7890"}

r = curl_cffi.get(url, proxies=proxies, impersonate="chrome", timeout=30)

# 支持 SOCKS5

proxies = {"https": "socks5://127.0.0.1:7890"}

r = curl_cffi.get(url, proxies=proxies, timeout=30)

# 单个代理 + 代理认证

r = curl_cffi.get(url, proxy="http://user:pass@127.0.0.1:7890", timeout=30)

10.4 异常处理

python 复制代码
from curl_cffi.requests import RequestsError

try:

  r = curl_cffi.get("https://httpbin.org/get", timeout=10)

  r.raise_for_status()          # 4xx/5xx 抛 HTTPError

except RequestsError as e:

  print("网络层错误(超时/连接失败/解析失败等):", e)

RequestsError 是所有 curl_cffi 请求异常的基类,异常体系与 requests 类似(TimeoutConnectErrorHTTPError 等都在其下)。

10.5 响应缓存(可选)

测试阶段想避免重复打网络,Session 自带简单缓存:

python 复制代码
from datetime import timedelta

from curl_cffi import Session

with Session(cache=timedelta(minutes=5), timeout=30) as s:

  r1 = s.get("https://httpbin.org/get")

  r2 = s.get("https://httpbin.org/get")   # 5 分钟内命中缓存,不再发网络请求

十一、完整实战项目:并发批量下载图片

把前面所有知识点串成一个能直接跑、有产出的小项目:用 AsyncSession + 指纹伪装并发下载一批图片到本地,并输出统计报告。

python 复制代码
"""

curl_cffi 实战项目:并发批量下载图片

环境:curl_cffi 0.16.x / Python 3.10+

运行:python download_images.py

"""

import asyncio

import os

import time

from pathlib import Path

import curl_cffi

from curl_cffi.requests import RequestsError

# 生成一批可下载的图片 URL(httpbin 提供的测试图片)

def build_urls(count: int = 12) - list[str]:

  images = ["image/png", "image/jpeg", "image/webp", "image/svg"]

  # 不足时用 /bytes/N 随机字节端点补足(模拟不同大小的文件)

  urls = []

  for i in range(count):

      img = images[i % len(images)]

      urls.append(f"https://httpbin.org/{img}")

  return urls

async def download_one(session: curl_cffi.AsyncSession, url: str, save_dir: Path, sem: asyncio.Semaphore):

  """下载单个文件,返回 (文件名, 是否成功, 耗时, 大小)"""

  name = url.rsplit("/", 1)[-1] + f"_{abs(hash(url)) % 10000}.bin"

  path = save_dir / name

  t0 = time.perf_counter()

  try:

      async with sem:

          r = await session.get(url, timeout=60)

          r.raise_for_status()

          path.write_bytes(r.content)

      return (name, True, time.perf_counter() - t0, len(r.content))

  except (RequestsError, OSError) as e:

      return (name, False, time.perf_counter() - t0, str(e))

async def main():

  save_dir = Path("downloads")

  save_dir.mkdir(exist_ok=True)

  urls = build_urls(12)

  sem = asyncio.Semaphore(5)          # 限流:最多 5 个并发

  print(f"共 {len(urls)} 个任务,并发上限 {sem._value}")

  t0 = time.perf_counter()

  async with curl_cffi.AsyncSession(impersonate="chrome") as s:

      results = await asyncio.gather(*[download_one(s, u, save_dir, sem) for u in urls])

  total_cost = time.perf_counter() - t0

  # 统计

  ok = [r for r in results if r[1]]

  fail = [r for r in results if not r[1]]

  total_bytes = sum(r[3] for r in ok if isinstance(r[3], int))

  print("-" * 50)

  print(f"✅ 成功 {len(ok)} / {len(results)},失败 {len(fail)}")

  print(f"📦 共下载 {total_bytes / 1024:.1f} KB,总耗时 {total_cost:.2f}s")

  print(f"💾 文件保存在:{save_dir.resolve()}")

  for name, ok_flag, cost, size in fail:

      print(f"   ❌ {name}: {size}")

if __name__ == "__main__":

  asyncio.run(main())

运行效果(实测):

复制代码
共 12 个任务,并发上限 5

--------------------------------------------------

✅ 成功 12 / 12,失败 0

📦 共下载 185.2 KB,总耗时 2.94s

💾 文件保存在:...new-chatdownloads

项目用到了本文哪些知识点? 一眼就能对上:

章节 项目中的体现
指纹伪装 AsyncSession(impersonate="chrome")
异步并发 asyncio.gather + Semaphore 限流
文件下载 r.content 写文件
超时 / 异常 timeout=60 + RequestsError 捕获
状态码校验 r.raise_for_status()

十二、避坑指南

  1. 别用 files=上传文件 :新版已移除,会报错。用 multipart= + CurlMime()(见 6.1)。

  2. Python 版本:必须 3.10+,v0.14 起不再支持旧版本。

  3. RetryStrategy的导入路径from curl_cffi.requests import RetryStrategy(不在顶层导出)。

  4. 内置 retry 不重试 5xx:它是网络层重试,按状态码重试要自己写循环。

  5. 重定向后 Cookie 可能丢失 :优先用 session.cookies 而不是 response.cookies

  6. Cookie 持久化别用 get_dict():会丢域 / 路径属性,用 pickle 序列化 cookies.jar._cookies

  7. 异步循环里别 await单个任务 :先建任务列表,再统一 asyncio.gather(),否则等于串行。

  8. 并发别拉满 :高并发要限流(Semaphore)+ 轮换代理,否则 IP 更容易被风控。

  9. impersonate值要存在 :版本号写错(如 chrome999)会直接报错,用 curl-cffi listBrowserType 枚举查可用值。

  10. 关注版本迭代:curl_cffi 更新频繁(HTTP/3、WebSocket、指纹更新等都是近年加的),老文章语法可能已过时,以官方文档为准。

十三、参考链接


本文代码均在 curl_cffi 0.16.3 + Python 3.14 环境实测通过,示例仅用于技术学习,请遵守目标网站的 robots 协议与相关法律法规。

相关推荐
Sylvia33.1 小时前
火星数据体育API|一站式接入足球篮球电竞等18+项目实时数据
java·开发语言·python·websocket·游戏
程序员清风1 小时前
LangGraph 入门:用状态机设计可靠的 Agent 工作流
人工智能·python
Data_Journal1 小时前
使用 AutoScraper 进行网页抓取:分步教程
大数据·开发语言·数据库·python·scrapy
FanetheDivine1 小时前
学习python 1.搭建python环境
python
虎虎(_ _)。゜zzZ2 小时前
SQLAlchemy入门教程
数据库·后端·python·sql·ai·sqlalchemy
BUG研究员_3 小时前
LangGraph持久化之失败后恢复运行
python·agent
gb42152873 小时前
数字人面试和RAG区别?
python
宁渡AI大模型3 小时前
AI 全栈面试新趋势:Vibe Coding、前端、Java 后端高频面试题深度解析|河南宁渡科技有限公司编程教程
java·javascript·人工智能·python·ai大模型
Groundwork Explorer3 小时前
ESP32-C3 SuperMini 排查WIFI收发故障
python·单片机·嵌入式硬件·mcu