Python:无需写入磁盘即可下载和解压缩.zip文件

我设法让我的第一个Python脚本工作,从URL下载一个.ZIP文件列表,然后继续提取ZIP文件并将它们写入磁盘。

我现在无法实现下一步。

我的主要目标是下载和解压zip文件并通过TCPstream传递内容(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" )) 

任何帮助越过下一步将不胜感激。

我的build议是使用一个StringIO对象。 他们模拟文件,但驻留在内存中。 所以你可以做这样的事情:

 # get_zip_data() gets a zip archive containing 'foo.txt', reading 'hey, foo' 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" 

或更简单地(向维萨尔道歉):

 myzipfile = zipfile.Zipfile(StringIO(get_zip_data())) for name in myzipfile.namelist(): [ ... ] 

下面是我用来获取压缩的csv文件的代码片段,请看看:

 from StringIO import StringIO from zipfile import ZipFile from urllib import urlopen url = urlopen("http://www.test.com/file.zip") zipfile = ZipFile(StringIO(url.read())) for line in zipfile.open(file).readlines(): print line 

写入驻留在RAM中的临时文件

事实certificatetempfile模块( http://docs.python.org/library/tempfile.html )只是事情:

tempfile.SpooledTemporaryFile([max_size = 0 [,mode ='w + b'[,bufsize = -1 [,suffix =''[,prefix ='tmp'[,dir = None]]]]]])

这个函数的操作与TemporaryFile()完全一样,不同之处在于数据在内存中被caching,直到文件大小超过max_size,或者调用文件的fileno()方法为止,此时内容被写入磁盘并且操作与TemporaryFile ()。

生成的文件有一个额外的方法,rollover(),这会导致文件无论大小如何都会转到磁盘上的文件。

返回的对象是一个类似文件的对象,它的_file属性是一个StringIO对象或一个真正的文件对象,具体取决于是否调用了rollover()。 这个类文件对象可以像普通文件一样在with语句中使用。

2.6版本中的新function

或者如果你懒惰,并且你在Linux上有一个tmpfs挂载的/tmp ,你可以在那里创build一个文件,但是你必须自己删除它并且处理命名

我想提供一个更新的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 ,并从中导入BytesIO ,因为我们将处理一个字节stream – Docs ,也是这个线程 。
  • 的urlopen:
    • “Python 2.6及更早版本的遗留urllib.urlopen函数已经停用; urllib.request.urlopen()对应于旧的urllib2.urlopen。”, Docs 。
  • 导入urllib.request:
    • 这个线程 。

注意:

  • 在Python 3中,打印的输出行将如下所示: b'some text' 。 这是预期的,因为它们不是string – 请记住,我们正在阅读一个字节stream。 看看Dan04的优秀答案 。

我做了一些小的修改:

  • 我使用with ... as而不是zipfile = ...根据文档 。
  • 该脚本现在使用namelist()来遍历zip中的所有文件并打印其内容。
  • 我把ZipFile对象的创build移到了with-statement中,尽pipe我不确定这是否更好。
  • 为了响应NumenorForLife的注释,我添加了(并注释掉了)一个将字节stream写入文件(压缩文件中的每个文件)的选项; 它将"unzipped_and_read_"添加到文件名的开始处和一个".file"扩展名(我不希望将".txt"用于具有字节串的文件)。 当然,如果要使用代码,缩进代码当然需要调整。
    • 在这里需要小心 – 因为我们有一个字节string,我们使用二进制模式,所以"wb" ; 我有一种感觉,编写二进制文件打开一jar蠕虫…
  • 我正在使用一个示例文件, UN / LOCODE文本存档 :

我没有做的事情:

  • NumenorForLife询问有关将zip保存到磁盘的问题。 我不确定他是什么意思 – 下载zip文件? 这是一个不同的任务; 见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) 

我想添加我的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 

在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 

Vishal的例子,无论多么伟大,都涉及到文件名的混淆,我看不到重新编写“zipfile”的好处。

这里是我的例子,下载一个包含一些文件的zip文件,其中之一是一个csv文件,我随后读入一个pandas DataFrame:

 from StringIO import StringIO from zipfile import ZipFile from urllib import urlopen import pandas url = urlopen("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)