Anatomia Problemu

Szlag mnie trafia. Za każdym, razem kiedy wczytuję plik csv i jest błąd kodowania utf-8.

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb0 in position 12: invalid start byte

To wygląda jak próba odkręcenia śrubokrętem płaskim gwintu krzyżakowego.

Jakimś zrządzeniem losu często mi się to przytrafia.

Przeglądam strony w necie i nie widzę, żeby to był powszechny problem. Żeby to komuś do dokuczało.

Albo problem dotyczy tylko mnie, albo wszyscy mają dobrze zakodowane pliki.

Jestem jednak dosyć sceptyczny. To, że czegoś nie widać, to nie znaczy, że tego nie ma.

Sam natkniesz się na ten problem. Może już natknąłeś.

Przypomina to mniej więcej tę scenę.

Nie Ma Takiego Znaku, Znaczy Miasta

Chcesz odczytać plik. Liczysz, że wszystko pójdzie gładko.

A tu guzik. Bo nie rozpoznaje znaku.

Trzeba wejść w plik i zobaczyć, jakie to kodowanie będzie.

Próbujesz po kolei Windows-1250, ISO-8859-1. W końcu znajdujesz, zapisujesz i możesz pracować dalej.

Nie wierzę. Tyle czasu zmarnowane.

Musi być jakiś sposób, aby to uprościć. Podejść systemowo. Bardziej… NAUKOWO.

Rozwiązanie

Na świecie jest tylu Programistów, Data Naukowców, Amerykańskich Naukowców. Na pewno, ktoś coś wymyślił. Tak uważałem przynajmniej w poprzednim wpisie.

Są. Gotowe do użycia. Proste, szybkie i skuteczne biblioteki Pythona.

Scenariusz jest taki:

  1. Odkrywamy kodowanie (za pomocą dwóch bibliotek bliźniaczek chardet i charset_normalizer),
  2. Wybieramy kodowanie, które jest najbardziej prawdopodobne (najwyższe confidence),
  3. Importujemy i zapisujemy jako plik z kodowaniem utf-8.

Złe Znaki Poskramiacz

Najważniejsza rzecz na początek.

Nazwa funkcji.

Ma być marketingowo. Z siłą. Hit Internetu i świata Data Nauki.

Propozycje? zle_znaki_rozpoznawacz_i_zamieniacz (widać radziecki sznyt), znako_rozwieracz (zbyt pornograficzne), zle_znaki_poskramiacz. I tak zostanie.

from pathlib import Path

# funkcja, która pobiera ścieżkę pliku i podaje nazwę dla nowego pliku
def nazwa_nowego_pliku(sciezka_do_pliku,dodatek_do_nazwy):
    plik = Path(sciezka_do_pliku)
    return Path(plik.parent.absolute()) / (plik.stem+'_'+dodatek_do_nazwy+plik.suffix)

# dwie bibliteki do rozpoznawania kodowania
import chardet
import charset_normalizer as charset

import pandas as pd


def zle_znaki_poskramiacz(sciezka_do_pliku):
    plik = Path(sciezka_do_pliku)
    kodowanie = 'default'
    
    with open(plik.absolute(), 'rb') as rawdata:
        charset_result = charset.detect(rawdata.read(10000))
        # wracamy na początek pliku
        rawdata.seek(0, 0)
        chardet_result = chardet.detect(rawdata.read(10000))
    
    print(chardet_result)
    print(charset_result)
    
    # gdzie jest większa pewność wyniku
    if charset_result['confidence'] > chardet_result['confidence']:
        kodowanie = charset_result['encoding']
    else:
        kodowanie = chardet_result['encoding']
    
    try:
        # importujemy plik z odkrytym kodowaniem i zapisujemy do nowego pliku
        df = pd.read_csv(sciezka_do_pliku, encoding=kodowanie)
        nowy_plik = nazwa_nowego_pliku(sciezka_do_pliku,'utf8')
        df.to_csv(nowy_plik,index=None)
        return nowy_plik
    except UnicodeDecodeError:
        print("""
            ---------------------------------------------------
            Niestety nie udało się odkodować i zapisać pliku!!!
            ---------------------------------------------------
             """)  

Siostry bliźniaczki robią robotę w ramach całej funkcji, z których jedna wydaje się, być zdolniejsza (charset_normalizer). Daje za każdym razem lepsze wyniki.

Teraz za każdym razem wczytuje plik csv robię za pośrednictwem zle_znaki_poskramiacz-a. Funkcja zwraca dodatkowo ścieżkę do nowego pliku z kodowaniem utf-8.

Dla pliku csv z tej strony wygląda to tak.

df = pd.read_csv(zle_znaki_poskramiacz('SeoulBikeData.csv'))

Parę info przy imporcie.

{'encoding': 'ISO-8859-1', 'confidence': 0.73, 'language': ''}
{'encoding': 'cp1250', 'language': '', 'confidence': 1.0}

Wykryte kodowanie encoding i pewność co do tego confidence. Z automatu wybiera najbardziej pewne kodowanie czyli tutaj cp1250.

Na wszelki wypadek dodałem obsługę błędu. To gdy zdarzy się coś co się nie powinno wydarzyć. Czyli nie odkryje kodowania.

Przetestuj i daj znać. Może coś trzeba ulepszyć? Ciekaw jestem jak Ty sobie radzisz z tego typu problemami.

Ja na tą chwilę czuję się spełniony i szczęśliwy.

Taktyka Killera