Можно ли скачать только часть ZIP-архива (например, один файл)?

Есть ли способ, с помощью которого я могу загрузить только часть файла .rar или .zip без загрузки всего файла?

Существует ZIP-файл, содержащий файлы A, B, C и D. Мне нужен только A. Могу ли я как-то настроить загрузку, чтобы загружать только A или, если возможно, извлечь файл на самом сервере и получить только A?


person user1026134    schedule 17.12.2011    source источник
comment
Несмотря на глупое название, я думаю, что это довольно хороший вопрос. Да, это возможно. Однако требуемый объем работы не является тривиальным... для конечного пользователя это невыполнимо (если только кто-то уже не создал такой инструмент).   -  person    schedule 17.12.2011
comment
Это во многом зависит от вашего протокола передачи - вам, очевидно, нужно использовать протокол, который может передавать диапазоны файлов, а не только полные файлы. Например, если ваш протокол передачи — NFS, то вы можете обнаружить, что стандартные инструменты архивирования прозрачно делают именно это.   -  person Toby Speight    schedule 07.02.2018


Ответы (6)


Хитрость заключается в том, чтобы сделать то, что /8543275#8543275">Серхио предлагает, не делая этого вручную. Это легко сделать, если вы смонтируете ZIP-файл через виртуальную файловую систему с поддержкой HTTP, а затем используете для него стандартную команду unzip. Таким образом, вызовы ввода-вывода утилиты распаковки преобразуются в диапазон GET HTTP, что означает, что по сети передаются только фрагменты ZIP-файла, которые вы хотите.

Вот пример для Linux с использованием HTTPFS, очень легкой виртуальной файловой системы (в ней используется FUSE). Аналогичные инструменты есть и для Windows.

Получить/создать httpfs:

$ wget http://sourceforge.net/projects/httpfs/files/httpfs/1.06.07.02
$ tar -xjf httpfs_1.06.07.10.tar.bz2
$ rm httpfs
$ ./make_httpfs

Смонтируйте удаленный ZIP-файл и извлеките из него один файл:

$ mkdir mount_pt
$ sudo ./httpfs http://server.com/zipfile.zip mount_pt
$ sudo ls mount_pt
zipfile.zip
$ sudo unzip -p mount_pt/zipfile.zip the_file_I_want.txt > the_file_I_want.txt
$ sudo umount mount_pt

Конечно, вы также можете использовать любые другие инструменты помимо командной строки (мне нужно sudo, потому что кажется, FUSE настроен таким образом на моей машине, вам это не нужно).

person Adam    schedule 10.03.2013
comment
Почему вы используете sudo? - person Marian; 18.12.2018

В каком-то смысле да, можете.

формат ZIP-файла говорит о наличии центрального каталог. По сути, это таблица, в которой хранится, какие файлы находятся в архиве и какие у них смещения.

Таким образом, используя Content-Range, вы можете загрузить часть файла из конец (центральный каталог — это последнее, что есть в ZIP-файле) и попытайтесь определить в нем центральный каталог. Если вам это удалось, то вы знаете список файлов и смещения, поэтому вы можете продолжить и получить эти фрагменты по отдельности и распаковать их самостоятельно.

Этот подход довольно подвержен ошибкам и не гарантирует работу. Но взлом вообще :-)

Другой возможный подход — создать для этого собственный сервер (подробнее см. pst answer).

person Sergio Tulentsev    schedule 17.12.2011
comment
Интересно, есть ли библиотека, которая может отображать запросы диапазона содержимого HTTP как своего рода извращенный поток ввода-вывода... :) (На самом деле, это было бы возможно [fsvo], как описано, для ряда языков, которые принимают потоковые входные данные. Хотя не то, к чему хотелось бы прикасаться.) - person ; 17.12.2011
comment
Это не взлом, а способ правильно выполнить задачу. Фактически, HTTP здесь становится просто способом доступа к ZIP-потоку, и любой ZIP-компонент, работающий с потоками, может быть использован для извлечения всего одного файла из удаленного потока. - person Eugene Mayevski 'Callback; 17.12.2011
comment
@EugeneMayevski'EldoSCorp Да, наверное, вы правы, я не так смотрел на это :-) - person Sergio Tulentsev; 17.12.2011

Есть несколько способов, которыми обычный человек может загрузить отдельный файл из сжатого ZIP-файла, но, к сожалению, они не являются общеизвестными. Существуют некоторые инструменты с открытым исходным кодом и онлайн-сервисы, в том числе:

person Shervin Emami    schedule 05.09.2013
comment
Интересно, сработало ли у вас частичное заархивирование. Мне это кажется красивым обещанием, которое ничего мне не дало. - person Jan Vlcinsky; 12.02.2014

Вы можете сделать так, чтобы ваш файл отображался в конце ZIP-файла.

Скачать 100k:

$ curl -r -100000 https://www.keepassx.org/releases/2.0.2/KeePassX-2.0.2.zip -o tail.zip
% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                             Dload  Upload   Total   Spent    Left  Speed
100   97k  100   97k    0     0  84739      0  0:00:01  0:00:01 --:--:-- 84817

Проверяем, какие файлы мы получили:

