Python实现Google Photos可恢复上传:为何无法触发续传?
Google Photos可恢复上传的误区分析与修正
核心问题与误区拆解
你的代码存在几个关键的协议理解错误,导致续传逻辑无法触发:
错误使用
upload, finalize命令
初始上传时直接指定X-Goog-Upload-Command: upload, finalize会强制服务尝试一次性完成上传并终结会话。如果上传中途失败(比如SSL连接中断),这个会话可能已经失效,无法再发起续传请求,直接抛出异常跳过了循环判断。未捕获连接类异常
你遇到的SSLEOFError属于requests库的连接异常,会直接中断代码执行,根本不会走到while response.status_code != 200的判断逻辑里,所以续传循环永远不会被触发。偏移量读取错误
查询已上传字节数时,你错误地从原response的headers中取X-Goog-Upload-Size-Received,但这个值应该来自query请求的响应头(query_response.headers)。续传请求方法错误
Google Photos的可恢复上传全程使用POST方法,你续传时用了PUT,不符合协议要求,会导致请求被拒绝。大文件一次性读取风险
直接将整个文件对象传给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
相关产品推荐
相关产品推荐

