![](/img/trans.png)
[英]Is there a special trick to downloading a zip file and writing it to disk with Python?
[英]Downloading and unzipping a .zip file without writing to disk
我已经设法让我的第一个 python 脚本工作,它从 URL 下载 .ZIP 文件列表,然后继续提取 ZIP 文件并将它们写入磁盘。
我现在不知所措,无法实现下一步。
我的主要目标是下载并解压缩 zip 文件并通过 TCP 流传递内容(CSV 数据)。 如果可以的话,我不希望将任何 zip 或提取的文件实际写入磁盘。
这是我当前的脚本,它可以工作,但不幸的是必须将文件写入磁盘。
import urllib, urllister
import zipfile
import urllib2
import os
import time
import pickle
# check for extraction directories existence
if not os.path.isdir('downloaded'):
os.makedirs('downloaded')
if not os.path.isdir('extracted'):
os.makedirs('extracted')
# open logfile for downloaded data and save to local variable
if os.path.isfile('downloaded.pickle'):
downloadedLog = pickle.load(open('downloaded.pickle'))
else:
downloadedLog = {'key':'value'}
# remove entries older than 5 days (to maintain speed)
# path of zip files
zipFileURL = "http://www.thewebserver.com/that/contains/a/directory/of/zip/files"
# retrieve list of URLs from the webservers
usock = urllib.urlopen(zipFileURL)
parser = urllister.URLLister()
parser.feed(usock.read())
usock.close()
parser.close()
# only parse urls
for url in parser.urls:
if "PUBLIC_P5MIN" in url:
# download the file
downloadURL = zipFileURL + url
outputFilename = "downloaded/" + url
# check if file already exists on disk
if url in downloadedLog or os.path.isfile(outputFilename):
print "Skipping " + downloadURL
continue
print "Downloading ",downloadURL
response = urllib2.urlopen(downloadURL)
zippedData = response.read()
# save data to disk
print "Saving to ",outputFilename
output = open(outputFilename,'wb')
output.write(zippedData)
output.close()
# extract the data
zfobj = zipfile.ZipFile(outputFilename)
for name in zfobj.namelist():
uncompressed = zfobj.read(name)
# save uncompressed data to disk
outputFilename = "extracted/" + name
print "Saving extracted file to ",outputFilename
output = open(outputFilename,'wb')
output.write(uncompressed)
output.close()
# send data via tcp stream
# file successfully downloaded and extracted store into local log and filesystem log
downloadedLog[url] = time.time();
pickle.dump(downloadedLog, open('downloaded.pickle', "wb" ))
下面是我用来获取压缩的 csv 文件的代码片段,请看:
蟒蛇2 :
from StringIO import StringIO
from zipfile import ZipFile
from urllib import urlopen
resp = urlopen("http://www.test.com/file.zip")
zipfile = ZipFile(StringIO(resp.read()))
for line in zipfile.open(file).readlines():
print line
蟒蛇3 :
from io import BytesIO
from zipfile import ZipFile
from urllib.request import urlopen
# or: requests.get(url).content
resp = urlopen("http://www.test.com/file.zip")
zipfile = ZipFile(BytesIO(resp.read()))
for line in zipfile.open(file).readlines():
print(line.decode('utf-8'))
这里的file
是一个字符串。 要获取要传递的实际字符串,可以使用zipfile.namelist()
。 例如,
resp = urlopen('http://mlg.ucd.ie/files/datasets/bbc.zip')
zipfile = ZipFile(BytesIO(resp.read()))
zipfile.namelist()
# ['bbc.classes', 'bbc.docs', 'bbc.mtx', 'bbc.terms']
我的建议是使用StringIO
对象。 它们模拟文件,但驻留在内存中。 所以你可以做这样的事情:
# get_zip_data() gets a zip archive containing 'foo.txt', reading 'hey, foo'
import zipfile
from StringIO import StringIO
zipdata = StringIO()
zipdata.write(get_zip_data())
myzipfile = zipfile.ZipFile(zipdata)
foofile = myzipfile.open('foo.txt')
print foofile.read()
# output: "hey, foo"
或者更简单(向 Vishal 道歉):
myzipfile = zipfile.ZipFile(StringIO(get_zip_data()))
for name in myzipfile.namelist():
[ ... ]
在 Python 3 中使用 BytesIO 而不是 StringIO:
import zipfile
from io import BytesIO
filebytes = BytesIO(get_zip_data())
myzipfile = zipfile.ZipFile(filebytes)
for name in myzipfile.namelist():
[ ... ]
我想提供 Vishal 使用 Python 2 的优秀答案的更新 Python 3 版本,以及对可能已经提到的改编/更改的一些解释。
from io import BytesIO
from zipfile import ZipFile
import urllib.request
url = urllib.request.urlopen("http://www.unece.org/fileadmin/DAM/cefact/locode/loc162txt.zip")
with ZipFile(BytesIO(url.read())) as my_zip_file:
for contained_file in my_zip_file.namelist():
# with open(("unzipped_and_read_" + contained_file + ".file"), "wb") as output:
for line in my_zip_file.open(contained_file).readlines():
print(line)
# output.write(line)
必要的改变:
StringIO
模块(它已移至io.StringIO
)。 相反,我使用io.BytesIO
] 2 ,因为我们将处理一个字节流-文档,也该线程。urllib.urlopen
函数已停止使用; urllib.request.urlopen()
对应于旧的urllib2.urlopen
。”, 文档和此线程。笔记:
b'some text'
。 这是意料之中的,因为它们不是字符串——记住,我们正在读取一个字节流。 看看Dan04 的优秀答案。我做了一些小改动:
with ... as
而不是zipfile = ...
。.namelist()
循环浏览 zip 中的所有文件并打印它们的内容。ZipFile
对象的创建移到with
语句中,尽管我不确定这是否更好。"unzipped_and_read_"
添加到文件名的开头和".file"
扩展名(我不喜欢对带有字节串的文件使用".txt"
)。 如果您想使用它,当然需要调整代码的缩进。
"wb"
; 我有一种感觉,无论如何编写二进制文件都会打开一罐蠕虫......我没有做的:
这里有一个方法:
import urllib.request
import shutil
with urllib.request.urlopen("http://www.unece.org/fileadmin/DAM/cefact/locode/2015-2_UNLOCODE_SecretariatNotes.pdf") as response, open("downloaded_file.pdf", 'w') as out_file:
shutil.copyfileobj(response, out_file)
写入驻留在 RAM 中的临时文件
事实证明, tempfile
模块( http://docs.python.org/library/tempfile.html )就是这样:
tempfile.SpooledTemporaryFile([max_size=0[, mode='w+b'[, bufsize=-1[, suffix=''[, prefix='tmp'[, dir=None]]]]]])
该函数的操作与 TemporaryFile() 完全一样,除了数据在内存中假脱机直到文件大小超过 max_size,或者直到调用文件的 fileno() 方法,此时内容被写入磁盘并且操作像 TemporaryFile 一样继续()。
生成的文件有一个额外的方法,rollover(),它会导致文件滚动到磁盘上的文件,而不管其大小。
返回的对象是一个类文件对象,其 _file 属性是 StringIO 对象或真正的文件对象,具体取决于是否调用了 rollover()。 这个类文件对象可以在 with 语句中使用,就像普通文件一样。
2.6 版中的新功能。
或者,如果你很懒,并且在 Linux 上有一个 tmpfs 挂载的/tmp
,你可以在那里创建一个文件,但你必须自己删除它并处理命名
为了完整起见,我想添加我的 Python3 答案:
from io import BytesIO
from zipfile import ZipFile
import requests
def get_zip(file_url):
url = requests.get(file_url)
zipfile = ZipFile(BytesIO(url.content))
zip_names = zipfile.namelist()
if len(zip_names) == 1:
file_name = zip_names.pop()
extracted_file = zipfile.open(file_name)
return extracted_file
return [zipfile.open(file_name) for file_name in zip_names]
使用requests添加其他答案:
# download from web
import requests
url = 'http://mlg.ucd.ie/files/datasets/bbc.zip'
content = requests.get(url)
# unzip the content
from io import BytesIO
from zipfile import ZipFile
f = ZipFile(BytesIO(content.content))
print(f.namelist())
# outputs ['bbc.classes', 'bbc.docs', 'bbc.mtx', 'bbc.terms']
使用help(f)获取更多函数详细信息,例如extractall()提取 zip 文件中的内容,稍后可以与 open一起使用。
所有这些答案都显得过于庞大和冗长。 使用请求来缩短代码,例如:
import requests, zipfile, io
r = requests.get(zip_file_url)
z = zipfile.ZipFile(io.BytesIO(r.content))
z.extractall("/path/to/directory")
Vishal 的例子,无论多么伟大,在涉及到文件名时都会让人感到困惑,而且我看不出重新定义“zipfile”的好处。
这是我下载包含一些文件的 zip 文件的示例,其中一个文件是一个 csv 文件,我随后将其读入了 Pandas DataFrame:
from StringIO import StringIO
from zipfile import ZipFile
from urllib import urlopen
import pandas
url = urlopen("https://www.federalreserve.gov/apps/mdrm/pdf/MDRM.zip")
zf = ZipFile(StringIO(url.read()))
for item in zf.namelist():
print("File in zip: "+ item)
# find the first matching csv file in the zip:
match = [s for s in zf.namelist() if ".csv" in s][0]
# the first line of the file contains a string - that line shall de ignored, hence skiprows
df = pandas.read_csv(zf.open(match), low_memory=False, skiprows=[0])
(注意,我使用 Python 2.7.13)
这是对我有用的确切解决方案。 我只是通过删除 StringIO 并添加 IO 库为 Python 3 版本稍微调整了它
from io import BytesIO
from zipfile import ZipFile
import pandas
import requests
url = "https://www.nseindia.com/content/indices/mcwb_jun19.zip"
content = requests.get(url)
zf = ZipFile(BytesIO(content.content))
for item in zf.namelist():
print("File in zip: "+ item)
# find the first matching csv file in the zip:
match = [s for s in zf.namelist() if ".csv" in s][0]
# the first line of the file contains a string - that line shall de ignored, hence skiprows
df = pandas.read_csv(zf.open(match), low_memory=False, skiprows=[0])
在 Vishal 的回答中,在磁盘上没有文件的情况下文件名应该是什么并不明显。 我已经修改了他的答案,无需修改即可满足大多数需求。
from StringIO import StringIO
from zipfile import ZipFile
from urllib import urlopen
def unzip_string(zipped_string):
unzipped_string = ''
zipfile = ZipFile(StringIO(zipped_string))
for name in zipfile.namelist():
unzipped_string += zipfile.open(name).read()
return unzipped_string
使用zipfile
模块。 要从 URL 中提取文件,您需要将urlopen
调用的结果包装在BytesIO
对象中。 这是因为urlopen
返回的 Web 请求的结果不支持搜索:
from urllib.request import urlopen
from io import BytesIO
from zipfile import ZipFile
zip_url = 'http://example.com/my_file.zip'
with urlopen(zip_url) as f:
with BytesIO(f.read()) as b, ZipFile(b) as myzipfile:
foofile = myzipfile.open('foo.txt')
print(foofile.read())
如果您已经在本地下载了文件,则不需要BytesIO
,只需以二进制模式打开并直接传递给ZipFile
即可:
from zipfile import ZipFile
zip_filename = 'my_file.zip'
with open(zip_filename, 'rb') as f:
with ZipFile(f) as myzipfile:
foofile = myzipfile.open('foo.txt')
print(foofile.read().decode('utf-8'))
再次注意,您必须以二进制 ( 'rb'
) 模式open
文件,而不是以文本形式open
,否则您将收到zipfile.BadZipFile: File is not a zip file
错误。
将所有这些东西用作with
语句的上下文管理器是一种很好的做法,以便它们可以正确关闭。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.