登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  python教程

Python requests 请求一直卡住怎么办:timeout、状态码和重试一步步排查

来源:17golang原创

时间:2026-06-16 16:17:22 330浏览 收藏

用 Python 写接口调用时,最怕的一类问题不是立刻报错,而是程序一直停在那里。日志没有继续输出,任务队列越堆越多,手动中断后才发现卡在 requests.get() 这一行。

我们这次按排查路线来走:先复现没有 timeout 的卡住现象,再加上超时限制,接着检查 HTTP 状态码,最后给临时失败场景加上有限重试。这样处理后,请求失败会变成可控失败,而不是把整个任务拖住。

摘要

requests 默认不会自动给请求设置超时时间。调用外部接口时,建议显式传入 timeout,再用 raise_for_status() 检查状态码。对 429、500、502、503、504 这类可重试状态,可以用 HTTPAdapterRetry 做有限重试,最后再检查返回体是否能按预期解析。

适合人群

  • 使用 Python requests 调第三方接口、内部接口或爬取公开页面的开发者。
  • 遇到接口调用偶发卡住、任务队列堆积、日志停在请求阶段的后端同学。
  • 想把 timeout、状态码检查和重试封装成统一请求方法的项目维护者。
目录
  • 问题现场:请求没有 timeout 导致任务卡住
  • 第一步验证:给请求加上 timeout
  • 第二步定位:状态码不能只看有没有返回
  • 修复方案:timeout、状态码检查和 Retry 组合
  • 最终封装:一个可复用的请求函数
  • 常见坑位
  • 总结检查清单

问题现场:请求没有 timeout 导致任务卡住

我们先看现象。一个同步任务每天拉取外部接口数据,平时几秒钟就能结束。有一天任务突然没有结束,日志最后一行停在“开始请求外部 API”,后续解析、入库、完成日志都没有出现。

import requests

def fetch_user_profile(user_id):
    url = f"https://api.example.test/users/{user_id}"
    response = requests.get(url)
    return response.json()

这段代码看起来很普通,但问题在于它没有设置 timeout。当网络链路、目标服务或代理层长时间没有返回时,任务可能一直等待。队列型任务里这会直接带来连锁反应:一个 worker 卡住,后面的任务开始堆积。

Python requests 没有 timeout 时任务等待、任务堆积,设置 timeout 后快速失败并进入重试恢复

这一步说明,我们不能只关注“接口是否能通”,还要给每次调用设定等待上限。超时不是坏事,它是把不可控等待变成可控失败的第一道边界。

第一步验证:给请求加上 timeout

接着验证这个猜测。我们先不改复杂结构,只给请求加一个明确的超时时间。requeststimeout 可以传一个数字,也可以传一个二元组,分别表示连接超时和读取超时。

import requests

def fetch_user_profile(user_id):
    url = f"https://api.example.test/users/{user_id}"
    response = requests.get(url, timeout=(3, 10))
    return response.json()

这里的 (3, 10) 表示连接阶段最多等 3 秒,服务已连接后读取响应最多等 10 秒。实际项目里要根据接口特征调整:内网接口通常更短,跨网络或第三方接口可以略长,但不建议完全不设。

加上 timeout 后,程序不会无限等待。它会在超时后抛出错误,我们就可以在外层捕获并记录上下文。

try:
    data = fetch_user_profile(1001)
except requests.exceptions.Timeout:
    print("请求超时,稍后重试或进入失败分支")

第二步定位:状态码不能只看有没有返回

现在请求不会无限卡住了,但还有一个隐藏问题:接口返回了,不代表业务成功。比如 500、502、503、504 都可能有响应体;如果代码直接 response.json(),可能把失败响应当成正常数据继续处理。

所以第二步要检查状态码。最直接的方式是调用 raise_for_status(),让非 2xx/3xx 的响应进入错误分支。

import requests

def fetch_user_profile(user_id):
    url = f"https://api.example.test/users/{user_id}"
    response = requests.get(url, timeout=(3, 10))
    response.raise_for_status()
    return response.json()

