在不写入磁盘的情况下下载和解压.zip文件
我已经成功让我的第一个Python脚本运行起来,它可以从一个网址下载一系列的.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" ))
10 个回答
34
我想提供一个更新版的Python 3代码,基于Vishal的优秀回答,原回答是用Python 2写的,同时我会解释一些适应和变化的地方,这些可能已经提到过。
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)
必要的改动:
- 在Python 3中,没有
StringIO
模块(它被移到了io.StringIO
)。相反,我使用io.BytesIO
,因为我们要处理的是字节流——文档,还有这个讨论。 - urlopen:
- Python 2.6及更早版本的遗留函数
urllib.urlopen
已经被淘汰;urllib.request.urlopen()
对应于旧的urllib2.urlopen
。文档和这个讨论。
- Python 2.6及更早版本的遗留函数
注意:
- 在Python 3中,打印输出的行会像这样显示:
b'some text'
。这是正常的,因为它们不是字符串——记住,我们在读取的是字节流。可以看看Dan04的优秀回答。
我做的一些小改动:
- 我使用
with ... as
代替zipfile = ...
,这是根据文档的建议。 - 现在脚本使用
.namelist()
来遍历压缩文件中的所有文件并打印它们的内容。 - 我把创建
ZipFile
对象的代码放到了with
语句中,虽然我不确定这样是否更好。 - 我添加了一个选项(并注释掉了),可以将字节流写入文件(每个压缩文件中的文件),这是响应NumenorForLife的评论;它会在文件名开头加上
"unzipped_and_read_"
,并加上".file"
扩展名(我更喜欢不使用".txt"
来处理字节字符串的文件)。如果你想使用这个功能,代码的缩进当然需要调整。- 这里需要小心——因为我们有一个字节字符串,所以使用二进制模式,即
"wb"
;我感觉写入二进制文件会引发一系列问题……
- 这里需要小心——因为我们有一个字节字符串,所以使用二进制模式,即
- 我使用了一个示例文件,即联合国/LOCODE文本档案:
我没有做的事情:
- NumenorForLife询问关于将压缩文件保存到磁盘的事。我不太确定他指的是什么——是下载压缩文件吗?那是另一个任务;可以参考Oleh Prypin的优秀回答。
这里有一种方法:
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)
114
下面是我用来获取压缩的csv文件的代码片段,请看一下:
Python 2:
from StringIO import StringIO
from zipfile import ZipFile
from urllib import urlopen
resp = urlopen("http://www.test.com/file.zip")
myzip = ZipFile(StringIO(resp.read()))
for line in myzip.open(file).readlines():
print line
Python 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")
myzip = ZipFile(BytesIO(resp.read()))
for line in myzip.open(file).readlines():
print(line.decode('utf-8'))
这里的 file
是一个字符串。要获取你想要传递的实际字符串,可以使用 zipfile.namelist()
。比如说,
resp = urlopen('http://mlg.ucd.ie/files/datasets/bbc.zip')
myzip = ZipFile(BytesIO(resp.read()))
myzip.namelist()
# ['bbc.classes', 'bbc.docs', 'bbc.mtx', 'bbc.terms']
79
我建议你使用一个 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():
[ ... ]