web-dev-qa-db-fra.com

Facebook JSON mal encodé

J'ai téléchargé mes données de messagerie Facebook (dans votre compte Facebook, accédez à paramètres, puis à Vos informations Facebook, puis Téléchargez vos informations, puis créez un fichier avec au moins la case Messages cochée) pour faire des statistiques intéressantes

Cependant, il y a un petit problème avec l'encodage. Je ne suis pas sûr, mais il semble que Facebook ait utilisé un mauvais encodage pour ces données. Lorsque je l'ouvre avec l'éditeur de texte, je vois quelque chose comme ceci: Rados\u00c5\u0082aw. Quand j'essaye de l'ouvrir avec python (UTF-8) je reçois RadosÅ\x82aw. Cependant, je devrais obtenir: Radosław.

Mon script python:

text = open(os.path.join(subdir, file), encoding='utf-8')
conversations.append(json.load(text))

J'ai essayé quelques encodages les plus courants. Voici des exemples de données:

{
  "sender_name": "Rados\u00c5\u0082aw",
  "timestamp": 1524558089,
  "content": "No to trzeba ostatnie treningi zrobi\u00c4\u0087 xD",
  "type": "Generic"
}
23
Jakub Jendryka

Je peux en effet confirmer que les données de téléchargement de Facebook sont incorrectement encodées; un Mojibake . Les données d'origine sont encodées en UTF-8 mais ont été décodées en latin -1 à la place. Je m'assurerai de déposer un rapport de bogue.

En attendant, vous pouvez réparer les dommages de deux manières:

  1. Décodez les données en JSON, puis réencodez toutes les chaînes en Latin-1, décodez à nouveau en UTF-8:

    >>> import json
    >>> data = r'"Rados\u00c5\u0082aw"'
    >>> json.loads(data).encode('latin1').decode('utf8')
    'Radosław'
    
  2. Chargez les données au format binaire, remplacez tout \u00hh séquences avec l'octet que représentent les deux derniers chiffres hexadécimaux, décodez en UTF-8 puis décodez en JSON:

    import re
    from functools import partial
    
    fix_mojibake_escapes = partial(
         re.compile(rb'\\u00([\da-f]{2})').sub,
         lambda m: bytes.fromhex(m.group(1).decode()))
    
    with open(os.path.join(subdir, file), 'rb') as binary_data:
        repaired = fix_mojibake_escapes(binary_data.read())
    data = json.loads(repaired.decode('utf8'))
    

    À partir de vos échantillons de données, cela produit:

    {'content': 'No to trzeba ostatnie treningi zrobić xD',
     'sender_name': 'Radosław',
     'timestamp': 1524558089,
     'type': 'Generic'}
    
23
Martijn Pieters

Ma solution pour analyser les objets utilise parse_hook rappel sur charge/charges fonction:

import json


def parse_obj(dct):
    for key in dct:
        dct[key] = dct[key].encode('latin_1').decode('utf-8')
        pass
    return dct


data = '{"msg": "Ahoj sv\u00c4\u009bte"}'

# String
json.loads(data)  
# Out: {'msg': 'Ahoj svÄ\x9bte'}
json.loads(data, object_hook=parse_obj)  
# Out: {'msg': 'Ahoj světe'}

# File
with open('/path/to/file.json') as f:
     json.load(f, object_hook=parse_obj)
     # Out: {'msg': 'Ahoj světe'}
     pass

Mise à jour:

La solution pour analyser la liste avec des chaînes ne fonctionne pas. Voici donc une solution mise à jour:

import json


def parse_obj(obj):
    for key in obj:
        if isinstance(obj[key], str):
            obj[key] = obj[key].encode('latin_1').decode('utf-8')
        Elif isinstance(obj[key], list):
            obj[key] = list(map(lambda x: x if type(x) != str else x.encode('latin_1').decode('utf-8'), obj[key]))
        pass
    return obj
2
Geekmoss

Basé sur la solution @Martijn Pieters, j'ai écrit quelque chose de similaire en Java.

public String getMessengerJson(Path path) throws IOException {
    String badlyEncoded = Files.readString(path, StandardCharsets.UTF_8);
    String unescaped = unescapeMessenger(badlyEncoded);
    byte[] bytes = unescaped.getBytes(StandardCharsets.ISO_8859_1);
    String fixed = new String(bytes, StandardCharsets.UTF_8);
    return fixed;
}

La méthode unescape est inspirée de l'org.Apache.commons.lang.StringEscapeUtils.

private String unescapeMessenger(String str) {
    if (str == null) {
        return null;
    }
    try {
        StringWriter writer = new StringWriter(str.length());
        unescapeMessenger(writer, str);
        return writer.toString();
    } catch (IOException ioe) {
        // this should never ever happen while writing to a StringWriter
        throw new UnhandledException(ioe);
    }
}

private void unescapeMessenger(Writer out, String str) throws IOException {
    if (out == null) {
        throw new IllegalArgumentException("The Writer must not be null");
    }
    if (str == null) {
        return;
    }
    int sz = str.length();
    StrBuilder unicode = new StrBuilder(4);
    boolean hadSlash = false;
    boolean inUnicode = false;
    for (int i = 0; i < sz; i++) {
        char ch = str.charAt(i);
        if (inUnicode) {
            unicode.append(ch);
            if (unicode.length() == 4) {
                // unicode now contains the four hex digits
                // which represents our unicode character
                try {
                    int value = Integer.parseInt(unicode.toString(), 16);
                    out.write((char) value);
                    unicode.setLength(0);
                    inUnicode = false;
                    hadSlash = false;
                } catch (NumberFormatException nfe) {
                    throw new NestableRuntimeException("Unable to parse unicode value: " + unicode, nfe);
                }
            }
            continue;
        }
        if (hadSlash) {
            hadSlash = false;
            if (ch == 'u') {
                inUnicode = true;
            } else {
                out.write("\\");
                out.write(ch);
            }
            continue;
        } else if (ch == '\\') {
            hadSlash = true;
            continue;
        }
        out.write(ch);
    }
    if (hadSlash) {
        // then we're in the weird case of a \ at the end of the
        // string, let's output it anyway.
        out.write('\\');
    }
}
0
Ondrej Sotolar