ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python requests API调用实战:认证、限流与错误处理

Python requests API调用实战:认证、限流与错误处理 1. 为什么第一个API调用总是卡在认证这一步很多人第一次接触API调用卡住的地方往往不是代码本身而是我到底该拿什么去换数据。打开文档看到access_token、api_key、Bearer、client_id、client_secret这一堆名词脑子直接宕机。我见过太多人把api_key当成access_token塞进请求头然后对着401错误发呆半小时。先把这件事讲透。API调用的本质是你向一个远程服务发起HTTP请求对方验证你的身份后返回数据。这个验证身份的环节就是认证。认证方式五花八门但落到Python的requests库里最终都体现为往请求头或请求参数里塞点东西。常见的认证模式有这么几种我用一张表把它们理清楚认证方式典型字段放置位置适用场景API Keyapi_key/key请求头或URL参数简单服务、个人项目Bearer TokenAuthorization: Bearer xxx请求头大多数现代APIOAuth 2.0access_token请求头或参数需要用户授权的平台签名认证sign 时间戳请求参数金融、云服务access_token和api_key最大的区别在于api_key通常是长期有效的固定字符串而access_token往往有有效期需要通过client_id和client_secret去换取过期了还得刷新。这就是为什么你在热搜词里会看到access_token1967ab5c237这种片段——它是某个OAuth流程返回的临时凭证。我个人的经验是拿到一个新API先别急着写业务代码用最笨的办法跑通认证。打开Postman或者直接用curl把认证请求发出去看到返回的JSON里确实有access_token字段再动Python。这一步能帮你排除掉80%的环境问题。import requests # 第一步换取access_token以OAuth 2.0客户端凭证模式为例 auth_url https://api.example.com/oauth/token auth_payload { grant_type: client_credentials, client_id: your_client_id, client_secret: your_client_secret } auth_resp requests.post(auth_url, dataauth_payload, timeout10) token auth_resp.json().get(access_token) print(拿到token:, token[:20], ...)这段代码里有几个细节值得说。timeout10是必须加的不加的话网络一抖动你的程序就永久挂起。data而不是json是因为OAuth的token端点通常要求application/x-www-form-urlencoded格式用错了对方直接返回400。这些坑我都踩过文档里往往一笔带过但实际调试时能耗掉你一下午。提示access_token拿到后不要硬编码在代码里更不要提交到代码仓库。用环境变量或者配置文件管理这是基本的安全习惯。2. requests库发请求时那些文档不会告诉你的细节认证跑通之后就进入真正的请求环节。requests库的API设计得极其简洁requests.get()一行就能发请求但简洁的背后藏着不少需要理解的东西。热搜词里出现了requests库的get方法、http连接复用、http和https的区别说明这些是大家真正困惑的点。2.1 GET和POST到底怎么选新手最容易犯的错是把所有请求都写成GET。判断标准其实很简单读取数据用GET提交数据用POST。GET的参数拼在URL里有长度限制而且会被浏览器和服务器日志记录POST的参数放在请求体里适合传输敏感信息或大量数据。# GET参数通过params传递requests会自动帮你URL编码 resp requests.get( https://api.example.com/search, params{keyword: python, page: 1}, headers{Authorization: fBearer {token}}, timeout10 ) # POST参数通过json或data传递 resp requests.post( https://api.example.com/create, json{title: 测试, content: 内容}, headers{Authorization: fBearer {token}}, timeout10 )注意params和json的区别。params会自动把字典拼成?keywordpythonpage1并且处理特殊字符的编码。如果你手动拼URL遇到中文或空格就会出问题。json会自动设置Content-Type: application/json并把字典序列化这是现代API最常用的格式。2.2 连接复用为什么能救命热搜词里http连接复用和429 too many requests同时出现这不是巧合。当你用requests.get()发请求时每次都会新建一个TCP连接用完就关。如果在一个循环里发几百次请求光是建立连接的开销就够呛而且很多服务会对频繁的新连接做限流。解决办法是用Session对象。Session会保持底层连接复用TCP连接还能自动携带cookie和默认请求头。session requests.Session() session.headers.update({Authorization: fBearer {token}}) for i in range(100): resp session.get(fhttps://api.example.com/item/{i}, timeout10) # 处理resp实测下来用Session之后同样的100次请求耗时能从十几秒降到两三秒。这个优化在批量调用场景下是质变。2.3 状态码不是只有200和404很多人只看resp.status_code 200其他一律当失败。实际上状态码分五类每一类的处理策略完全不同2xx成功。200是标准成功201是创建成功204是无内容返回。3xx重定向。requests默认会自动跟随重定向但有些API的重定向需要你手动处理。4xx客户端错误。400是参数错误401是认证失败403是无权限404是资源不存在429是请求过于频繁。5xx服务端错误。500是内部错误502是网关错误503是服务不可用。热搜词里exceeded retry limit, last status: 429 too many requests出现频率极高说明限流是大家最常撞的墙。429的处理方式后面单独讲。3. 限流、重试与超时让调用稳定下来的三件套API调用从能跑通到能稳定跑中间隔着的就是限流、重试和超时这三件事。热搜词里exceeded retry limit、429 too many requests、502 bad gateway、unexpected status反复出现说明这是绝大多数人的痛点。3.1 429限流的本质与应对429的意思是你请求太快了歇会儿再来。服务端通常会在响应头里告诉你还要等多久比如Retry-After: 5表示5秒后再试。但很多服务不返回这个头只返回一个429。应对429的核心思路是指数退避第一次失败等1秒第二次等2秒第三次等4秒以此类推直到成功或达到最大重试次数。import time import requests def request_with_backoff(url, headers, max_retries5): for attempt in range(max_retries): try: resp requests.get(url, headersheaders, timeout10) if resp.status_code 429: wait 2 ** attempt print(f被限流等待{wait}秒后重试) time.sleep(wait) continue resp.raise_for_status() return resp.json() except requests.exceptions.RequestException as e: print(f第{attempt1}次失败: {e}) if attempt max_retries - 1: raise time.sleep(2 ** attempt) raise Exception(超过最大重试次数)这段代码的关键在于2 ** attempt它让等待时间指数增长。为什么不用固定间隔因为如果服务端压力大固定间隔的重试会持续给它施压指数退避能给它喘息空间也提高你最终成功的概率。3.2 超时设置不能只写一个数字timeout10看起来简单但它同时设置了连接超时和读取超时。更精细的做法是传一个元组resp requests.get(url, timeout(3.05, 27))第一个数字是连接超时第二个是读取超时。连接超时设短一点3秒左右因为连不上就是连不上等再久也没用读取超时设长一点因为服务端处理复杂请求可能需要时间。这个(3.05, 27)的组合是很多生产系统的经验值3.05是为了避开某些系统的TCP重传窗口。3.3 502和503这类服务端错误怎么办502 Bad Gateway和503 Service Unavailable都是服务端的问题不是你的错。但你的程序不能直接崩得能扛住。处理方式和429类似用重试加退避。区别在于429是明确告诉你慢点而502/503可能是服务端临时抽风重试几次往往就好了。我自己的做法是给重试加一个上限比如5次超过就记录日志并抛出异常让上层决定是跳过还是终止。无脑无限重试只会让你的程序卡死。注意重试只对幂等操作安全。GET、PUT、DELETE是幂等的重试没问题POST通常不幂等重试可能导致重复创建。如果必须重试POST要确保服务端支持幂等键。4. 从请求到数据响应解析与错误定位的实战方法请求发出去了返回了200但拿到的数据不对或者JSON解析报错这是另一个高频问题。热搜词里api error: 400、the specified http method is not allowed、maximum context length这些错误本质上都是请求发出去了但对方不认。4.1 先看原始响应再解析新手常犯的错是直接resp.json()一旦返回的不是JSON就抛异常而且看不到原始内容。正确的做法是先看resp.text确认内容格式再解析。resp requests.get(url, headersheaders, timeout10) print(状态码:, resp.status_code) print(响应头:, dict(resp.headers)) print(原始内容:, resp.text[:500]) # 只打印前500字符 if resp.headers.get(Content-Type, ).startswith(application/json): data resp.json() else: print(返回的不是JSON需要检查)这个习惯能帮你快速定位问题。比如400错误resp.text里通常会写明是哪个参数不对the specified http method is not allowed说明你用错了方法该用POST的地方用了GET。4.2 400错误的常见原因排查400 Bad Request是最常见的客户端错误原因通常有这几类参数缺失或格式错误必填参数没传或者类型不对该传数字传了字符串。Content-Type不匹配服务端要application/json你传了application/x-www-form-urlencoded。请求体不是合法JSON手动拼JSON字符串时多了个逗号或少了个引号。模型名称不支持热搜词里the supported api model names are deepseek-flash, deepseek-v4就是典型你传的模型名不在支持列表里。排查方法很直接把resp.text完整打印出来服务端通常会告诉你具体哪里错了。如果服务端不告诉你就对照文档逐个参数检查。4.3 上下文长度超限的处理热搜词里this models maximum context length is 1048576 tokens这个错误说明你发送的内容超过了模型能处理的最大长度。这类错误在调用大模型API时特别常见。处理思路有两个一是截断输入只保留最关键的部分二是分段处理把长文本拆成多段分别调用再合并结果。截断的时候要注意不能简单地从中间切最好按语义边界比如段落、句子来切避免把一句话切成两半。def truncate_by_tokens(text, max_chars3000): 按字符数粗略截断实际项目建议用tokenizer精确计算 if len(text) max_chars: return text return text[:max_chars] ...(已截断)字符数和token数不是一回事中文里一个字符可能对应一到两个token。粗略估算的话中文按1.5倍算比较保险。精确计算需要用到对应模型的tokenizer这个后面进阶部分再说。5. 把调用封装成能复用的工具类跑通单个请求之后下一步是把这些逻辑封装起来避免每次调用都重复写认证、重试、超时代码。这是从能跑到好用的关键一步。5.1 一个实用的API客户端骨架import requests import time import os class APIClient: def __init__(self, base_url, tokenNone): self.base_url base_url.rstrip(/) self.session requests.Session() if token: self.session.headers.update({ Authorization: fBearer {token}, Content-Type: application/json }) self.session.headers.update({User-Agent: MyApp/1.0}) def _request(self, method, path, max_retries5, **kwargs): url f{self.base_url}/{path.lstrip(/)} kwargs.setdefault(timeout, (3.05, 27)) for attempt in range(max_retries): try: resp self.session.request(method, url, **kwargs) if resp.status_code 429: wait 2 ** attempt time.sleep(wait) continue if resp.status_code 500: time.sleep(2 ** attempt) continue resp.raise_for_status() return resp.json() except requests.exceptions.RequestException as e: if attempt max_retries - 1: raise time.sleep(2 ** attempt) def get(self, path, **kwargs): return self._request(GET, path, **kwargs) def post(self, path, **kwargs): return self._request(POST, path, **kwargs)这个骨架把认证、重试、超时、连接复用都封装进去了。用的时候只需要client APIClient(https://api.example.com, tokenos.getenv(API_TOKEN)) data client.get(/users/1) result client.post(/messages, json{content: hello})5.2 为什么用Session而不是每次新建前面提过连接复用这里再强调一次。Session不仅复用TCP连接还自动管理cookie、保持默认请求头。在需要多次调用的场景下用Session是标配。我见过有人在一个循环里写requests.get()几百次请求下来慢得离谱换成Session直接快一个数量级。5.3 日志记录不能省生产环境里每次请求都应该记录关键信息请求URL、状态码、耗时、错误信息。不用很复杂logging模块几行就够import logging logging.basicConfig(levellogging.INFO, format%(asctime)s %(levelname)s %(message)s) start time.time() resp self.session.request(method, url, **kwargs) elapsed time.time() - start logging.info(f{method} {url} - {resp.status_code} ({elapsed:.2f}s))出问题的时候这些日志就是你的救命稻草。没有日志你只能靠猜。6. 那些年我踩过的API调用坑最后这部分分享几个文档里不会写、但实际开发中一定会遇到的坑。这些都是真金白银换来的经验。坑一环境变量没生效。你把token写进.env文件代码里用os.getenv(API_TOKEN)读结果返回None。原因通常是忘了加载.env文件或者变量名拼错了。用python-dotenv的话记得在代码开头load_dotenv()。更隐蔽的情况是你在终端里export了变量但IDE的运行配置没继承终端环境。坑二代理设置干扰请求。有些环境配置了系统代理requests会自动读取HTTP_PROXY和HTTPS_PROXY环境变量。如果你的请求莫名其妙超时或返回502先检查这两个变量。临时禁用可以这样session requests.Session() session.trust_env False # 忽略系统代理设置坑三JSON里的中文变成乱码。这通常是编码问题。requests会根据响应头的Content-Type判断编码但有些服务端不返回正确的编码声明。解决办法是手动指定resp.encoding utf-8 data resp.json()坑四并发调用把服务打挂。用ThreadPoolExecutor并发调用时如果不控制并发数很容易触发429甚至把对方服务打挂。用Semaphore限制并发数或者用asyncio配合信号量。我一般把并发数控制在5到10之间具体看服务端的限流策略。坑五token过期没处理。access_token有有效期过期后返回401。好的做法是在客户端里加一个自动刷新逻辑捕获401重新获取token再重试一次原请求。这个逻辑要小心死循环刷新失败就直接抛异常。def _request_with_refresh(self, method, path, **kwargs): try: return self._request(method, path, **kwargs) except requests.exceptions.HTTPError as e: if e.response.status_code 401: self.refresh_token() return self._request(method, path, **kwargs) raise这些坑的共同点是文档不会告诉你但每个做API调用的人迟早都会遇到。提前知道能省下大量调试时间。我个人在实际操作中的体会是API调用这件事代码本身不难难的是对各种异常情况的处理。把认证、重试、超时、日志这四件事做扎实你的调用代码就能从玩具变成工具。至于更进阶的异步调用、连接池调优、token自动刷新这些等你把基础打牢了再往上加会顺很多。
返回列表