curl上传GitHub Release大文件可正常获取响应,Python requests上传成功后却超时/挂起的原因
我遇到过类似的问题,结合你提供的curl和requests的对比信息,主要原因和解决思路可以分成这几点:
核心原因分析
1. HTTP协议版本的差异处理
看你的curl日志,它自动协商使用了HTTP/2(日志里有ALPN: server accepted h2),而标准的requests库基于urllib3,默认是用HTTP/1.1的。GitHub的uploads服务器在处理大文件(100MB+)时,HTTP/1.1下的响应返回逻辑可能存在延迟——比如服务器需要额外时间完成文件校验、同步到CDN,这时候HTTP/1.1的连接复用机制可能导致requests卡在等待响应的状态;而HTTP/2的多路复用和流处理机制更高效,能顺利拿到服务器返回的201响应。
2. 超时设置的问题
你在requests里设置的timeout=2或者timeout=20是总超时时间,包含了上传时间和等待响应的时间。大文件上传本身就需要不少时间,留给服务器返回响应的时间就被压缩了,很容易触发读取超时。而curl默认没有设置超时,会一直等待直到收到响应,所以不会出现这个问题。
3. 请求头的细微差异
你的curl请求里带了X-GitHub-Api-Version: 2022-11-28,并且Accept头是application/vnd.github+json,但requests里用的是application/vnd.github.v3+json(虽然这俩语义相近),缺少API版本头。这种细微差异可能导致服务器处理响应的逻辑不同,间接引发超时。
对应的解决方案
方案1:让requests支持HTTP/2
给requests添加HTTP/2支持,和curl保持一致的协议版本:
首先安装依赖:
pip install requests[http2]
然后修改代码,启用HTTP/2:
import os import requests from urllib3.contrib.pyopenssl import inject_into_urllib3 # 注入HTTP/2支持到urllib3 inject_into_urllib3() release_id = "93670877" file = "spa_eng.apkg" url = f"https://uploads.github.com/repos/Vuizur/tatoeba-to-anki/releases/{release_id}/assets?name={file}" headers = { "Accept": "application/vnd.github+json", "Content-Type": "application/octet-stream", "Authorization": f"Bearer {os.getenv('GITHUB_TOKEN')}", "X-GitHub-Api-Version": "2022-11-28" # 和curl保持一致 } with open(file, "rb") as f: # 分开设置连接超时和读取超时,给服务器足够的响应时间 response = requests.post(url, headers=headers, data=f, timeout=(10, 60)) print(response.status_code) print(response.json())
方案2:改用原生支持HTTP/2的httpx库
如果觉得requests加HTTP/2的配置麻烦,可以直接用httpx,它原生支持HTTP/2,用法和requests很像:
pip install httpx
代码示例:
import os import httpx release_id = "93670877" file = "spa_eng.apkg" url = f"https://uploads.github.com/repos/Vuizur/tatoeba-to-anki/releases/{release_id}/assets?name={file}" headers = { "Accept": "application/vnd.github+json", "Content-Type": "application/octet-stream", "Authorization": f"Bearer {os.getenv('GITHUB_TOKEN')}", "X-GitHub-Api-Version": "2022-11-28" } with open(file, "rb") as f: with httpx.Client(http2=True) as client: # 同样分开设置超时,读取超时设长一点 response = client.post(url, headers=headers, content=f, timeout=(10, 60)) print(response.status_code) print(response.json())
方案3:优化requests的超时设置(不换协议)
如果不想改协议,就把超时设置成分开的连接超时和读取超时,给服务器足够的时间返回响应:
import os import requests release_id = "93670877" file = "spa_eng.apkg" url = f"https://uploads.github.com/repos/Vuizur/tatoeba-to-anki/releases/{release_id}/assets?name={file}" headers = { "Accept": "application/vnd.github+json", "Content-Type": "application/octet-stream", "Authorization": f"Bearer {os.getenv('GITHUB_TOKEN')}", "X-GitHub-Api-Version": "2022-11-28" } with open(file, "rb") as f: # (连接超时10秒,读取超时120秒),根据你的文件大小调整读取超时 response = requests.post(url, headers=headers, data=f, timeout=(10, 120)) print(response.status_code) print(response.json())
备注:内容来源于stack exchange,提问作者Pux




