2011-02-08 25 views
12

Piszę skrypt, który będzie działał z danymi pochodzącymi z oprzyrządowania jako strumieniami gzip. W około 90% przypadków moduł gzip działa idealnie, ale niektóre strumienie powodują, że produkuje on IOError: Not a gzipped file. Jeśli nagłówek programu gzip zostanie usunięty, a strumień deflacyjny zostanie przesłany bezpośrednio do zlib, otrzymam zamiast niego Error -3 while decompressing data: incorrect header check. Po około pół dnia uderzania głową o ścianę odkryłem, że strumienie, które mają problemy, zawierają pozornie losową liczbę dodatkowych bajtów (które nie są częścią danych gzip) dołączonych do końca.Jak mogę pracować z plikami Gzip, które zawierają dodatkowe dane?

Wydaje mi się dziwne, że Python nie może pracować z tymi plikami z dwóch powodów:

  1. Zarówno Gzip i 7zip są w stanie otworzyć te „wyściełane” pliki bez problemu. (. Gzip produkuje komunikat decompression OK, trailing garbage ignored, 7zip uda cicho)
  2. Zarówno docs Gzip i Python wydają się wskazywać, że to powinno działać: (Kopalnia nacisk)

    Gzip's format.txt:

    Musi istnieć możliwość do wykryj koniec skompresowanych danych dowolną metodą kompresji, , niezależnie od faktycznego rozmiaru skompresowanych danych. W szczególności dekompresor musi być w stanie wykryć i pominąć niektóre dane dołączone do ważnego pliku skompresowanego w systemie plików rekord zorientowanych, lub gdy skompresowane dane można odczytać tylko z urządzenia w wielokrotnościami określony rozmiar bloku.

    Python's gzip.GzipFile`:

    Wywołanie close() metoda A GzipFile obiektu nie zamyka fileobj, ponieważ może chcesz dołączyć więcej materiału po danych skompresowanych. Umożliwia to również przekazanie obiektu StringIO otwartego do zapisania jako fileobj i pobranie wynikowego bufora pamięci za pomocą metody obiektu obiektu.

    Python's zlib.Decompress.unused_data:

    Ciąg, który zawiera żadnych bajtów przeszłości końcu skompresowanych danych. Oznacza to, że pozostaje on "", dopóki nie będzie dostępny ostatni bajt zawierający dane kompresji. Jeśli cały ciąg znaków zawiera skompresowane dane, jest to pusty ciąg znaków o numerze "".

    Jedynym sposobem określenia, gdzie kończy się ciąg skompresowanych danych, jest ich dekompresja. Oznacza to, że gdy skompresowane dane zawierają część większego pliku, jego koniec można znaleźć tylko poprzez odczytanie danych i podanie go przez jakiś niepustny ciąg w metodzie obiektu dekompresyjnego o numerze decompress(), dopóki atrybut unused_data przestanie być atrybutem . pusty ciąg.

Oto cztery sposoby próbowałem. (Te przykłady są Python 3.1, ale Przetestowałem 2,5 i 2,7 i miał ten sam problem.)

# approach 1 - gzip.open 
with gzip.open(filename) as datafile: 
    data = datafile.read() 

# approach 2 - gzip.GzipFile 
with open(filename, "rb") as gzipfile: 
    with gzip.GzipFile(fileobj=gzipfile) as datafile: 
     data = datafile.read() 

# approach 3 - zlib.decompress 
with open(filename, "rb") as gzipfile: 
    data = zlib.decompress(gzipfile.read()[10:]) 

# approach 4 - zlib.decompressobj 
with open(filename, "rb") as gzipfile: 
    decompressor = zlib.decompressobj() 
    data = decompressor.decompress(gzipfile.read()[10:]) 

Czy robię coś źle?

UPDATE

porządku, podczas gdy problem z gzip wydaje się być błąd w module, moi zlib problemy są samookaleczenia. ;-)

Podczas kopania do gzip.py zdałem sobie sprawę, co robiłem źle - domyślnie, zlib.decompress i in. spodziewaj się strumieni zlib-owskich, a nie nagich strumieni deflacyjnych. Przekazując ujemną wartość dla wbits, można powiedzieć zlib, aby pominąć nagłówek zlib i odszyfrować surowy strumień. Obie te czynności:

# approach 5 - zlib.decompress with negative wbits 
with open(filename, "rb") as gzipfile: 
    data = zlib.decompress(gzipfile.read()[10:], -zlib.MAX_WBITS) 

# approach 6 - zlib.decompressobj with negative wbits 
with open(filename, "rb") as gzipfile: 
    decompressor = zlib.decompressobj(-zlib.MAX_WBITS) 
    data = decompressor.decompress(gzipfile.read()[10:]) 

Odpowiedz

18

To jest błąd. Jakość modułu gzip w Pythonie znacznie odbiega od jakości, która powinna być wymagana w bibliotece standardowej Pythona.

Problem polega na tym, że moduł gzip zakłada, że ​​plik jest strumieniem plików w formacie gzip. Pod koniec skompresowanych danych zaczyna się od zera, spodziewając się nowego nagłówka gzip; jeśli go nie znajdzie, wywołuje wyjątek. To jest źle.

oczywiście, jest ważne aby złączyć dwa pliki gzip np

echo testing > test.txt 
gzip test.txt 
cat test.txt.gz test.txt.gz > test2.txt.gz 
zcat test2.txt.gz 
# testing 
# testing 

błąd modułu GZIP jest to, że nie powinna podnieść wyjątek, jeśli nie ma gzip header drugim razem; powinien po prostu zakończyć plik. Powinno to spowodować, że poda wyjątek, jeśli nie ma nagłówka za pierwszym razem.

Nie ma czystego rozwiązania bez bezpośredniej modyfikacji modułu gzip; jeśli chcesz to zrobić, spójrz na dół metody _read. Powinien ustawić inną flagę, np. reading_second_block, aby powiedzieć _read_gzip_header, aby podnieść EOFError zamiast IOError.

W tym module są inne błędy. Na przykład szuka niepotrzebnie, powodując awarię w przypadku strumieni nieodczytywalnych, takich jak gniazda sieciowe. Daje mi to bardzo małe zaufanie do tego modułu: programista, który nie wie, że gzip musi działać bez szukania, jest poważnie niewykwalifikowany, aby zaimplementować go w standardowej bibliotece Pythona.

+0

bym faktycznie spasowanych o trochę z 'wewnętrznych gzip' użytkownika podczas debugowania tego problemu, ale nie przyszło mi do głowy, aby naprawić tam problem i spakuj zmodyfikowany moduł za pomocą mojego skryptu.To brzydkie jak diabli, ale wygląda na to, że nadal może być najlepszą dostępną opcją. : -/ –

+0

@Ben: Jest wystarczająco samodzielny, że przynajmniej nie jest to poważny koszt; tylko jeden plik. Jest to bardziej denerwujące w przypadku rodzimych modułów. –

+0

Podczas pracy, zakładając, że nie łamie ona ograniczeń rozmiaru ani czasu na kodzie, można czytać bajt po chwili po otrzymaniu błędu. Dołącz każdy bajt na liście, gdy otrzymasz kolejny błąd IOError z parametrem "Nie jest to plik gzipowany", osiągnąłeś koniec danych, '' .znajdź i zwróć –

4

Miałem podobny problem w przeszłości. Napisałem numer new module, który działa lepiej w przypadku strumieni. Możesz to wypróbować i sprawdzić, czy to działa.

+3

Czy kiedykolwiek rozważałeś połączenie swoich poprawek ze standardowym modułem gzip? – Karmastan

+0

Rozważałem to, ale jak to jest mieć zależność od innej części ramy, która jest w. Jednak powinno być łatwe do naprawienia. – Keith

-1

Nie mogłem pracować z wyżej wymienionymi technikami. więc popełnił obejść stosując zipfile pakiet

import zipfile 
from io import BytesIO 
mock_file = BytesIO(data) #data is the compressed string 
z = zipfile.ZipFile(file = mock_file) 
neat_data = z.read(z.namelist()[0]) 

Działa idealnie

Powiązane problemy