You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python实现Google Photos可恢复上传:为何无法触发续传?

Google Photos可恢复上传的误区分析与修正

核心问题与误区拆解

你的代码存在几个关键的协议理解错误,导致续传逻辑无法触发:

  1. 错误使用upload, finalize命令
    初始上传时直接指定X-Goog-Upload-Command: upload, finalize会强制服务尝试一次性完成上传并终结会话。如果上传中途失败(比如SSL连接中断),这个会话可能已经失效,无法再发起续传请求,直接抛出异常跳过了循环判断。

  2. 未捕获连接类异常
    你遇到的SSLEOFError属于requests库的连接异常,会直接中断代码执行,根本不会走到while response.status_code != 200的判断逻辑里,所以续传循环永远不会被触发。

  3. 偏移量读取错误
    查询已上传字节数时,你错误地从原response的headers中取X-Goog-Upload-Size-Received,但这个值应该来自query请求的响应头(query_response.headers)。

  4. 续传请求方法错误
    Google Photos的可恢复上传全程使用POST方法,你续传时用了PUT,不符合协议要求,会导致请求被拒绝。

  5. 大文件一次性读取风险
    直接将整个文件对象传给data=f会一次性加载大文件到内存,不仅占用资源,也会导致中断后无法精准定位续传位置,应该分块读取上传。


修正后的代码示例

import requests
from requests.exceptions import RequestException

def upload_to_photos(creds, file_path, content_type, file_size):
    # 创建可恢复上传会话
    headers = {
        'Authorization': f'Bearer {creds.token}',
        'X-Goog-Upload-Content-Type': content_type,
        'X-Goog-Upload-Protocol': 'resumable',
        'X-Goog-Upload-Command': 'start',
        'X-Goog-Upload-Raw-Size': str(file_size)
    }
    response = requests.post('https://photoslibrary.googleapis.com/v1/uploads', headers=headers)
    session_url = response.headers['X-Goog-Upload-URL']
    print("创建上传会话成功")

    offset = 0
    chunk_size = 10 * 1024 * 1024  # 10MB分块,可根据网络调整

    with open(file_path, 'rb') as f:
        f.seek(offset)
        while True:
            try:
                # 读取分块数据
                chunk = f.read(chunk_size)
                if not chunk:
                    # 所有数据上传完成,发起finalize命令
                    finalize_headers = {
                        'Authorization': f'Bearer {creds.token}',
                        'X-Goog-Upload-Command': 'finalize',
                        'X-Goog-Upload-Offset': str(offset)
                    }
                    response = requests.post(session_url, headers=finalize_headers)
                    if response.status_code == 200:
                        print("上传完成")
                        return response.text
                    else:
                        raise RuntimeError(f"终结会话失败,状态码: {response.status_code}")

                # 分块上传,仅用upload命令
                upload_headers = {
                    'Authorization': f'Bearer {creds.token}',
                    'Content-Length': str(len(chunk)),
                    'X-Goog-Upload-Content-Type': content_type,
                    'X-Goog-Upload-Command': 'upload',
                    'X-Goog-Upload-Offset': str(offset)
                }
                response = requests.post(session_url, data=chunk, headers=upload_headers)

                # 处理响应状态:308表示需要续传,200表示部分/全部完成
                if response.status_code == 308:
                    # 更新已上传偏移量
                    offset = int(response.headers['X-Goog-Upload-Size-Received'])
                    print(f"已上传{offset}/{file_size}字节,继续续传")
                    f.seek(offset)
                elif response.status_code == 200:
                    print("上传完成")
                    return response.text
                else:
                    raise RuntimeError(f"上传失败,状态码: {response.status_code}")

            except RequestException as e:
                # 捕获连接异常,发起query请求获取当前偏移量
                print(f"连接异常: {str(e)},查询当前上传进度")
                query_headers = {
                    'Authorization': f'Bearer {creds.token}',
                    'Content-Length': '0',
                    'X-Goog-Upload-Command': 'query'
                }
                query_response = requests.post(session_url, headers=query_headers)
                if query_response.status_code == 200:
                    offset = int(query_response.headers['X-Goog-Upload-Size-Received'])
                    print(f"查询到已上传{offset}字节,准备续传")
                    f.seek(offset)
                else:
                    raise RuntimeError(f"查询进度失败,状态码: {query_response.status_code}")

关键修正点说明

  • 分块上传:用10MB分块避免内存过载,同时符合Google推荐的分块大小
  • 拆分upload和finalize命令:先分块上传所有数据,最后再终结会话
  • 捕获连接异常:遇到SSL、超时等错误时,先查询当前进度再续传
  • 正确处理308状态码:这是Google可恢复协议中表示"需要续传"的标准状态码,原代码没处理这个状态
  • 偏移量正确读取:从query请求的响应头获取已上传字节数

内容的提问来源于stack exchange,提问作者HAK

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 22:42:51