$ unzip -t tail.zip
  (please check that you have transferred or created the zipfile in the
  appropriate BINARY mode and that you have compiled UnZip properly)
error [tail.zip]:  attempt to seek before beginning of zipfile
  (please check that you have transferred or created the zipfile in the
  appropriate BINARY mode and that you have compiled UnZip properly)
error [tail.zip]:  attempt to seek before beginning of zipfile
  (please check that you have transferred or created the zipfile in the
  appropriate BINARY mode and that you have compiled UnZip properly)
error [tail.zip]:  attempt to seek before beginning of zipfile
  (please check that you have transferred or created the zipfile in the
  appropriate BINARY mode and that you have compiled UnZip properly)
error [tail.zip]:  attempt to seek before beginning of zipfile
  (please check that you have transferred or created the zipfile in the
  appropriate BINARY mode and that you have compiled UnZip properly)
    testing: KeePassX-2.0.2/share/translations/keepassx_uk.qm   OK
    testing: KeePassX-2.0.2/share/translations/keepassx_zh_CN.qm   OK
    testing: KeePassX-2.0.2/share/translations/keepassx_zh_TW.qm   OK
    testing: KeePassX-2.0.2/zlib1.dll   OK
At least one error was detected in tail.zip.

Затем извлеките последний файл:

$ unzip tail.zip KeePassX-2.0.2/zlib1.dll
Archive:  tail.zip
error [tail.zip]:  missing 7751495 bytes in zipfile
  (attempting to process anyway)
  inflating: KeePassX-2.0.2/zlib1.dll
person dodtsair    schedule 11.07.2016

Я думаю Идея Серджио Туленцева гениальна.

Однако, если есть контроль над сервером - например, может быть развернут пользовательский код - тогда это довольно тривиальная операция (по схеме вещей :) для сопоставления/обработки запроса, извлечения соответствующей части ZIP-архива и отправьте данные обратно в потоке HTTP.

Запрос может выглядеть так:

http://foo.bar/myfile.zip_a.jpeg

Это означало бы извлечь и вернуть файл .jpeg из myfile.zip.

(Я намеренно выбрал этот глупый формат, чтобы браузеры, скорее всего, выбрали myfile.zip_a.jpeg в качестве имени в диалоговом окне загрузки, когда оно появится.)

Конечно, как это реализовано, зависит от сервера/языка/фреймворка, и могут уже существовать решения, поддерживающие подобную операцию (но я этого не знаю).

person Community    schedule 17.12.2011

Основываясь на хорошем входе, я написал фрагмент кода в Powershell, чтобы показать, как это может работать:

# demo code downloading a single DLL file from an online ZIP archive
# and extracting the DLL into memory to mount it finally to the main process.

cls
Remove-Variable * -ea 0

# definition for the ZIP archive, the file to be extracted and the checksum:
$url = 'https://github.com/sshnet/SSH.NET/releases/download/2020.0.1/SSH.NET-2020.0.1-bin.zip'
$sub = 'net40/Renci.SshNet.dll'
$md5 = '5B1AF51340F333CD8A49376B13AFCF9C'

# prepare HTTP client:
Add-Type -AssemblyName System.Net.Http
$handler = [System.Net.Http.HttpClientHandler]::new()
$client  = [System.Net.Http.HttpClient]::new($handler)

# get the length of the ZIP archive:
$req = [System.Net.HttpWebRequest]::Create($url)
$req.Method = 'HEAD'
$length = $req.GetResponse().ContentLength
$zip = [byte[]]::new($length)

# get the last 10k:
# how to get the correct length of the central ZIP directory here?
$start = $length-10kb
$end   = $length-1
$client.DefaultRequestHeaders.Add('Range', "bytes=$start-$end")
$result = $client.GetAsync($url).Result
$last10kb = $result.content.ReadAsByteArrayAsync().Result
$last10kb.CopyTo($zip, $start)

# get the block containing the DLL file:
# how to get the exact file-offset from the ZIP directory?
$start = $length-3537kb
$end   = $length-3201kb
$client.DefaultRequestHeaders.Clear()
$client.DefaultRequestHeaders.Add('Range', "bytes=$start-$end")
$result = $client.GetAsync($url).Result
$block = $result.content.ReadAsByteArrayAsync().Result
$block.CopyTo($zip, $start)

# extract the DLL file from archive:
Add-Type -AssemblyName System.IO.Compression
$stream = [System.IO.Memorystream]::new()
$stream.Write($zip,0,$zip.Length)
$archive = [System.IO.Compression.ZipArchive]::new($stream)
$entry = $archive.GetEntry($sub)
$bytes = [byte[]]::new($entry.Length)
[void]$entry.Open().Read($bytes, 0, $bytes.Length)

# check MD5:
$prov = [Security.Cryptography.MD5CryptoServiceProvider]::new().ComputeHash($bytes)
$hash = [string]::Concat($prov.foreach{$_.ToString("x2")})
if ($hash -ne $md5) {write-host 'dll has wrong checksum.' -f y ;break}

# load the DLL:
[void][System.Reflection.Assembly]::Load($bytes)

# use the single demo-call from the DLL:
$test = [Renci.SshNet.NoneAuthenticationMethod]::new('test')
'done.'
person Carsten    schedule 10.04.2021