Metadata-Version: 2.4
Name: never_fox
Version: 0.3.15
Summary: Browser-level (Firefox 152) HTTP client — real NSS TLS, HTTP/2, byte-identical fingerprint
Author: neverl805
License: MIT
Project-URL: Homepage, https://github.com/neverl805/never_fox
Keywords: tls,fingerprint,firefox,ja3,ja4,http2,nss,anti-bot
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Topic :: Internet :: WWW/HTTP
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: hpack>=4.0
Requires-Dist: brotli
Requires-Dist: zstandard
Requires-Dist: publicsuffixlist>=1.0.2.20260716

# never_fox

**发出去的请求在字节层面就是 Firefox 152** —— 不是模拟,是真的。

`never_fox` 是一个 requests 风格的 Python HTTP 客户端,底层链接 **Firefox 真正的 TLS 引擎 NSS**,所以它的 TLS ClientHello 与真 Firefox 152 **逐字节相同(含 ECH、X25519MLKEM768 后量子组)**,HTTP/2 帧与 header 也与真火狐一致。专为**高并发爬虫 / 风控对抗**设计。

> 一句话原理:Chrome 有 Cronet、Firefox 的网络栈是 NSS(TLS)+ Necko。NSS 开源可独立链接 —— 我们直接链接真 NSS,用逆向出来的 Firefox 152 配置驱动它。这是"模拟"做不到的:curl_cffi 这类能对齐 JA3/JA4,但 ECH 等细节对不上,因为它们用的不是 NSS。

## 为什么它是"真的"

在本地受控 HTTPS 服务器上,让**真 Firefox 152** 和 never_fox 都访问,逐字段对比握手数据,并在 **4 个指纹站(peet.ws / browserleaks / scrapfly / howsmyssl,算法各不同)** 交叉验证:

| 维度 | 与真 Firefox 152 |
|---|---|
| TLS ClientHello 结构字节(屏蔽随机数/临时密钥/扩展排列) | ✅ 一致 |
| JA3 / JA3N / JA4 / JA4_o / JA4_r | ✅ 全部一致 |
| HTTP/2 Akamai 指纹(SETTINGS/WINDOW_UPDATE/优先级/伪头序) | ✅ 一致 |
| 所有 HTTP header(名 / 顺序 / 值) | ✅ 一致 |
| ECH、record_size_limit、MLKEM、delegated_credentials | ✅ 一致 |

## 特性

| | |
|---|---|
| **真指纹** | 真 NSS 引擎,ClientHello 字节级 == Firefox 152(含 ECH) |
| **requests 兼容** | `get/post/put/patch/delete/head/options`;默认 `raw_headers=True`,传入头的值优先并按 Firefox 请求形状纠序,缺失项才兜底(`raw_headers=False` 可启用 Session 合并);`Response.headers` 大小写不敏感、`.elapsed`(timedelta)、`.request`/`.links`/`.iter_lines`/`.is_redirect`/`bool(resp)`;异常层级 `RequestException`/`HTTPError`/`ConnectionError`/`Timeout`/`TooManyRedirects`;`auth=(u,p)`、`files=`(multipart)、`hooks=`、按请求 `verify` |
| **异步** | `AsyncSession`,future 驱动,**线程数≈连接数而非请求数** |
| **高并发** | 冷启动单飞建连(一个 origin 收敛到 1 条 h2,像浏览器)+ 多路复用 + 引用计数防崩 |
| **Cookie** | **RFC 6265/6265bis** cookie jar:`Expires`+`Max-Age`、Public Suffix、Secure 来源、SameSite、host-only、default-path、`__Host-`/`__Secure-`;H2 按 Firefox 拆 cookie 字段,H1 自动合并;支持完整记录 `get_records/set_record` |
| **稳健** | 仅幂等方法自动重试/退避、取消/超时/超限发 RST_STREAM、H2 上传双层流控与 CONTINUATION、H1 严格分帧、原始/解压响应上限、跨 origin 重定向剥离凭据与实体头 |
| **代理** | HTTP CONNECT + **SOCKS5**(可认证);目标看到真 FF152,代理只见加密隧道 |
| **限流** | 每 host 限速(带抖动)+ 429/503 指数退避(尊重 `Retry-After`) |
| **HTTP/3** | 默认 `h3="auto"`:通过 DNS HTTPS/SVCB 或 Alt-Svc 自动发现 H3,复用常驻 neqo QUIC 会话;失败快速回退 H2 |

## 环境要求与构建

