
简介面向Python开发者的requests库资源包适合希望从urllib2转向更简洁HTTP客户端库的学习者也适合需要深入理解HTTP请求封装原理的中级玩家。压缩包共121个文件大小约643KB其中76个py源码文件覆盖核心模块和调用示例20个rst文档源文件与5个html页面构成完整的官方风格说明文档再辅以配置文件、许可证、版本说明等目录结构清晰便于按需查阅。已有7010人学习下载是快速理解requests库工作机制的实用资料。读者既能通过源码学习请求构建、会话维持、超时重试与异常处理等关键实现也能参照文档源码和示例代码把典型请求模式直接复用到爬虫、接口测试或自动化脚本中缩短日常HTTP交互的开发排错时间。进一步阅读还能体会requests对urllib3的封装层次为后续二次开发或自研HTTP客户端提供借鉴。 下载文件这种需求用requests模块确实是绝大多数 python 开发者的第一选择。这玩意儿说简单也简单requests.get(url)然后open()写文件就完事了。但实际一上手尤其是碰到大文件、限流、连接中断这些情况坑一个接一个网上搜到的答案七零八落搜一圈回来发现还是在原地打转。这篇东西我打算把用requests做下载这件事彻底摊开讲一遍从安装环境到断点续传从对付 429 限流到排查各种玄学报错既有能直接抄的代码也有我这些年踩坑踩出来的经验希望对正准备拿 python 下载东西的朋友有点用。1. 动手之前先把 requests 环境搞利索1.1 安装 requests 的几个常见姿势很多朋友卡在第一步不是不会写代码而是环境根本没弄好。requests是第三方库python 自带的标准库urllib虽然也能发 HTTP 请求但用起来那叫一个别扭。所以我一般都直接推荐用requests前提是先把它装进当前环境。正常情况下在终端里执行下面这条命令就能装好pip install requests但如果你用的是国内网络直接装经常会慢到怀疑人生甚至直接超时报错。这时候就需要指定国内镜像源实测下来清华源和阿里的源都比较稳pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple还有一类情况是电脑上装了多个 python 版本或者使用 conda 管理环境。这种时候你直接用pip装很可能装到了别的环境里代码里import requests照样报ModuleNotFoundError。我建议你先在终端里确认一下当前解释器路径再装python -m pip install requests用python -m pip的方式可以保证装到当前激活的 python 环境中这一招在处理多版本环境时特别管用。如果你用的是 PyCharm那么直接在底部的 Terminal 面板执行上面的命令就行装完代码里就能正常 import 了。另外要提醒一句pycharm 配置 python 环境时选错解释器的坑很常见装完库之后建议立刻在代码里跑一下import requests; print(requests.__version__)验证环境是否真的通了。1.2 快速验证环境是否可用装完之后别急着写大段代码先做个最小化验证确认整个链路是通的。新建一个 py 文件输入下面这几行import requests response requests.get(https://www.baidu.com, timeout10) print(response.status_code) print(response.text[:100])如果终端里输出了200以及百度首页 HTML 片段说明 requests 已经能正常工作。这里加timeout10是我一直以来的习惯不加这个参数万一请求卡住你的程序会一直死等下去那种体验基本上等同于电脑死机。后面所有请求我都会强调这个参数真的能省掉无数烦恼。2. 小文件下载最简单的实现方式2.1 基础写法与参数说明如果有人只是下载个小文件比如几十 KB 的图片、JSON 配置、小 PDF其实两三行代码就能搞定import requests url https://example.com/sample.jpg response requests.get(url, timeout10) response.raise_for_status() with open(sample.jpg, wb) as f: f.write(response.content)这里我专门加了response.raise_for_status()这一行非常重要。它可以确保响应状态码是 4xx 或 5xx 时直接抛异常而不是让你拿到一个错误页面还在那傻乎乎地写进文件。网上很多教程代码不写这一行结果服务器返回 404 页面文件也保存了只是打开看是个 HTML 错误页排查半天不知道问题出在哪。另外再强调一次文件名、路径、URL 编码、超时设置这些细节各个都是坑。URL 里如果带有中文参数一定先做 URL 编码from urllib.parse import quote url https://example.com/download?file quote(中文文件名.pdf)2.2 下载时必须要考虑的三件事第一件事是超时设置。很多人的代码卡住不动十有八九是漏了timeout。建议同时设置连接超时和读取超时response requests.get(url, timeout(5, 15))这里的第一个数字 5 表示连接阶段的超时第二个数字 15 表示读取阶段的超时。分阶段设置的好处是如果服务器根本连不上5 秒就快速失败如果连接建立了但数据一直不返回15 秒再判死刑不会一卡到底。第二件事是 User-Agent 伪装。不少网站会对没有浏览器标识的请求直接拒绝或返回 403headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } response requests.get(url, headersheaders, timeout10)第三件事是响应的编码处理。如果下载的是文本内容最好显式指定编码response.encoding utf-8不指定编码的话requests 会从响应头推测碰到不规则网站八九不离十会推测错然后中文全部变成乱码。写文件的时候统一用wb模式写二进制是最稳妥的做法。3. 大文件下载的正确姿势流式下载与断点续传3.1 为什么不能直接使用 response.content我第一次下载一个大文件时直接用了上一节的写法结果机器直接卡成了幻灯片。原因是response.content会把整个响应体全部读进内存一个 2GB 的文件就会尝试占用 2GB 内存来存它这在小文件时毫无感知放到大文件上就是灾难。正确做法是开启流式下载。用streamTrue让 requests 保持连接然后通过迭代器分块读取内容边读边写内存占用只有几十 KBimport requests url https://example.com/bigfile.zip response requests.get(url, streamTrue, timeout(5, 30)) response.raise_for_status() with open(bigfile.zip, wb) as f: for chunk in response.iter_content(chunk_size8192): if chunk: f.write(chunk)这里的chunk_size8192表示每次读取 8KB这个值可以根据你的网络情况调整。实践下来8192 到 65536 之间都是比较合理的选择。太小了 IO 频繁下载速度受影响太大了内存占用又会上去得不偿失。3.2 用进度条提升下载体验没有进度条的下载就像看着一锅水慢慢烧开心里完全没底。这里我推荐直接用tqdm库配合流式下载做一个实时进度条pip install tqdmimport requests from tqdm import tqdm url https://example.com/bigfile.zip response requests.get(url, streamTrue, timeout(5, 30)) response.raise_for_status() total_size int(response.headers.get(Content-Length, 0)) chunk_size 8192 with open(bigfile.zip, wb) as f, tqdm( totaltotal_size, unitB, unit_scaleTrue, desc下载进度 ) as bar: for chunk in response.iter_content(chunk_sizechunk_size): if chunk: f.write(chunk) bar.update(len(chunk))Content-Length如果服务器不返回total_size就是 0进度条会显示为未知总量模式但不影响下载功能。这种模式下的进度条虽然无法显示百分比至少能让你知道程序还在跑心里踏实很多。3.3 断点续传的实现思路辛辛苦苦下载到一半网络断了一下文件直接废了这种痛苦经历相信大家都体会过。requests本身支持通过Range头实现断点续传。原理很简单告诉服务器你要从哪个字节开始读。import os url https://example.com/bigfile.zip local_file bigfile.zip headers {} if os.path.exists(local_file): headers[Range] fbytes{os.path.getsize(local_file)}- response requests.get(url, headersheaders, streamTrue, timeout(5, 30)) if response.status_code 206: # 206 表示部分内容服务器支持断点续传 mode ab elif response.status_code 200: # 200 表示服务器不支持从零开始 mode wb else: response.raise_for_status() return with open(local_file, mode) as f: for chunk in response.iter_content(chunk_size8192): if chunk: f.write(chunk)这里有个隐藏得很深的坑如果服务器不支持 Range 请求它不会报错而是直接返回 200 和完整数据。如果你继续用ab模式追加文件就会整个重复一遍越补越坏。所以必须先判断状态码206才能追加重写200就老老实实重新下载。3.4 大文件的附加校验断点续传逻辑写完文件看似下全了但万一下载过程中数据损坏了怎么办最好的兜底方案是校验文件哈希。下载完成后算一下文件的 SHA256跟服务器提供的值比对一次import hashlib def calc_sha256(file_path, chunk_size8192): h hashlib.sha256() with open(file_path, rb) as f: while True: chunk f.read(chunk_size) if not chunk: break h.update(chunk) return h.hexdigest()如果服务器页面提供了 SHA256 值下载完顺手验一下能过滤掉绝大多数由于网络抖动导致的静默损坏。虽然多一步操作但对大文件来说值得。4. 遇到 429 限流别慌这是有套路的4.1 429 是怎么产生的很多人在写爬虫或者频繁调用接口时都会碰到429 Too Many Requests的状态码。服务器不是不让你下而是你在单位时间内请求太密集触发了它的限流机制。这个机制我理解为像一个十字路口的红绿灯——单个请求没问题但你在红灯时硬闯那就要被拦下来。报错信息里经常有一句exceeded retry limit, last status: 429 too many requests意思就是重试次数也超限了。这时候最直接有效的方法是等一段时间再重试。但这个“等”是有讲究的。如果所有请求都固定等同样长的时间所有请求会同时恢复又同时触发限流形成恶性循环。所以要用“退避算法”。4.2 用退避算法处理 429所谓退避就是每次重试之前等待的时间递增。python 里实现指数退避非常简单import time import random def exponential_backoff(attempt, base1, max_wait60): sleep_time min(base * (2 ** attempt), max_wait) # 加上随机抖动避免多个请求同一时间恢复 sleep_time random.uniform(0, 0.5) return sleep_time配合 requests 的重试逻辑可以在请求外层包一层循环import requests import time url https://example.com/api for attempt in range(6): response requests.get(url, headersheaders, timeout10) if response.status_code 429: retry_after int(response.headers.get(Retry-After, 1)) wait_time max(exponential_backoff(attempt), retry_after) print(f触发限流等待 {wait_time:.2f} 秒后重试) time.sleep(wait_time) continue response.raise_for_status() break注意看这里有个非常细节的处理我优先读取响应头里的Retry-After字段。很多限流实现都会返回这个字段通知你具体要等多少秒。如果服务器明确告诉你了就尊重它的要求否则等待时间取指数退避的结果两者取较大值。这样的处理策略比单纯固定等待或者暴力重试要优雅得多也不会给服务器持续制造压力。4.3 伪装成正常浏览器的几个细节很多时候 429 不一定是请求频率太高还有可能是你的请求特征太明显地像爬虫。几个关键字段建议手动配置一下headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Accept: */*, Accept-Language: zh-CN,zh;q0.9, Referer: https://example.com/, Connection: keep-alive, }这套请求头信息量大Accept、Accept-Language、Referer这些字段组合起来能让服务器认为请求来自一个正常的浏览器环境。很多人只设置了User-Agent其它字段缺一堆服务器一看就知道是脚本该限流还是限流。另外有一个极其常见但又容易被忽略的点频繁创建新连接对服务器来说是一种负担也会更容易触发限流策略。用requests.Session()维护连接复用是更友好的做法session requests.Session() session.headers.update(headers) for i in range(10): response session.get(url, timeout10) # 处理响应...Session 对象会复用 TCP 连接降低服务端握手压力对双方都好。爬取多页面或者调用批量接口时坚持用 Session 也是一个好习惯。4.4 异步请求触发限流的说明有些热词提到dma continuous requests、too many concurrent requests。这是异步或并发场景下常见的限流问题。当你用 asyncio 或者多线程同时发出大量请求时瞬间并发量过高触发网关限流就可能看到stream disconnected before completion: too many pending requests之类的报错。这类并发限流的应对核心不在于增加重试次数而在于控制并发度。我建议在代码里加个信号量把同时进行的请求数量限制在合理范围比如 5 个import asyncio import aiohttp semaphore asyncio.Semaphore(5) async def fetch(session, url): async with semaphore: async with session.get(url) as response: return await response.text()控制并发度时观察服务端日志也是个不错的习惯。如果服务器返回 429就降低并发如果偶尔出现超时可能并发偏高可以逐步下调。这种抑制节奏比盲目堆并发要科学得多。5. 那些高频踩坑场景我帮你整理成了速查表5.1 常见报错与逐条排查思路我见过太多人在下载时碰见几千个字的长报错然后慌了神。其实大部分报错的根源就那么几个先把最常见的列出来报错信息或现象常见原因处理方式ModuleNotFoundError: No module named requests依赖未安装或装错环境使用python -m pip install requests装到当前环境requests.exceptions.ConnectTimeout网络不通或对方服务器响应慢检查网络调大连接超时时间requests.exceptions.ReadTimeout数据读取超时网络不稳定换成流式下载调大读取超时或设置重试SSLError/InsecureRequestWarning证书验证失败优先处理证书链临时方案可加verifyFalseHTTPError: 404 Client Error链接失效或 URL 不正确确认 URL配合raise_for_status()快速定位ChunkedEncodingError网络中断响应不完整用重试机制读取时加异常捕获或断点续传429 Too Many Requests请求频率过高触发限流退避算法 请求头伪装 降低并发保存的文件无法打开写入了错误页面或数据损坏加状态码校验和哈希校验5.2 一个容易忽略的坑verifyFalse 与服务端鉴权有些网站 SSL 证书配置有问题或者用了自签名证书直接请求会报SSLError。很多人第一反应是加上verifyFalse这个参数确实能绕过证书验证response requests.get(url, verifyFalse, timeout10)但这个参数一加同时也会吞掉InsecureRequestWarning的情况还会让请求失去安全性保障。如果只是本地测试或访问可信的有限场景可以接受但请不要在敏感数据的请求中这样用。更好的办法是找到网站的 CA 证书通过verify/path/to/ca.pem自行指定这样既安全又能验证。还有就是那些带有鉴权机制的接口下载时除了 requests 之外还会涉及token或session的维护。通常做法是在请求头里带上headers[Authorization] Bearer YOUR_ACCESS_TOKEN有些服务对未鉴权的请求会提示warning: you are sending unauthenticated requests to the hf hub. please set ...随后给出更低的限流阈值。这种场景下正确携带鉴权信息反而能有效规避 429 限流。5.3 下载大文件时进程被意外的请求中断还有一类问题发生在下载过程中程序被意外中断文件留下一半。遇到这种情况程序重启后如果没有断点续传逻辑就会重新从零开始。这里的解决方案就是我上面 3.3 节提到的加断点续传。看似多写了十几行代码但对于动不动几个 GB 的大文件来说这十几行代码能让你少崩溃好几次。有时候下载进度条卡住了不一定是被墙或卡死也可能只是服务器响应慢、单次 chunk 还没返回。此时不要急着杀掉进程设置合理的读取超时然后等一等往往就有结果。真正卡死的场景通常伴随着长时间没有任何网络活动。6. 最后分享一点个人经验实际用requests下载文件这些年我最大的体会是下载这个动作本身不复杂复杂的是把各种边界情况都处理妥当。超时怎么设、断点怎么续、429 怎么退避、进度怎么展示、哈希怎么校验这些都是藏在代码之外的细节。把这些细节都补齐了你的下载脚本才能从“能用”进化到“可靠”。还有一个小技巧顺手送给你们写下载脚本时建议把 URL、保存路径、请求头、重试次数都提取成配置项或常量不要散落在一堆代码里。这样换一个下载任务时只需要改配置不用重新梳理逻辑。我在自己的项目里都会维护一个简单的下载模板长期改改补补现在基本是拿来即用。希望这篇内容能让你少走一些弯路有更好的方案也欢迎交流。本文还有配套的精品资源点击获取