Facebook JSON编码严重

时间:2018-04-24 18:10:47

标签: python python-3.x unicode mojibake

我下载了我的Facebook信使数据(在您的Facebook帐户中,转到设置,然后转到您的Facebook信息,然后下载您的信息 ,然后创建一个文件,至少选中消息框)以进行一些很酷的统计

然而,编码存在一个小问题。我不确定,但看起来Facebook对这些数据使用了错误的编码。当我用文本编辑器打开它时,我看到类似这样的内容:Rados\u00c5\u0082aw。当我尝试用python(UTF-8)打开它时,我得到RadosÅ\x82aw。但是我应该得到:Radosław

我的python脚本:

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

我尝试了一些最常见的编码。示例数据是:

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

6 个答案:

答案 0 :(得分:21)

我确实可以确认Facebook下载数据编码错误;一个Mojibake。原始数据是UTF-8编码的,但是被解码为Latin -1。我会确保提交错误报告。

与此同时,您可以通过两种方式修复损坏:

  1. 将数据解码为JSON,然后将任何字符串重新编码为Latin-1,再次解码为UTF-8:

    >>> import json
    >>> data = r'"Rados\u00c5\u0082aw"'
    >>> json.loads(data).encode('latin1').decode('utf8')
    'Radosław'
    
  2. 将数据加载为二进制文件,将所有\u00hh序列替换为最后两个十六进制数字所代表的字节,解码为UTF-8,然后解码为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'))
    

    从您的样本数据中产生:

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

答案 1 :(得分:3)

我想用下面的递归代码片段扩展@Geekmoss的答案,我曾经将我的facebook数据解码。

import json

def parse_obj(obj):
    if isinstance(obj, str):
        return obj.encode('latin_1').decode('utf-8')

    if isinstance(obj, list):
        return [parse_obj(o) for o in obj]

    if isinstance(obj, dict):
        return {key: parse_obj(item) for key, item in obj.items()}

    return obj

decoded_data = parse_obj(json.loads(file))

我注意到这种方法效果更好,因为您下载的Facebook数据可能包含字典列表,在这种情况下,由于lambda身份功能,这些字典将按原样返回。

答案 2 :(得分:2)

我的解析对象解决方案使用parse_hook callback on load/loads函数:

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

更新

使用字符串解析列表的解决方案不起作用。因此,这里是更新的解决方案:

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

答案 3 :(得分:1)

这是带有jq和iconv的命令行解决方案。在Linux上进行了测试。

df

答案 4 :(得分:0)

基于@Martijn Pieters解决方案,我用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;
}

unescape方法是受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('\\');
    }
}

答案 5 :(得分:0)

Facebook 程序员似乎混淆了 Unicode 编码转义序列的概念,可能是在实现他们自己的临时序列化程序时。 Invalid Unicode encodings in Facebook data exports 中的更多详细信息。

试试这个:

import json
import io

class FacebookIO(io.FileIO):
    def read(self, size: int = -1) -> bytes:
        data: bytes = super(FacebookIO, self).readall()
        new_data: bytes = b''
        i: int = 0
        while i < len(data):
            # \u00c4\u0085
            # 0123456789ab
            if data[i:].startswith(b'\\u00'):
                u: int = 0
                new_char: bytes = b''
                while data[i+u:].startswith(b'\\u00'):
                    hex = int(bytes([data[i+u+4], data[i+u+5]]), 16)
                    new_char = b''.join([new_char, bytes([hex])])
                    u += 6

                char : str = new_char.decode('utf-8')
                new_chars: bytes = bytes(json.dumps(char).strip('"'), 'ascii')
                new_data += new_chars
                i += u
            else:
                new_data = b''.join([new_data, bytes([data[i]])])
                i += 1

        return new_data

if __name__ == '__main__':
    f = FacebookIO('data.json','rb')
    d = json.load(f)
    print(d)