.NET GZipStream 解压缩纯文本的前 548 个字节,即文件中的所有第一条记录。 7Zip 将整个文件提取为一个 1.2GB 的输出文件,但它是纯文本(大约 130 万行),没有记录分隔符,当我在 7Zip 中测试该文件时,它报告了 1,441 个字节。
我检查了一些东西,但找不到一个可以直接解压这个东西的压缩库。
在文件中进行了一些转换后,我发现 1,441 字节是 ISIZE 的值,这通常是 gzip 文件的最后 4 个字节,是附加到压缩文件的 8 字节页脚记录的一部分数据块。
事实证明,您拥有的是一大组串联在一起的 .gz 文件。虽然这完全是一件令人头疼的事情,但有几种方法可以解决这个问题。
首先是扫描压缩文件中的 gzip 标头签名字节:0x1F 和 0x8B。当您找到这些时,您将(通常)在流中拥有每个 .gz 文件的开头。您可以在文件中构建一个偏移量列表,然后提取文件的每个块并解压缩。
另一种选择是使用一个库,该库将报告从输入流中消耗的字节数。由于几乎所有的解压器都使用某种缓冲,你会发现输入流的移动比消耗的字节数要远得多,因此很难直接猜测。但是,DotNetZip 流将为您提供实际消耗的输入字节,您可以使用它来计算下一个起始位置。这将允许您将文件作为流处理并单独提取每个文件。
不管怎样,都不快。
这是第二个选项的方法,使用 DotNetZip 库:
public static IEnumerable<byte[]> UnpackCompositeFile(string filename)
{
using (var fstream = File.OpenRead(filename))
{
long offset = 0;
while (offset < fstream.Length)
{
fstream.Position = offset;
byte[] bytes = null;
using (var ms = new MemoryStream())
using (var unpack = new Ionic.Zlib.GZipStream(fstream, Ionic.Zlib.CompressionMode.Decompress, true))
{
unpack.CopyTo(ms);
bytes = ms.ToArray();
// Total compressed bytes read, plus 10 for GZip header, plus 8 for GZip footer
offset += unpack.TotalIn + 18;
}
yield return bytes;
}
}
}
它很丑而且速度不快(我花了大约 48 秒来解压整个文件),但它似乎可以工作。每个byte[] 输出代表流中的一个压缩文件。这些可以用System.Text.Encoding.UTF8.GetString(...)转成字符串,然后解析提取含义。
文件中的最后一项如下所示:
WARC/1.0
WARC-Type: metadata
WARC-Target-URI: https://zverek-shop.ru/dljasobak/ruletka_sobaki/ruletka-tros_standard_5_m_dlya_sobak_do_20_kg
WARC-Date: 2017-11-25T14:16:01Z
WARC-Record-ID: <urn:uuid:e19ef645-b057-4305-819f-7be2687c3f19>
WARC-Refers-To: <urn:uuid:df5de410-d4af-45ce-b545-c699e535765f>
Content-Type: application/json
Content-Length: 1075
{"Container":{"Filename":"CC-MAIN-20171117170336-20171117190336-00002.warc.gz","Compressed":true,"Offset":"904209205","Gzip-Metadata":{"Inflated-Length":"463","Footer-Length":"8","Inflated-CRC":"1610542914","Deflate-Length":"335","Header-Length":"10"}},"Envelope":{"Format":"WARC","WARC-Header-Length":"438","Actual-Content-Length":"21","WARC-Header-Metadata":{"WARC-Target-URI":"https://zverek-shop.ru/dljasobak/ruletka_sobaki/ruletka-tros_standard_5_m_dlya_sobak_do_20_kg","WARC-Warcinfo-ID":"<urn:uuid:283e4862-166e-424c-b8fd-023bfb4f18f2>","WARC-Concurrent-To":"<urn:uuid:ca594c00-269b-4690-b514-f2bfc39c2d69>","WARC-Date":"2017-11-17T17:43:04Z","Content-Length":"21","WARC-Record-ID":"<urn:uuid:df5de410-d4af-45ce-b545-c699e535765f>","WARC-Type":"metadata","Content-Type":"application/warc-fields"},"Block-Digest":"sha1:4SKCIFKJX5QWLVICLR5Y2BYE6IBVMO3Z","Payload-Metadata":{"Actual-Content-Type":"application/metadata-fields","WARC-Metadata-Metadata":{"Metadata-Records":[{"Value":"1140","Name":"fetchTimeMs"}]},"Actual-Content-Length":"21","Trailing-Slop-Length":"0"}}}
这是占用1441字节的记录,包括它后面的两个空行。
只是为了完整性...
TotalIn 属性返回读取的压缩字节数,不包括 GZip 页眉和页脚。在上面的代码中,我使用 18 字节的常量作为页眉和页脚大小,这是 GZip 的最小大小。虽然这适用于该文件,但处理串联 GZip 文件的其他任何人可能会发现标头中有额外的数据使其变大,这将阻止上述工作。
在这种情况下,您有两个选择:
- 直接解析GZip header,使用
DeflateStream解压。
- 扫描从
TotalIn + 18 字节开始的 GZip 签名字节。
两者都应该工作而不会太慢。由于缓冲发生在解压缩代码中,您将不得不在每个段之后向后寻找流,因此读取一些额外的字节不会让您减慢太多。