native 引擎是编译产物,按平台分发(像 Cronet)。**重依赖 NSS/NSPR/brotli/zstd 不用我们编译** —— 由各平台包管理器提供预编译版,我们只编 ~250 行的引擎(几秒)。

```bash
# 1) 系统依赖(预编译的 NSS 等)
brew install nss nspr brotli zstd                                  # macOS
sudo apt-get install libnss3-dev libnspr4-dev libbrotli-dev libzstd-dev zlib1g-dev patchelf  # Linux
# Windows: MSYS2 装 mingw-w64-x86_64-{nss,nspr,brotli,zstd,zlib,gcc,pkg-config}

# 2) Python 依赖
pip install hpack brotli zstandard publicsuffixlist

# 3) 跨平台编译(自动找 NSS,产出 libfxtls.{dylib,so,dll})
python native/build.py

# 4) 自检指纹 == Firefox 152(NSS 版本漂移会在这里报错)
python native/verify.py

# 5)(可选)打包自包含,拷到同 OS+架构机器免依赖运行
python native/bundle.py                                            # -> native/vendor/
```

然后把仓库目录加入 `PYTHONPATH`,`import never_fox` 即可。

### 多平台预编译(GitHub CI)

`.github/workflows/build.yml` 用矩阵在 **Linux x86_64 / Linux arm64 / macOS arm64 / Windows x86_64** 原生 runner 上自动:装预编译 NSS → `build.py` → `verify.py` 指纹门禁 → 上传各平台产物;打 `vX.Y.Z` tag 会把四平台产物附到 GitHub Release。NSS 由包管理器缓存,只在升版本时重拉。

## 快速开始

```python
import never_fox as nf

r = nf.get("https://example.com/", params={"q": "x"})
print(r.status_code, r.ok, r.http_version, r.text[:200])
r.raise_for_status()

r = nf.post("https://httpbin.org/post", json={"hello": "firefox152"})   # 或 data={...} 表单
print(r.json())
```

### Session(Cookie / 重定向 / 连接池)

```python
s = nf.Session()                      # verify=True, h3="auto"
s.get("https://site/login")           # Set-Cookie 自动保存
s.post("https://site/api", data={"a": 1})   # 自动带 Cookie、自动重定向(r.history)
print(s.cookies.as_dict())
s.put(...); s.delete(...); s.patch(...); s.head(...); s.options(...)
s.close()
```

`Session` 默认启用自动资源兜底：响应体上限 32 MiB、响应头上限 256 KiB、最多缓存 64 个 H2 host、每 host 最多 6 条连接、最多 8 个 H3 worker、连接空闲 60 秒回收、DNS/Alt-Svc 各最多 4096 项、Cookie 最多 3000 个且单域名最多 180 个（单条 4 KiB）。后台 reaper 每 5 秒清扫一次，无需业务代码定期清理；即使 Session 被遗忘也会由析构兜底关闭。显式传 `max_response_bytes=None` 可恢复无限响应，但不建议爬虫使用。

### 异步(高并发)

```python
import asyncio, never_fox as nf

async def main():
    async with nf.AsyncSession() as s:
        # 上千并发共享连接池中的多路复用连接;响应通过 future 等待,
        # 不为每个请求占一个线程(线程数≈连接数,不随请求数增长)。
        rs = await asyncio.gather(*[s.get(f"https://site/p/{i}") for i in range(1000)])
        print(sum(r.ok for r in rs), "ok")

asyncio.run(main())
```

### 爬虫调优(代理 / 限速 / 退避)

```python
s = nf.Session(
    max_connections_per_host=16,    # 每 host 最多连接数
    max_connecting=8,               # 每 Session 同时冷建连上限(跨 host)
    max_pool_hosts=64,              # 全局最多保留的 H2 host
    max_h3_workers=8,               # 常驻 H3/QUIC worker 上限
    connection_idle_timeout=60,     # H2/H3 空闲回收秒数
    max_response_bytes=32 << 20,    # 原始及解压后响应上限
    rate_limit=5,                   # 每 host <= 5 请求/秒(0=不限)
    backoff_retries=3,              # 429/503 指数退避重试,尊重 Retry-After
    retries=3,                      # 连接级重试(短指数抖动;已发送请求仅安全方法重放)
    verify=True,                    # 用 Firefox 同款 Mozilla 根证书校验
)

# 代理:HTTP CONNECT 或 SOCKS5(可认证)。同一个代理 session id 应复用同一个
# Session/H2 隧道；不要在请求前连续覆盖 proxy 变量。
s = nf.Session(proxy="http://user:pass@proxy:8080", retries=3,
               connection_idle_timeout=60)
r = nf.get(url, proxy="socks5://user:pass@10.0.0.1:1080")     # 或 proxies={"https": "..."}
```

