Tarfile в Python: можно ли распаковать более эффективно, извлекая только некоторые данные?
Я заказываю огромные стопки ландсатов из USGS, которые поступают в архив tar.gz. Я пишу простой скрипт на Python, чтобы распаковать их. Каждый архив содержит 15 изображений формата tiff размером 60-120 МБ, что составляет чуть более 2 ГБ. Я могу легко извлечь весь архив с помощью следующего кода:
import tarfile
fileName = "LT50250232011160-SC20140922132408.tar.gz"
tfile = tarfile.open(fileName, 'r:gz')
tfile.extractall("newfolder/")
На самом деле мне нужны только 6 из этих 15 tiff, обозначенных в названии как "группы". Это некоторые файлы большего размера, поэтому вместе они составляют около половины данных. Итак, я подумал, что мог бы ускорить этот процесс, изменив код следующим образом:
fileName = "LT50250232011160-SC20140922132408.tar.gz"
tfile = tarfile.open(fileName, 'r:gz')
membersList = tfile.getmembers()
namesList = tfile.getnames()
bandsList = [x for x, y in zip(membersList, namesList) if "band" in y]
print("extracting...")
tfile.extractall("newfolder/",members=bandsList)
Однако добавление таймера к обоим сценариям не показывает значительного прироста эффективности второго сценария (в моей системе оба запускаются примерно за минуту на одной сцене). Хотя извлечение происходит несколько быстрее, похоже, что усиление компенсируется временем, которое требуется, чтобы выяснить, какие файлы необходимо извлечь в первую очередь.
Вопрос в том, является ли этот компромисс неотъемлемым в том, что я делаю, или просто результат того, что мой код неэффективен? Я относительно новичок в Python и обнаружил только tarfile сегодня, поэтому меня не удивит, если последний будет правдой, но я не смог найти никаких рекомендаций для эффективного извлечения только части архива.
Спасибо!
2 ответа
Проблема в том, что tar
Файл не имеет центрального списка файлов, но хранит файлы последовательно с заголовком перед каждым файлом. tar
Затем файл сжимается через GZIP, чтобы дать вам tar.gz
, С tar
файл, если вы не хотите извлекать определенный файл, вы просто пропустите следующий header->size
байтов в архиве, а затем прочитать следующий заголовок. Если архив дополнительно сжат, вам все равно придется пропустить такое количество байтов, только не в файле архива, а в потоке распакованных данных - что для некоторых форматов сжатия работает, но для других требуется, чтобы вы распаковывали все промежуточное.
GZIP принадлежит к последнему классу схем сжатия. Таким образом, хотя вы экономите время, не записывая ненужные файлы на диск, ваш код все равно распаковывает их. Вы можете преодолеть эту проблему, переопределив _Stream
класс для не-gzip архивов, но для вашего gz
файлы, с этим ничего не поделаешь.
Вы можете сделать это более эффективно, открыв tarfile как поток.( https://docs.python.org/2/library/tarfile.html)
mkdir tartest
cd tartest/
dd if=/dev/urandom of=file1 count=100 bs=1M
dd if=/dev/urandom of=file2 count=100 bs=1M
dd if=/dev/urandom of=file3 count=100 bs=1M
dd if=/dev/urandom of=file4 count=100 bs=1M
dd if=/dev/urandom of=file5 count=100 bs=1M
cd ..
tar czvf test.tgz tartest
Теперь читайте так:
import tarfile
fileName = "test.tgz"
tfile = tarfile.open(fileName, 'r|gz')
for t in tfile:
if "file3" in t.name:
f = tfile.extractfile(t)
if f:
print(len(f.read()))
Обратите внимание |
в открытой команде. Мы только читаем file3
,
$ time python test.py
104857600
real 0m1.201s
user 0m0.820s
sys 0m0.377s
Если я изменю r|gz
назад к r:gz
Я получил:
$ time python test.py
104857600
real 0m7.033s
user 0m6.293s
sys 0m0.730s
Примерно в 5 раз быстрее (так как у нас есть 5 файлов одинакового размера). Это так, потому что стандартный способ открытия позволяет искать в обратном направлении; он может сделать это только в сжатом tarfile, извлекая (я не знаю точную причину этого). Если вы открываете как поток, вы больше не можете искать случайно, но если вы читаете последовательно, что возможно в вашем случае, это происходит намного быстрее. Однако вы не можете getnames
больше заранее. Но это не обязательно в этом случае.