如何使用Python从无响应体的HTTP响应中下载Excel附件
核心问题
你当前代码拿不到有效Excel文件有两个明显错误:
- 给GET请求错误设置了
Content-Type: application/json头,这个头是用来声明请求体格式的,GET请求无请求体时不需要设置,错误设置会导致服务端按JSON接口逻辑返回而非文件流 - 读取响应后直接用
utf-8解码内容,Excel是二进制格式文件,不能按文本编码解析,直接解码会导致内容损坏,你当前拿到空响应也和服务端识别请求格式错误直接返回空有关
推荐实现方案(requests库)
原生http.client处理流、重定向、头解析的逻辑比较繁琐,优先用更稳定的requests库实现,先执行安装命令:pip install requests
完整代码如下:
import requests import os from urllib.parse import unquote # 替换为你的实际配置 HOSTNAME = "目标站点域名,例如 bi.example.com" EXPORT_API = "/dbapi/v4/sql_jobs/export/xlsx/1656366248381_479368648" SAVE_FOLDER = "./excel_downloads" # 自动创建本地保存目录 os.makedirs(SAVE_FOLDER, exist_ok=True) headers = { # 删除错误的content-type配置 "Authorization": f"Bearer {getToken()}", # 明确告知服务端需要接收Excel格式文件 "Accept": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" } # 开启流模式、自动跟随重定向 resp = requests.get( url=f"https://{HOSTNAME}{EXPORT_API}", headers=headers, stream=True, allow_redirects=True ) # 请求失败直接抛错,避免拿到异常响应写坏文件 resp.raise_for_status() # 从响应头自动解析文件名 file_name = "export.xlsx" # 默认兜底文件名 content_disp = resp.headers.get("Content-Disposition") if content_disp: if "filename*=" in content_disp: file_name = unquote(content_disp.split("filename*=")[1].split("'")[-1]) elif "filename=" in content_disp: file_name = unquote(content_disp.split("filename=")[1].strip('"')) # 二进制分段写入文件,避免大文件占满内存 save_path = os.path.join(SAVE_FOLDER, file_name) with open(save_path, "wb") as f: for chunk in resp.iter_content(chunk_size=8192): if chunk: f.write(chunk) print(f"文件下载完成,保存路径:{save_path}")
原生http.client版本(无第三方依赖)
如果不能安装第三方库,可以用修正后的原生模块实现:
import http.client import os from urllib.parse import unquote # 替换为实际配置 HOSTNAME = "目标站点域名" EXPORT_API = "/dbapi/v4/sql_jobs/export/xlsx/1656366248381_479368648" SAVE_FOLDER = "./excel_downloads" os.makedirs(SAVE_FOLDER, exist_ok=True) conn = http.client.HTTPSConnection(HOSTNAME) headers = { "Authorization": f"Bearer {getToken()}", "Accept": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" } conn.request("GET", EXPORT_API, headers=headers) res = conn.getresponse() # 校验状态码 if res.status != 200: conn.close() raise Exception(f"请求失败,状态码:{res.status},错误信息:{res.reason}") # 解析文件名 file_name = "export.xlsx" content_disp = res.getheader("Content-Disposition") if content_disp: if "filename*=" in content_disp: file_name = unquote(content_disp.split("filename*=")[1].split("'")[-1]) elif "filename=" in content_disp: file_name = unquote(content_disp.split("filename=")[1].strip('"')) # 直接读取二进制内容,不要做utf-8解码 file_data = res.read() save_path = os.path.join(SAVE_FOLDER, file_name) with open(save_path, "wb") as f: f.write(file_data) conn.close() print(f"文件下载完成,保存路径:{save_path}")
常见问题排查
- 如果改完代码还是拿到0KB空文件,先检查token是否有效、当前账号是否有该导出任务的下载权限
- 部分站点会校验请求来源,可以在headers里加上
Referer字段,值为导出按钮所在的页面地址 - 确认导出任务已经执行完成:异步导出类接口如果任务还在运行中,不会直接返回文件流,可能返回空或者任务状态提示
- 可以先打印响应头的
Content-Type字段,如果返回值是application/json说明请求还是没被服务端识别为下载请求,需要核对接口路径、请求参数是否正确
内容的提问来源于stack exchange,提问作者Michal Palko
相关产品推荐
相关产品推荐