`Response`:`.status_code .ok .reason .url .text .content .json() .headers .cookies .history .elapsed .encoding .raise_for_status() .iter_content()`

## 工作原理

```
never_fox/            Python 层
  client.py           Session / Response / 连接池 / Cookie / 重定向 / 限速 / 代理解析
  aio.py              AsyncSession(future 驱动的异步)
  h2conn.py           HTTP/2 多路复用(复刻 Firefox 的 SETTINGS/优先级/伪头序)
  http1.py            HTTP/1.1 回退
  h3.py               HTTP/3(常驻 neqo 会话与多路复用)
  httpssvc.py         DNS HTTPS/SVCB 发现与 TTL 缓存
  cookies.py          CookieJar
  _native.py          ctypes 绑定到原生引擎
native/               原生引擎(C,链接真 NSS)
  fxtls_config.h      Firefox 152 的 ClientHello 配置(密码套件/组/签名算法/ECH/证书压缩…)
  fxtls_lib.c         连接 + TLS 握手 + 收发 + 代理(CONNECT/SOCKS5)-> libfxtls.dylib
  bundle.py           把依赖 dylib 收进 vendor/ 并改 @loader_path,便于跨机
harness/              指纹验证脚本(本地抓包对比 + 多站交叉验证)
```

证书用 NSS 内置的 **Mozilla 根证书列表(libnssckbi)** 校验 —— 和 Firefox 同款信任库。

## 已知限制

- 原生库是平台相关二进制:跨 OS / 架构需在目标机重新 `build.sh`(像 Cronet 按平台分发)。Firefox/Linux 是 OS 自洽目标,适合做服务端。
- TLS 会话:连接池复用已建立的 H2 连接;新建 TCP 连接目前固定使用 Firefox 152 首次连接的完整握手形状,不会发送恢复握手的 `pre_shared_key`。
- HTTP/3:需运行环境允许目标 UDP 端口出网。默认同时支持 RFC 9460 HTTPS/SVCB（AliasMode、优先级、`mandatory`、`alpn`、`port`、地址 hint、TTL/负缓存）和 Alt-Svc（`ma`/`clear`、备用 host/port）；每个 `Session` 复用常驻 QUIC 连接并并发承载多个 stream。HTTPS RR 有 50ms 优先窗口，Alt-Svc H3 建连 100ms 后准备 TCP/H2 后备，失败端点会临时熔断。为避免重复副作用，非安全方法只有在取消确认表明尚未创建 H3 请求流时才自动回退。Windows wheel 当前不含 neqo worker,会自动使用 H2。可用 `NEVER_FOX_DNS_SERVERS=1.1.1.1,8.8.8.8` 覆盖系统 DNS 服务器。
- requests 差异:`stream=True` 收下但 body 始终全量缓冲;`cert=`(客户端证书)不支持(NSS 用 Mozilla 根),会抛 `NotImplementedError`;`verify='/ca.pem'` 自定义 CA 包不认(走 NSS 根)。

## 验证复现

```bash
# 本地起 HTTPS/h2 服务,真 Firefox + never_fox 都访问,逐字段对比
python harness/localcap/diff_h2cap.py        # 看 harness/localcap/FULL_DIFF.md
# 构建本地 H3 对照服务,实抓 Firefox 152 与公共 Session 的
# QPACK、QUIC transport parameters、ClientHello 并和 golden 比较
H3_SERVER_OUT=$PWD/native/h3/neqo-server python native/h3/build_h3.py
python harness/capture_h3.py
# 不启动浏览器,仅验证 never_fox 当前 H3 输出与已提交 golden
python harness/capture_h3.py --skip-firefox
# 多站交叉验证报告
cat harness/localcap/MULTISITE.md
```

H3 实抓需要 `openssl`、NSS 的 `certutil/pk12util`，以及 macOS 上的 Firefox 152。
参考数据位于 `harness/golden/firefox152_h3.json`；只有确认浏览器版本和线级变化后才应使用
`python harness/capture_h3.py --update-golden` 更新。

详细逆向与对比过程见 [REPORT.md](REPORT.md)。

## 免责声明

仅用于授权范围内的安全研究、风控对抗测试与合规数据采集。请遵守目标站点的条款与当地法律。
