使用google-api-python-client连接Google Drive时随机超时问题求助
我用树莓派做了个定时项目,每小时拍摄照片并上传到Google Drive。用google-api-python-client对接Drive API,apscheduler后台自动执行流程。流程大概80%时间能正常跑,但有20%概率会触发Timeout错误。
我已经给服务对象加了5次重试机制,把超时等待时间调到3分钟,问题还是没解决。现在停了自动化流程,用下面的open()函数手动连接,依然出现相同的超时问题,而且当前网络是正常的。
奇怪的是,流程会连续几小时正常运行,然后突然进入一段超时期,之后又自动恢复正常。我查了Drive API配额,远没到限制。想问问是不是有过期的配置项?或者无意中消耗了什么受限资源?附上相关代码和错误日志,求帮忙排查。
相关代码
import os from google.oauth2 import service_account from googleapiclient.discovery import build from googleapiclient.http import MediaFileUpload from error_logger import ErrorLogger from ssl import SSLCertVerificationError import socket SCOPES = ['https://www.googleapis.com/auth/drive'] SERVICE_ACCOUNT_FILE = "API_KEYS/my_credentials.json" credentials = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES) connected = False timeout_in_sec = 60*3 def open(): try: global connected socket.setdefaulttimeout(timeout_in_sec) service = build('drive', 'v3', credentials=credentials) service.files().list(pageSize=1, fields="files(id, name)").execute() socket.setdefaulttimeout(None) connected = True return service except Exception as e: ErrorLogger.log(e) def close(service): global connected connected = False service.close() service = open() close(service)
错误日志
2025-03-29 15:00:02,425 - my_application - ERROR - timed out Traceback
(most recent call last): File
"/home/pablo/Desktop/estacion_captura/gservice.py", line 38, in open
service.files().list(pageSize=1, fields="files(id, name)").execute() File
"/home/pablo/Desktop/estacion_captura/venvs/estacion_captura2/lib/python3.11/site-packages/googleapiclient/_helpers.py",
line 130, in positional_wrapper
return wrapped(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pablo/Desktop/estacion_captura/venvs/estacion_captura2/lib/python3.11/site-packages/googleapiclient/http.py",
line 923, in execute
resp, content = _retry_request(
^^^^^^^^^^^^^^^ File "/home/pablo/Desktop/estacion_captura/venvs/estacion_captura2/lib/python3.11/site-packages/googleapiclient/http.py",
line 222, in _retry_request
raise exception File "/home/pablo/Desktop/estacion_captura/venvs/estacion_captura2/lib/python3.11/site-packages/googleapiclient/http.py",
line 191, in _retry_request
resp, content = http.request(uri, method, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pablo/Desktop/estacion_captura/venvs/estacion_captura2/lib/python3.11/site-packages/google_auth_httplib2.py",
line 209, in request
self.credentials.before_request(self._request, method, uri, request_headers) File
"/home/pablo/Desktop/estacion_captura/venvs/estacion_captura2/lib/python3.11/site-packages/google/auth/credentials.py",
line 156, in before_request
self.refresh(request) File "/home/pablo/Desktop/estacion_captura/venvs/estacion_captura2/lib/python3.11/site-packages/google/oauth2/service_account.py",
line 438, in refresh
access_token, expiry, _ = _client.jwt_grant(
^^^^^^^^^^^^^^^^^^ File "/home/pablo/Desktop/estacion_captura/venvs/estacion_captura2/lib/python3.11/site-packages/google/oauth2/_client.py",
line 312, in jwt_grant
response_data = _token_endpoint_request(
^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pablo/Desktop/estacion_captura/venvs/estacion_captura2/lib/python3.11/site-packages/google/oauth2/_client.py",
line 272, in _token_endpoint_request
response_status_ok, response_data, retryable_error = _token_endpoint_request_no_throw(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File
"/home/pablo/Desktop/estacion_captura/venvs/estacion_captura2/lib/python3.11/site-packages/google/oauth2/_client.py",
line 219, in _token_endpoint_request_no_throw
request_succeeded, response_data, retryable_error = _perform_request()
^^^^^^^^^^^^^^^^^^ File
"/home/pablo/Desktop/estacion_captura/venvs/estacion_captura2/lib/python3.11/site-packages/google/oauth2/_client.py",
line 195, in _perform_request
response = request(
^^^^^^^^ File "/home/pablo/Desktop/estacion_captura/venvs/estacion_captura2/lib/python3.11/site-packages/google_auth_httplib2.py",
line 119, in call
response, data = self.http.request(
^^^^^^^^^^^^^^^^^^ File "/home/pablo/Desktop/estacion_captura/venvs/estacion_captura2/lib/python3.11/site-packages/httplib2/init.py",
line 1724, in request
(response, content) = self._request(
^^^^^^^^^^^^^^ File "/home/pablo/Desktop/estacion_captura/venvs/estacion_captura2/lib/python3.11/site-packages/httplib2/init.py",
line 1444, in _request
(response, content) = self._conn_request(conn, request_uri, method, body, headers)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File
"/home/pablo/Desktop/estacion_captura/venvs/estacion_captura2/lib/python3.11/site-packages/httplib2/init.py",
line 1366, in _conn_request
conn.connect() File "/home/pablo/Desktop/estacion_captura/venvs/estacion_captura2/lib/python3.11/site-packages/httplib2/init.py",
line 1156, in connect
sock.connect((self.host, self.port)) TimeoutError: timed out
排查方向及解决方案
1. 针对token刷新阶段的超时优化
从错误栈看,超时发生在服务账号获取access token的阶段(jwt_grant调用),而非Drive API请求本身。当前设置的全局socket.setdefaulttimeout未被google-auth库继承,建议给auth请求单独配置超时:
import httplib2 # 替换原credentials初始化后的代码 http = httplib2.Http(timeout=timeout_in_sec) credentials.authorize(http) # 构建service时传入带超时的http客户端 service = build('drive', 'v3', credentials=credentials, http=http)
2. 扩大重试策略覆盖范围
googleapiclient的默认重试只覆盖Drive API请求,token刷新的重试由google-auth库控制,可配置自定义重试逻辑:
from google.auth.transport.requests import Request as AuthRequest from requests.adapters import HTTPAdapter from urllib3.util.retry import Retry # 配置重试策略 retry_strategy = Retry( total=5, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504], allowed_methods=["POST"] # token请求用POST ) adapter = HTTPAdapter(max_retries=retry_strategy) auth_request = AuthRequest() auth_request.mount("https://", adapter) # 刷新token时使用带重试的请求对象 credentials.refresh(auth_request)
3. 树莓派网络层面排查
- DNS解析检查:切换DNS服务器为8.8.8.8,或定期执行
sudo systemd-resolve --flush-caches清空DNS缓存,避免解析异常。 - WiFi稳定性优化:若用WiFi连接,执行
sudo iwconfig wlan0 power off关闭无线网卡电源管理,防止休眠断连;给树莓派分配固定IP,避免DHCP续租时的网络波动。 - 路由器排查:重启家用路由器,排除路由器长时间运行导致的临时丢包问题。
4. 服务账号配置优化
- 更新服务密钥:登录Google Cloud控制台,检查服务账号密钥的创建时间,重新生成新密钥替换现有
my_credentials.json文件。 - 缓存access token:手动指定token缓存路径,减少重复请求token的次数:
from google.oauth2.credentials import Credentials from google.auth.transport.requests import Request import pickle token_path = "token.pickle" if os.path.exists(token_path): with open(token_path, 'rb') as token: credentials = pickle.load(token) if not credentials or not credentials.valid: if credentials and credentials.expired and credentials.refresh_token: credentials.refresh(Request()) else: credentials = service_account.Credentials.from_service_account_file(SERVICE_ACCOUNT_FILE, scopes=SCOPES) with open(token_path, 'wb') as token: pickle.dump(credentials, token)
内容的提问来源于stack exchange,提问作者Pablo Rios

