如何使用urllib2通过单次HTTP请求批量下载文件而非逐个请求?
如何使用urllib2通过单次HTTP请求批量下载文件而非逐个请求?
问题背景
我用Tornado搭建后端服务,客户端基于Jython 2.5.2(只能用urllib2)。之前实现了一个批量下载文本文件的功能:后端把所有文本文件的文件名和内容用分隔符拼接后一次性返回,客户端再拆分还原成文件,效果很好。但加入非文本文件后就失效了。后来改成每个文件单独发请求下载,虽然能工作但速度特别慢。想找一种既能批量传输又支持非文本文件的方法。
原后端代码(仅支持文本):
class DownloadHandler(web.RequestHandler): def get(self): files_as_text = "" for file in os.listdir("files"): files_as_text += file+"---title_split---"+open("files/"+file).read()+"---file_split---" self.write(files_as_text)
原客户端代码:
for uptodate_file in uptodate_files.split("---file_split---")[:-1]: uptodate_filename, uptodate_file_contents = uptodate_file.split("---title_split---") new_file = open(dir+uptodate_filename, 'wb') new_file.write(uptodate_file_contents).read()) new_file.close()
改用单个请求下载的客户端代码(慢但有效):
urllib2.urlopen(url+uptodate_filename).read()
解决方案
针对你的需求,有两种实用方案,既保留批量传输的高效性,又能兼容所有文件类型:
方案1:Base64编码统一传输(简单易实现)
核心思路是把所有文件(文本+非文本)的内容转成Base64编码的字符串——Base64会把二进制数据转换成纯ASCII字符,这样就能安全地用分隔符拼接,不会破坏原始数据。
后端(Tornado)修改代码:
import base64 import os from tornado import web class DownloadHandler(web.RequestHandler): def get(self): files_data = [] for file_name in os.listdir("files"): file_path = os.path.join("files", file_name) # 二进制读取文件并转Base64(转成字符串方便拼接) with open(file_path, "rb") as f: file_content = base64.b64encode(f.read()).decode("utf-8") # 拼接文件名和编码后的内容 files_data.append(f"{file_name}---title_split---{file_content}") # 用分隔符连接所有文件数据 self.write("---file_split---".join(files_data))
客户端(Jython)修改代码:
import urllib2 import base64 import os target_dir = "./your_target_dir/" # 替换成你的目标目录 response = urllib2.urlopen(your_download_url).read() for file_block in response.split("---file_split---")[:-1]: filename, encoded_content = file_block.split("---title_split---") # 解码Base64并写入文件 with open(os.path.join(target_dir, filename), "wb") as new_file: new_file.write(base64.b64decode(encoded_content))
方案2:自定义二进制包格式(传输效率更高)
如果文件体积大、数量多,Base64的33%体积开销可能会影响速度,这时可以用自定义二进制结构打包所有文件,直接传输二进制数据,没有额外体积损耗。
后端(Tornado)修改代码:
import os import struct from tornado import web class DownloadHandler(web.RequestHandler): def get(self): # 初始化二进制数据缓冲区 batch_data = b"" for file_name in os.listdir("files"): file_path = os.path.join("files", file_name) with open(file_path, "rb") as f: file_content = f.read() # 按「文件名长度(4字节大端整数) + 文件名 + 文件内容长度(4字节大端整数) + 文件内容」的格式打包 filename_bytes = file_name.encode("utf-8") batch_data += struct.pack(">I", len(filename_bytes)) # 大端字节序保证跨平台兼容 batch_data += filename_bytes batch_data += struct.pack(">I", len(file_content)) batch_data += file_content # 设置响应头为二进制流 self.set_header("Content-Type", "application/octet-stream") self.write(batch_data)
客户端(Jython)修改代码:
import urllib2 import struct import os target_dir = "./your_target_dir/" response_data = urllib2.urlopen(your_download_url).read() offset = 0 while offset < len(response_data): # 读取文件名长度(4字节) filename_len = struct.unpack(">I", response_data[offset:offset+4])[0] offset += 4 # 读取文件名 filename = response_data[offset:offset+filename_len].decode("utf-8") offset += filename_len # 读取文件内容长度(4字节) content_len = struct.unpack(">I", response_data[offset:offset+4])[0] offset += 4 # 读取文件内容 file_content = response_data[offset:offset+content_len] offset += content_len # 写入本地文件 with open(os.path.join(target_dir, filename), "wb") as new_file: new_file.write(file_content)
方案选择建议
- 如果追求实现简单、代码改动小,选Base64方案,适合文件数量不多、体积不大的场景;
- 如果追求极致传输效率,避免Base64的体积开销,选二进制打包方案,适合大文件或大量文件的场景。
备注:内容来源于stack exchange,提问作者Curtis C
相关产品推荐
相关产品推荐

