Facebook JSON плохо закодирован
Я загрузил данные своего мессенджера 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"
}
9 ответов
Я действительно могу подтвердить, что данные о загрузке Facebook неправильно закодированы; моджибаке Исходные данные имеют кодировку UTF-8, но вместо этого были расшифрованы как латиница -1. Я обязательно подам отчет об ошибке.
В то же время вы можете исправить ущерб двумя способами:
Декодируйте данные как JSON, затем перекодируйте любые строки как Latin-1, снова декодируйте как UTF-8:
>>> import json >>> data = r'"Rados\u00c5\u0082aw"' >>> json.loads(data).encode('latin1').decode('utf8') 'Radosław'
Загрузите данные в двоичном виде, замените все
\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'}
Вот решение для командной строки с jq и iconv. Проверено на Linux.
cat message_1.json | jq . | iconv -f utf8 -t latin1 > m1.json
Я хотел бы расширить ответ @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, которые вы загружаете, могут содержать список dicts, и в этом случае эти dicts будут просто возвращены "как есть" из-за функции идентификации лямбда.
Мое решение для разбора использования объектов parse_hook
обратный вызов при загрузке / загрузке функции:
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
Программисты Facebook, похоже, перепутали концепции кодирования Unicode и escape-последовательностей , вероятно, при реализации своего собственного специального сериализатора. Дополнительные сведения см. В разделе «Недопустимые кодировки Unicode при экспорте данных Facebook» .
Попробуй это:
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)
На основе решения @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('\\');
}
}
Расширение решения Martijn № 1, которое, как я вижу, может привести к рекурсивной обработке объектов (это, безусловно, привело меня изначально):
Вы можете применить это ко всей строке объекта json, если вы этого не сделаете.
ensure_ascii
json.dumps(obj, ensure_ascii=False, indent=2).encode('latin-1').decode('utf-8')
затем запишите его в файл или что-то в этом роде.
PS: это должен быть комментарий к ответу @Martijn: /questions/2472760/facebook-json-ploho-zakodirovan/2472767#2472767 (но я не могу добавлять комментарии)
Это мой подход к Node 17.0.1, основанный на рекурсивном коде @hotigeftas с использованием пакета iconv-lite.
import iconv from 'iconv-lite';
function parseObject(object) {
if (typeof object == 'string') {
return iconv.decode(iconv.encode(object, 'latin1'), 'utf8');;
}
if (typeof object == 'object') {
for (let key in object) {
object[key] = parseObject(object[key]);
}
return object;
}
return object;
}
//usage
let file = JSON.parse(fs.readFileSync(fileName));
file = parseObject(file);
Это ответ @Geekmoss, но адаптированный для Python 3:
def parse_facebook_json(json_file_path):
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
with json_file_path.open('rb') as json_file:
return json.load(json_file, object_hook=parse_obj)
# Usage
parse_facebook_json(Path("/.../message_1.json"))