资讯动态

Python入门首选requests库:从HTTP协议到爬虫与429限流排查

发布时间:2026/10/13 8:25:16 来源:尧图企业网站定制
1. 为什么Python入门第一个该学透的库是requests1.1 requests在Python生态里的真实地位很多人觉得requests就是爬虫专用库这个理解不算错但严重低估了它的价值。Python入门阶段你可能要处理接口调试、自动化脚本、数据采集、量化策略、运维监控甚至给同事写个批量工具——这些场景里requests几乎是绕不开的底层依赖。它不是“某个领域的库”而是Python网络世界的通用方言。我见过不少初学者绕开requests先去啃“更高级”的异步框架比如aiohttp结果写出来的代码既难调试又容易踩并发坑。原因很简单异步框架对网络协议、事件循环、协程调度的理解要求比requests高一个量级。而requests把HTTP请求封装成了一次“函数调用”让你能把精力放在业务逻辑上而不是手写一堆socket解析。先把requests吃透再用它作为跳板去理解更底层的协议、异步模型这条路比一上来就挑战硬核框架顺畅得多。1.2 发请求之前先搞懂HTTP到底在做什么requests是好用但它替你封装的那层东西你得知道是什么。HTTP本质上是一个“请求-响应”协议客户端发一段结构化文本给服务器服务器解析后返回另一段文本。整个过程和一个很日常的场景很像——你去餐厅吃饭你坐下服务员过来你报出菜名——这就是请求方法GET是“我看一眼菜单”POST是“我要点这道菜”。你填了个电话号码给服务员备注——这就是**请求头Headers**里的元信息比如User-Agent告诉服务端“我是用什么客户端来的”。厨师做好菜服务员端上来——这是响应。菜牌上的编号和最终上桌的菜品一一对应——这就是状态码Status Code200表示“有这道菜正常上桌”404表示“菜单上没有这道菜”500表示“后厨出事了”429则是“我忙不过来你先别点菜了”。你真正写代码时不一定需要手撸HTTP报文但看到状态码、请求头、响应体这些词脑海里得有这张“餐厅流程图”。否则遇到问题时你连从哪一步开始排查都不知道。1.3 一次requests.get()背后到底发生了什么很多人用requests就是“照抄代码”却不理解一行requests.get(url)到底做了多少事。拆开看它在几毫秒到几秒内替你完成了URL解析与参数编码DNS域名解析TCP连接建立三次握手TLS/SSL握手如果URL是HTTPS构造并发送HTTP请求报文接收并解析响应头、响应体自动处理cookie、重定向、连接复用等细节这些步骤里任何一步失败requests都会抛异常。比如DNS解析失败抛requests.exceptions.ConnectionError连接超时抛requests.exceptions.ConnectTimeout请求被服务端拒绝返回4xx状态码时requests会把它包装成HTTPError但不会主动抛它后面我会讲为什么。所以说requests的价值不是“让你不用懂网络”而是“让你在懂网络的前提下把精力从底层重复劳动中解放出来”。写Python入门阶段的项目没有比这个库更适合当“HTTP协议活教材”的。2. 环境准备与第一个请求安装细节与踩坑实录2.1 Python3环境与pip安装在装requests之前先确保本机有一个能用的Python3环境。终端里输入python3 --version如果提示找不到命令先去官网下载对应系统的安装包。装完后顺手确认pip可用pip3 --version有了pip安装requests就一句话的事pip3 install requests安装完成后强烈建议先验证一下版本因为很多“奇怪报错”都源于版本过旧或升级到一半的半成品状态import requests print(requests.__version__)如果输出一个类似2.31.0的版本号说明环境正常。这里提醒一句尽量在虚拟环境中做这件事。我知道对新手来说“虚拟环境”这个词有点吓人但你只需要一条命令python3 -m venv myenv source myenv/bin/activate # Windows下是 myenv\Scripts\activate好处是不同项目之间依赖互不污染以后你装了一百个库把系统Python搞崩的概率会低很多。我早期就是图省事直接在系统Python里pip install结果某次升级系统自带的某个库把整个开发环境搞到崩溃重装系统才缓过来。这个坑是真的疼。2.2 新手一定会遇到的提示defaulting to user installation你执行pip install requests时很可能看到这样一段提示Defaulting to user installation because normal site-packages is not writeable很多新手看到这行英文就慌了以为装失败了。其实翻译过来就是当前Python的全局site-packages目录没有写权限所以pip自动改道把库装到你当前用户自己的目录下了。这不一定是个错误——Python官方推荐的就是“能装到用户目录就装用户目录”避免影响系统级Python。但如果你发现import requests能用而pip show requests显示的位置和你预期不符也不用慌这是pip的正常策略。当然如果你希望装到系统全局要么给目录加权限不推荐容易破坏环境要么使用虚拟环境推荐。顺便回答一个高频问题——“python中requests库如何升级”。看版本、升级、检查三步走pip show requests # 查看当前版本和安装位置 pip install --upgrade requests # 升级到最新版 pip install requests2.28.1 # 指定版本安装升级前注意如果手头有跑在旧环境的生产项目先看一眼项目里有没有用到requests的旧参数比如早期版本的verify、hooks等行为差异避免升级后行为不一致。我遇到过某次升级后urllib3版本跟着变了某些连接池行为出现细微差别排查了半天才发现是版本升级引起的。2.3 第一个请求requests.get()的核心用法环境没问题代码就走起来。先跑一个最简单的GETimport requests response requests.get(https://httpbin.org/get) print(response.status_code) print(response.text)初次运行你可能看到几百行JSON格式的响应体。这个httpbin.org是专门用来测试HTTP请求的工具站强烈推荐新手拿它当“练手沙盒”。它会原样回显你发出的请求参数、Headers、IP方便你验证自己对requests的每项配置是否生效。如果打印response.status_code得到200说明这一步你已经从“听说过requests”跨到了“亲手跑通requests”。接下来要做的就是把这一个请求拆解出各种花样——给它加参数、加请求头、加超时、加session。下一节一个个说。3. 核心API拆解参数、请求头、Session与响应对象3.1 params、headers、timeout写进每个请求的必备三件套很多新人只会写requests.get(url)一旦遇到需要传参、伪装浏览器的场景就处处卡壳。实际上请求的三大核心配置是params、headers、timeout它们的配合能解决80%的日常请求需求。params把查询参数拼进URL的工具。新手最常见的错误是手动拼URL# 不推荐手动拼接容易漏编码 url https://httpbin.org/get?name张三age18 # 推荐交给requests处理编码 params { name: 张三, age: 18 } response requests.get(https://httpbin.org/get, paramsparams) print(response.url)当你用第二段代码时requests会自动对中文、特殊符号做URL编码。尤其当参数里出现、#这类容易“污染”URL段结构的字符时手动拼接简直是自找麻烦。我调研过不少爬虫项目报错原因最后都落在“URL没编码”上。headers请求头的字典。最常设置的是User-Agent标识客户端类型、Referer标识来源页、Cookie身份信息。用法headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } response requests.get(https://httpbin.org/headers, headersheaders)为什么要设置User-Agent因为很多服务端会检查“你是不是一个正经浏览器”。requests默认的User-Agent是python-requests/x.x.x服务器一看就知道你在用脚本访问轻则返回泛化数据重则直接拒绝。把User-Agent伪装成浏览器是最基础的反爬应对手段但注意这只能对付最基础的简单反爬规则不代表你可以用它在别人网站上乱爬数据——务必遵守目标网站的robots协议和法律法规。timeout这是我在任何入门教程里都会强调的参数没有例外。看这段代码# 危险写法没有超时限制服务端不响应时程序会一直挂着 response requests.get(https://example.com/slow) # 正确写法连接和读取阶段分别设置超时 response requests.get(https://example.com/slow, timeout(3, 10))timeout(3, 10)的意思是建立连接最多等3秒拿到响应头后读取数据最多等10秒。连接阶段超时会抛ConnectTimeout读取阶段超时会抛ReadTimeout。没有timeout的请求一旦对方服务器“装死”你的脚本就会一直卡在那里看起来像死循环。我在生产环境见过因为少写一个timeout导致定时任务挂了整整一晚上的事故。3.2 响应对象status_code、text、json()、content怎么选响应对象Response上有几个属性/方法新手最容易混淆我这里用一个表格讲明白它们的区别和适用场景响应元素返回类型什么时候用注意点response.status_codeint判断请求是否成功2xx表示成功4xx是客户端问题5xx是服务端问题response.textstr想直接看文本内容使用response.encoding解码编码不对会乱码response.contentbytes下载文件、图片、二进制流不涉及编码问题适合直接写文件response.json()dict/list接口返回JSON格式时必须先确认content-type是application/json否则抛JSONDecodeErrorresponse.headersCaseInsensitiveDict查响应头信息键不区分大小写response.headers[Content-Type]和response.headers[content-type]等价实际经验是**能先判断状态码就绝不直接response.json()。**比较稳妥的写法response requests.get(https://httpbin.org/get, timeout5) if response.status_code 200: data response.json() print(data[headers]) else: print(f请求失败状态码{response.status_code})如果你用response.json()解析的不是JSON格式或者JSON结构不完整会直接抛异常脚本就中断了。所以“先看状态码再解析响应体”应该成为肌肉记忆。还有一个常见误区很多人用response.text看到一串乱码就以为网站有问题其实往往是编码判断错了。下一节爬虫流程里我会专门讲编码处理。3.3 Session会话让多个请求共享Cookie、Headers和连接池同一个网站上你通常需要连续发送多个请求比如“先登录再访问个人中心”。这时如果每次都用requests.get()Curl每次都是独立的Cookie传不过去服务器就会觉得你是陌生人。requests.Session()就是为了解决这个问题存在的。它像一个“带状态的浏览器标签页”session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 ... }) # 第一次请求登录接口服务端返回Cookie并自动存储在session里 session.post(https://httpbin.org/cookies/set, params{name: test}) # 第二次请求自动携带session里存的Cookie response session.get(https://httpbin.org/cookies) print(response.json())Session还能复用TCP连接所以对频繁请求的场景更有性能优势。但注意Session用完最好调用session.close()或者用上下文管理器with requests.Session() as s:。如果一直new Session不关闭连接池里的空闲连接会越积越多最终可能出现“Too many open files”之类的系统级报错。早期我的爬虫脚本就有这个问题长时间跑下来网络连接数爆炸只能重启进程后来才意识到是Session没关导致的。3.4 POST请求data和json到底传哪个POST请求常见于提交表单、登录、调用API。requests里传body有两个参数一个是data一个是json它们的行为有本质区别# dataapplication/x-www-form-urlencoded 表单格式 requests.post(https://httpbin.org/post, data{name: 张三}) # jsonapplication/json JSON格式 import json requests.post(https://httpbin.org/post, json{name: 张三})用data时requests会按表单格式编码服务端接收到的是一段keyvaluekey2value2字符串用json时requests会先把字典转成JSON字符串再发送。绝大多数现代Web API都要求json格式所以如果你对接的是一个接口文档里写着“request body为JSON”的服务就直接用json参数。再强调一个小知识点requests.post(url, jsonpayload)等价于requests.post(url, datajson.dumps(payload), headers{Content-Type: application/json})但前者更简洁也不用你自己记着加Content-Type头。初学者别再手动json.dumps了直接交给你就完事儿。4. 用requests写第一个爬虫从单页抓取到完整流程4.1 实战目标抓取一个简单页面并解析内容与其背概念不如直接写一个完整的爬虫示例。这里我用https://httpbin.org/json作为目标它返回一组固定JSON数据适合用来验证流程。import requests def fetch_json(url): headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } response requests.get(url, headersheaders, timeout(5, 10)) if response.status_code 200: return response.json() else: raise Exception(f请求失败: {response.status_code}) if __name__ __main__: data fetch_json(https://httpbin.org/json) print(data)这一步你已经完成了一个最小可用的爬虫骨架有请求头、有超时、有状态判断、有数据解析。后续所有爬虫项目不管目标网站多复杂骨架基本就是这个。千万别一开始就想着“怎么爬淘宝”“怎么爬抖音”先把这类玩具网站抓通了再逐步加编码处理、异常处理、重试逻辑。4.2 编码与乱码处理为什么text出来是乱码爬虫新手遇到最多的问题就是状态码200但response.text打印出来全是乱码。原因很简单HTTP响应头里有一个Content-Type字段里面通常带有charsetutf-8之类的编码声明requests用这个声明去解码。但有些网站响应头里没带charset或者声明和实际内容编码不一致比如页面实际上是GBK编码声明却是UTF-8那么用response.text解码出来自然就是歪的。处理方式有两种# 方案一手动指定编码 response.encoding gbk print(response.text) # 方案二让requests通过内容本身去推测编码 response.encoding response.apparent_encoding print(response.text)apparent_encoding是requests基于内容字节统计推测出来的编码对乱码问题是个很好的兜底方案。但注意它需要遍历内容做统计会牺牲一些性能。所以我的习惯是先从响应头里找charset找不到再用apparent_encoding。实际抓取中文网站时“声明UTF-8实际GBK”和“声明GBK实际UTF-8”的情况我都遇到过编码检测这步真的不能省。4.3 异常处理与重试逻辑网络请求本身是不稳定的DNS抖动、连接被重置、超时、限流——随时可能发生。所以爬虫代码里异常处理比业务逻辑更要紧。requests的异常体系以requests.exceptions为根我平时至少会捕获这四个import requests from requests.exceptions import RequestException, Timeout, HTTPError, ConnectionError try: response requests.get(url, timeout(3, 5)) response.raise_for_status() # 状态码非2xx时主动抛出HTTPError except Timeout: print(请求超时稍后重试) except HTTPError as e: print(fHTTP错误状态码: {e.response.status_code}) except ConnectionError: print(连接失败检查网络或域名是否可访问) except RequestException as e: print(f其他请求异常: {e})这里有个容易被忽视的点response.raise_for_status()。默认情况下requests不会因为你收到404或500就抛异常它只负责“收到响应”不负责“响应正确”。如果你不主动检查状态码、不调用raise_for_status()那么遇到服务端返回错误页面时你的程序可能还在傻乎乎地解析错误页面HTML。我建议把它当成“请求成功”的守门员放在每个请求后面。至于重试最简单的原则是先“有限重试”再“人工介入”。比如遇到429或5xx睡眠几秒后重试最多3次。不要无限重试否则对方服务器一限流你这边就变成自杀式轰炸。后面第5节我会讲怎么配置更专业的重试策略。4.4 反爬初体验User-Agent伪装与请求频率控制第一次拿httpbin.org跑通后你会忍不住想去抓点真实网站。这个过程中你碰到的第一个“墙”往往是反爬机制。最基础的反爬检查就是看User-Agent。你发给httpbin.org/headers看看如果输出里有个Python-Requests字样说明你的身份太明显了。应对的办法是伪装成浏览器把User-Agent复制成你常用浏览器的完整UA字符串。但不建议把UA伪造得太乱比如某些防护严格的服务还会校验UA和实际浏览器行为是否匹配。作为入门阶段先学会设置一个合理UA再配合time.sleep(2)控制请求频率模拟一个“人在正常看网页”的节奏就已经能解决很多简单站点的反爬问题。再次强调爬虫一定要尊重目标网站的robots.txt和公开声明不碰需要登录才能访问的个人数据不以任何方式给服务器造成过载压力。学习阶段测试时用公开接口就好别拿别人的业务系统练手。这既是技术规范也是底线。5. 高频报错排查429 Too Many Requests 与重试上限5.1 “exceeded retry limit, last status: 429”到底在说什么如果你写过一个脚本、跑过一次定时任务、或者调用过第三方API大概率见过这么一段报错exceeded retry limit, last status: 429 too many requests这句话翻译过来是“重试次数已用尽最后一次响应的状态码是429”。429这个数字在HTTP世界里含义非常明确服务器认为你在一段时间内发送了太多请求触发了它预设的速率限制。服务器没有把你当恶意用户封死而是以429的状态码告诉你“太快了先歇会儿”。值得注意的是这个报错通常不是requests直接抛出来的。requests默认遇到429会正常返回Response对象而不会重试。真正抛出这个报错的多半是第三方封装库、框架、或者你通过HTTPAdapter配置的重试机制——它们会按既定次数去重试每次重试之间没有合适退避重试耗尽后就把“最后的状态码是429”作为异常抛了出来。所以看到这个报错说明你已经走入了“有重试逻辑”的层次只需做两件事分析为什么会被限流、调整重试策略和请求频率。5.2 根因分析是先有“请求过多”还是先有“重试不当”我遇到过很多次429报错认真复盘后发现真正的原因往往不是单一的。可以从这几个方向排查服务端限流设置很多公开API对未认证请求的速率限制低得惊人比如“每分钟最多20次”。如果你并发写着8个线程疯狂发请求撞上429只是时间问题。代理IP质量如果用了代理池代理节点可能是很多人共享的一旦某个节点已被限流你发什么请求都会撞429。换成独享代理或调整节点问题通常会消失。User-Agent过于集中多个并发请求共用同一个UA加上请求频率又高服务端很容易识别出“这不是一个正常人”。重试退避策略缺失这是最容易被忽略的。很多重试逻辑写的是“失败了就立刻重试”比如for i in range(3): try: return requests.get(url) except: continue # 没有睡眠没有退避这样遇到429重试3次三次几乎是瞬间打的服务器连续收到你的请求会继续返回429重试自然全部无效。最终就出现了“exceeded retry limit, last status: 429”。限流触发后立刻重试是重试的大忌你必须等待等限流窗口过去再发。5.3 一个完整的429排查链路复现为了让你以后遇到429不慌我复盘一次比较典型的排查过程你也可以照着这个思路走第一步复现报错。写个小脚本单请求打到目标接口发现能正常200返回。说明问题不是“接口本身坏了”而是“访问频率超过了阈值”。第二步统计失败分布。给脚本加上日志记录每次请求的时间戳和状态码。跑一段时间后你会发现前十几个请求全部200从某个时刻开始出现连续的429。这就定位到了“第几次请求触发限流”。第三步检查重试配置。看自己代码里有没有设置重试重试间隔是多少。如果发现是“失败就立即重试”先改成“失败后等待2到5秒再重试”并且只重试一次做验证。第四步控制源头发送速率。把并发数降下来或者加大每次请求之间的间隔。比如原来每秒10次改成每秒2次、每4次之间sleep 1秒。再跑429基本消失。第五步如果还是429。去看是不是代理IP的问题。换一个全新节点如果问题消失就是原节点被限流了查看是不是目标接口要求携带特定Headers比如APIToken有些服务不携带认证会给出更严格的限流阈值。这五步走完绝大多数429都能定位到具体原因。真正难处理的是“服务端限流窗口很长比如按小时限制总量”这种情况那只能靠节流和排队没有捷径。5.4 用HTTPAdapter配置请求重试策略requests内置的重试能力主要通过HTTPAdapter和urllib3.Retry实现。这是官方推荐的做法比手写for i in range(3)要专业得多尤其适合程序在长时间运行、需要自动处理临时故障的场景。看这段配置import requests from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry def build_session(retries3, backoff_factor0.5): session requests.Session() retry_strategy Retry( totalretries, # 总重试次数 backoff_factorbackoff_factor, # 退避因子每次重试等待 0.5*2^(n-1) 秒 status_forcelist[429, 500, 502, 503, 504], # 遇到这些状态码才重试 allowed_methods[GET, POST] # 只对幂等/指定方法重试 ) adapter HTTPAdapter(max_retriesretry_strategy) session.mount(http://, adapter) session.mount(https://, adapter) return session if __name__ __main__: s build_session() response s.get(https://httpbin.org/status/429, timeout5) print(response.status_code)重点说几个参数的意义total3单个请求最多重试3次不会无限重试。backoff_factor0.5重试等待时间按指数退避策略计算。第1次重试等待0.5秒第2次1秒第3次2秒。这样设计是因为服务端限流后需要时间恢复指数退避比固定间隔更合理。status_forcelist只有明确列出的状态码才触发重试。不是所有状态码都值得重试比如401未认证重试多少次都没用404也不会因为重试就变成200。allowed_methods[GET, POST]只对特定方法重试。POST有时候不是幂等的重复提交可能会导致业务数据重复所以用起来要谨慎。这套配置挂上之后“exceeded retry limit”就变成了一个“可预期的、受控的”事件而不是脚本突然崩溃。但这里要泼盆冷水重试策略是最后一道防线不是第一声警报。真正的解决之道永远是“从源头把请求频率控制在合理范围”重试只是兜底不是躺赢的借口。5.5 从根上减少429请求频率控制与并发设计很多朋友跑爬虫或者批量脚本时觉得“我加了重试就不会出问题了”。但实际生产经验告诉我好的请求模型是“前面少发后面稳定”而不是“前面猛发后面拼命重试”。几个经过验证的做法控制请求间隔。最简单也最有效的办法import time for url in urls: response requests.get(url, timeout5) print(response.status_code) time.sleep(1) # 每请求一次固定间隔1秒具体的间隔时长没有标准答案取决于目标服务的限流阈值。你可以从1秒开始尝试如果还出现429就加到2秒、3秒。并发度保守设置。如果你用ThreadPoolExecutor建议初始并发数从2到4开始跑观察稳定后再说。不要一上来就开32线程那不是请求是给小网站做压力测试。做本地缓存/去重。同一个数据本地已经有副本了就别重复请求拿完整响应改用Etag或条件请求。减少请求总量才是对限流最友好、对服务器最尊重的方案。预留限流余量。如果对方限流是“每秒钟最多10次”你自动调低到每秒6到8次。两三个小时跑完比“5分钟跑完但因为限流卡半天”高效得多。我曾经为了“跑得快”把并发调很高结果不仅数据没抓到多少还把对方服务打到限流最后连正常访问的请求也被临时封了教训非常深刻。6. 进阶使用与我的几点实战经验6.1 SSL验证、代理与大文件下载入门阶段你可能不关心SSL验证但对接真实接口时早晚会碰到。某些自建证书、内网服务证书链可能不完整requests会抛requests.exceptions.SSLError。最简单的临时处理是verifyFalseresponse requests.get(https://self-signed.badssl.com/, verifyFalse)但这是临时方案不是长期方案。生产环境里更稳妥的做法是把你信任的证书加到verify参数里比如response requests.get(https://example.com, verify/path/to/ca.pem)verifyFalse会同时产生一个警告InsecureRequestWarning你可以用urllib3.disable_warnings()关掉它但请记住这相当于你打开了“不验明对方身份”的门只适用于完全受控的内网环境。我在本地开发时偶尔用它但一到正式环境必配证书路径绝不偷懒。代理的使用也很常见proxies { http: http://127.0.0.1:7890, https: http://127.0.0.1:7890 } response requests.get(https://httpbin.org/get, proxiesproxies)这里特别提醒一些网络调试工具默认会开启系统代理你用requests时如果没显式指定代理而是自动读了系统代理请求可能走了一条“你没想到的路”。排查网络问题时先看看requests.get(..., proxies{http: None, https: None})是不是绕开了代理才正常很多谜之超时就这么解决的。写到这我突然意识到上面这个端口号只是随便举例你完全不需要关心具体值只管记住“requests支持代理字典”这个事实即可。6.2 大文件下载与流式读取下载大文件时如果你是response.content一次性读完再写文件内存占用会非常难看。假设一个500MB的压缩包直接content读进来内存直接爆炸。正确做法是走流式读取import 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_size1024 * 1024): f.write(chunk)streamTrue告诉requests不要急着把整个响应体都下载到内存而是边读边写入文件。iter_content每次拉取1MB的数据块。这样即使文件有几十个GB内存消耗也几乎可以忽略。顺带一个小技巧中断续传时配合Range请求头还能实现断点下载这个进阶玩法等你用到再去深挖。6.3 并发请求数组大了先想清楚要不要并发很多人一谈到“提高效率”就想上并发。但并发带来两个问题一是对目标服务器的压力成倍增加容易触发429二是代码复杂度上升调试难度加倍。所以我的建议是小任务先串行串行确实太慢再上并发并发数从保守开始调。如果非要并发最简单很低门槛的方案是用concurrent.futures。它和requests配合起来很顺手import concurrent.futures import requests URLS [https://httpbin.org/get] * 10 def fetch(url): response requests.get(url, timeout5) return len(response.content) with concurrent.futures.ThreadPoolExecutor(max_workers4) as executor: results list(executor.map(fetch, URLS)) print(results)注意这段代码里每个线程都直接requests.get没有共用Session。如果想共用同一个Session多线程里必须加锁或依赖Session自身的线程安全保证否则可能出现连接池并发问题。对新手来说我建议先用多线程独立请求等理解透了再考虑外层封装Session池。6.4 requests升级维护与兼容性注意最后聊聊升级维护这件“小事”。很多人装完requests就再也不管了直到某天跑一个项目报错说urllib3 v2.0 only supports OpenSSL 1.1.1才知道问题出在基础库版本不兼容上。我的维护习惯是每隔一段时间执行pip list --outdated看看requests和urllib3是否有新版。升级前先看release notes特别是涉及verify、timeout、Session等行为变动的说明。升级后立刻跑一遍自己项目的基础测试用例确认没有行为回归。如果项目要长期维护可以把requests的版本号写死在requirements.txt里升级时手动确定避免某次顺手升级把线上项目搞挂。real talkrequests这个库的核心API设计得非常稳定过去这么多年变化并不大。所以如果你用着旧版本业务代码基本不会有兼容问题真正的大坑往往来自它的依赖们——urllib3、certifi、charset-normalizer——这些底层库升级后行为可能会变化间接影响requests。所以排查requests异常时看下依赖版本总没错。6.5 我踩过的几个坑说给新手当预防针最后一节不啰嗦他们官方文档里的东西就说点个人手记。第一个坑是“升级requests后urllib3版本冲突”。有次我图省事直接升级了urllib3结果requests还是旧版两者组合后不断抛InsecureRequestWarning代码本身没变但日志全被警告刷屏。后来统一升到兼容版本一切恢复正常。从此我升级任何库都会带上“依赖一起升”。第二个坑是“Session不关闭连接数爆炸”。有段时间我写的定时任务每天跑一次每次new Session但不close跑了不到两周系统报错“Too many open files”。查了半天才发现Session对象虽然会随着进程退出被GC回收但在长驻进程里如果不手动close连接池的空闲连接不会立刻释放。从那之后我全改成了with requests.Session() as s:的写法。第三个坑是“重试逻辑写得太猛把对方服务打到限流”。这个之前提过但我还是想说requests本身只是个工具真正决定你项目命运的是你用工具的节奏和底线。遇到429正确情绪不是“我要重试到它给我200”而是“我的请求速率要调整到它不给我429”。学习requests这门功课其实学的不只是一个库的API而是“如何优雅地跟远端服务打交道”。你把请求频率控制好、异常处理写全、重试策略配置合理就已经比很多“只顾抓数据不管对方死活”的脚本强了。基础打牢后再去看看httpx、aiohttp这些进阶的选择会轻松很多。一步一步来不用急。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