这一步的意义是把“有响应”拆成两层判断:先确认 HTTP 层成功,再解析业务数据。否则日志里只看到“请求完成”,但数据结构可能已经错了。

修复方案:timeout、状态码检查和 Retry 组合

现在可以定位到完整原因:没有超时会导致任务长期等待;没有状态码检查会让失败响应混进正常流程;没有有限重试会让临时抖动直接变成失败。解决方案是把这三件事组合起来。

Python requests 使用 Session、timeout、状态码检查、Retry、JSON 检查并返回可控结果

对临时故障,可以使用 urllib3Retry 配合 requests.adapters.HTTPAdapter。下面示例只对 GET 和 HEAD 这类适合重试的请求启用策略,并限制重试次数。

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def build_session():
    retry = Retry(
        total=2,
        connect=2,
        read=2,
        status=2,
        backoff_factor=0.5,
        status_forcelist=[429, 500, 502, 503, 504],
        allowed_methods={"GET", "HEAD"},
        raise_on_status=False,
    )

    adapter = HTTPAdapter(max_retries=retry)
    session = requests.Session()
    session.mount("http://", adapter)
    session.mount("https://", adapter)
    return session

这里不要把重试次数设得太大。重试是用来抵抗临时抖动,不是用来掩盖接口长期不可用。对写操作尤其要谨慎,除非接口本身有幂等设计,否则不要轻易自动重试。

最终封装:一个可复用的请求函数

最后把请求逻辑封装起来。我们希望得到的不是“永远成功”,而是失败时能清楚返回是哪一类失败:超时、HTTP 状态异常、JSON 解析失败,还是其他请求错误。

import requests

session = build_session()

def get_json(url, params=None):
    try:
        response = session.get(url, params=params, timeout=(3, 10))
        response.raise_for_status()
        return {
            "ok": True,
            "data": response.json(),
            "status_code": response.status_code,
        }
    except requests.exceptions.Timeout as error:
        return {"ok": False, "type": "timeout", "message": str(error)}
    except requests.exceptions.HTTPError as error:
        status_code = error.response.status_code if error.response else None
        return {"ok": False, "type": "http", "status_code": status_code}
    except requests.exceptions.JSONDecodeError as error:
        return {"ok": False, "type": "json", "message": str(error)}
    except requests.exceptions.RequestException as error:
        return {"ok": False, "type": "request", "message": str(error)}

这样封装后,上层业务就不用猜失败原因。比如 typetimeout,可以进入稍后重试;typehttp,可以看状态码决定是否告警;typejson,说明接口返回体不是预期格式。

常见坑位

1. 只设置连接超时,不设置读取超时

如果只传一个数字,写法简单,但不容易区分连接阶段和读取阶段。对接口排查来说,二元组更清楚:连接慢和响应慢是两类问题。

2. 所有方法都自动重试

GET 请求通常更适合重试,POST、PATCH、DELETE 等写操作要看接口是否支持幂等。如果没有幂等保障,自动重试可能造成重复提交。

3. 没有记录上下文

捕获错误后只打印“请求失败”不够。至少要记录 URL、参数摘要、状态码、失败类型和耗时。这样后续才能判断是网络问题、服务问题,还是数据格式问题。

4. 把 Retry 当成稳定性保证

Retry 只是缓冲短暂抖动。如果接口长期慢或频繁 5xx,真正要做的是限流、降级、熔断或联系接口提供方,而不是无限增加重试次数。

总结检查清单

  • 每个外部 HTTP 调用都要显式设置 timeout
  • 先检查 response.raise_for_status(),再解析 JSON。
  • 只对适合重试的方法和状态码启用有限 Retry。
  • 错误返回要区分 timeouthttpjsonrequest
  • 日志要包含失败类型、状态码和关键业务参数摘要。

回到最开始的问题:requests 请求一直卡住,第一反应应该是检查有没有设置 timeout。把超时、状态码检查、有限重试和返回体检查连成固定流程,接口调用才会从“偶发卡死”变成“失败可控、原因可查”。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>