如何使用Python3.6的tarfile模块从内存读取文件?

7

我想要从url下载一个tar文件到内存中,然后将它的所有内容提取到dst文件夹中。我该怎么做?以下是我的尝试,但没有成功实现。

#!/usr/bin/python3.6
# -*- coding: utf-8 -*-

from pathlib import Path
from io import BytesIO
from urllib.request import Request, urlopen
from urllib.error import URLError
from tarfile import TarFile


def get_url_response( url ):
    req = Request( url )
    try:
        response = urlopen( req )
    except URLError as e:
        if hasattr( e, 'reason' ):
            print( 'We failed to reach a server.' )
            print( 'Reason: ', e.reason )
        elif hasattr( e, 'code'):
            print( 'The server couldn\'t fulfill the request.' )
            print( 'Error code: ', e.code )
    else:
        # everything is fine
        return response

url = 'https://dl.opendesktop.org/api/files/download/id/1566630595/s/6cf6f74c4016e9b83f062dbb89092a0dfee862472300cebd0125c7a99463b78f4b912b3aaeb23adde33ea796ca9232decdde45bb65a8605bfd8abd05eaee37af/t/1567158438/c/6cf6f74c4016e9b83f062dbb89092a0dfee862472300cebd0125c7a99463b78f4b912b3aaeb23adde33ea796ca9232decdde45bb65a8605bfd8abd05eaee37af/lt/download/Blue-Maia.tar.xz'
dst = Path().cwd() / 'Tar'

response = get_url_response( url )

with TarFile( BytesIO( response.read() ) ) as tfile:
    tfile.extractall( path=dst )

然而,我遇到了这个错误:
Traceback (most recent call last):
  File "~/test_tar.py", line 31, in <module>
    with TarFile( BytesIO( response.read() ) ) as tfile:
  File "/usr/lib/python3.6/tarfile.py", line 1434, in __init__
    fileobj = bltn_open(name, self._mode)
TypeError: expected str, bytes or os.PathLike object, not _io.BytesIO

我尝试将 BytesIO 对象作为 fileobj 传递给 TarFile
with TarFile( fileobj=BytesIO( response.read() ) ) as tfile:
    tfile.extractall( path=dst )

然而,它仍无法正常工作:

Traceback (most recent call last):
  File "/usr/lib/python3.6/tarfile.py", line 188, in nti
    s = nts(s, "ascii", "strict")
  File "/usr/lib/python3.6/tarfile.py", line 172, in nts
    return s.decode(encoding, errors)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xd2 in position 0: ordinal not in range(128)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.6/tarfile.py", line 2297, in next
    tarinfo = self.tarinfo.fromtarfile(self)
  File "/usr/lib/python3.6/tarfile.py", line 1093, in fromtarfile
    obj = cls.frombuf(buf, tarfile.encoding, tarfile.errors)
  File "/usr/lib/python3.6/tarfile.py", line 1035, in frombuf
    chksum = nti(buf[148:156])
  File "/usr/lib/python3.6/tarfile.py", line 191, in nti
    raise InvalidHeaderError("invalid header")
tarfile.InvalidHeaderError: invalid header

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "~/test_tar.py", line 31, in <module>
    with TarFile( fileobj=BytesIO( response.read() ) ) as tfile:
  File "/usr/lib/python3.6/tarfile.py", line 1482, in __init__
    self.firstmember = self.next()
  File "/usr/lib/python3.6/tarfile.py", line 2309, in next
    raise ReadError(str(e))
tarfile.ReadError: invalid header
2个回答

4

这种方法非常接近正确:

with TarFile( fileobj=BytesIO( response.read() ) ) as tfile:
    tfile.extractall( path=dst )

你应该使用tarfile.open而不是TarFile(请参见文档),并告诉它你正在读取一个xz文件(mode='r:xz'):
with tarfile.open( fileobj=BytesIO( response.read() ), mode='r:xz' ) as tfile:
    tfile.extractall( path=dst )

然而,正如您所注意到的那样,这还不够。

根本问题是什么?您正在从禁止热链接的网站下载。该网站正在阻止您的下载尝试。尝试打印响应,您将看到您获取了一堆垃圾HTML而不是tar.xz文件。


我使用了另一个.tar.xz类型的URL进行下载。是的,使用tarfile.open()函数可以工作。谢谢您提供的参考,我之前忽略了它。有没有什么机会或方法来规避热链接? - Sun Bear

2
奇怪的是,我使用open()函数可以使其正常工作,但是使用TarFile对象实例化则无法正确设置打开模式。无论如何,下面这个方法可以工作:
from _io import BytesIO
import tarfile

with open('Blue-Maia.tar.xz', 'rb') as f:
    tar = tarfile.open(fileobj=BytesIO( f.read() ), mode="r:xz")
    tar.extractall( path="test" )
    tar.close()

你可以添加一个try...except...finally来确保tar文件始终被关闭。
更新:
在你的代码中:
response = get_url_response( url )
tar = tarfile.open(fileobj=BytesIO( response.read() ), mode="r:xz")
tar.extractall( path="test" )
tar.close()

哦,抱歉,我尝试了几个方法,但在发布解决方案时犯了一个错误...已修复。 - olinox14
with open 只是用来替换您使用 get_url_response 方法获取的文件对象,您需要的是最后三行。 - olinox14
你能展示一下你的代码如何与我代码中的“response”链接吗?我在你的脚本中没看到它。语句 with open('Blue-Maia.tar.xz', 'rb') as f 的意思是你正在打开一个名为“Blue-Maia.tar.xz”的文件,该文件预先存在于你当前的工作目录中,并将这个已打开的文件赋值给f - Sun Bear
通过您更新的代码,我得到了 tarfile.ReadError: not an lzma file - Sun Bear
1
感谢您的帮助。您更新后的答案最终看起来与@Score_Under的答案相似。我接受了那个答案,因为它首先解释了我的错误,并展示了我应该使用的正确语法,这回答了我的问题。;) - Sun Bear
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接