فراس محمد
→ كل المقالات
البيانات العربية · Python

لماذا يظهر النص العربي مشوهاً في ملفات تصدير فيسبوك، وكيف تصلحه

ملف التصدير من Meta يكتب كل بايت من الحرف العربي رمزاً مستقلاً. أي برنامج يقرأ JSON يحوّل ذلك إلى حروف لاتينية، دون أي رسالة خطأ.

بقلم · · 4 دقائق قراءة

الجواب المختصر

حين تنزّل بياناتك من فيسبوك أو إنستغرام بصيغة JSON، تكتب Meta كل بايت من الحرف العربي في ترميز UTF-8 رمزاً مستقلاً بالشكل \u00XX. أي برنامج يقرأ JSON يحوّل كل رمز منها إلى حرف لاتيني واحد، فيظهر النص العربي المشوه رموزاً غريبة مثل التي في السطر الأخير أدناه. البايتات كلها موجودة. الحل أن تقرأ الملف، ثم تحوّل كل نص إلى بايتات بترميز Latin-1 وتفكّها بترميز UTF-8. في Python هذا سطر واحد:

text.encode('latin-1').decode('utf-8')
ال
\u00d8\u00a7\u00d9\u0084
ال

أين واجهت المشكلة

كنت أبني أرشيفاً واحداً قابلاً للبحث من سبع صيغ تصدير: نحو 570,000 رسالة من 2009 إلى 2026، قرابة نصفها بالعربية. منصة واحدة قدّمت نصاً بترميز UTF-8 موسوماً على أنه Latin-1. بدا الاستيراد سليماً. عدد الرسائل صحيح، والنص الإنجليزي سليم، ولم تظهر رسالة خطأ واحدة. وكان كل حرف عربي قد استُبدل.

لو وثقت بذلك التشغيل لضاع نصف الأرشيف، والبرنامج يعلن النجاح.

ما الذي يحدث، بايتاً ببايت

خذ حرف الألف (ا). رقمه في Unicode هو U+0627، وفي UTF-8 يشغل بايتين: D8 A7. تكتبه Meta في الملف هكذا: \u00d8\u00a7. وبرنامج JSON يقرأ ذلك حرفين: Ø ثم §.

واللام (ل) بايتاها D9 84، فتصبح Ù ومعها حرف تحكم غير مرئي رقمه U+0084. برامج Windows التي تقرأ النص بترميز cp1252 ترسم ذلك البايت على شكل „، وهكذا تتحول كلمة "ال" إلى السطر الأخير في المربع أعلاه.

لا يظهر أي خطأ لأن كل بايت من 00 إلى FF حرف صحيح في Latin-1. والحرف الإنجليزي بايت واحد معناه واحد في الترميزين، فيمر دون أن يتأثر.

وهناك علامة سريعة: كل حرف في كتلة العربية الأساسية في Unicode، بين U+0600 وU+06FF، يبدأ بالبايت D8 أو D9 أو DA أو DB. لذلك يمتلئ العربي المشوه بالرموز Ø و Ù و Ú و Û.

الحل، بعد تجربته

هذا الكود يمر على الملف كله، ويصلح كل نص فيه بما في ذلك أسماء المرسلين، ثم يكتب نسخة مقروءة:

import json

def fix(x):
    if isinstance(x, str):
        try:
            return x.encode('latin-1').decode('utf-8')
        except (UnicodeEncodeError, UnicodeDecodeError):
            return x  # already correct, leave it
    if isinstance(x, list):
        return [fix(v) for v in x]
    if isinstance(x, dict):
        return {fix(k): fix(v) for k, v in x.items()}
    return x

with open('message_1.json', encoding='utf-8') as f:
    data = fix(json.load(f))

with open('message_1.fixed.json', 'w', encoding='utf-8') as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

جربته على ملف بنيته بطريقة Meta نفسها في الترميز، فيه نص عربي ورمز تعبيري وكلمة café. عادت الرسائل الأربع مطابقة للأصل. وتشغيله مرة ثانية على الملف المُصلح لم يغيّر شيئاً، لأن النص السليم يفشل في فك UTF-8 فيتركه جزء except كما هو.

استخدم latin-1 في خطوة التحويل. مع cp1252 يتوقف الكود بخطأ عند حرف الفاء (ف)، ورقمه U+0641، لأن بايته الثاني 0x81 قيمة لا يعرّفها cp1252.

وفي Node.js يكفي سطر واحد، والمتغير raw يحمل نص الملف:

const data = JSON.parse(raw, (k, v) => typeof v === 'string' ? Buffer.from(v, 'latin1').toString('utf8') : v);

إذا كان النص سليماً أصلاً، يشوّهه Node دون رسالة خطأ. في تجربتي عادت كلمة "مرحبا" بهذا الشكل:

E1-('

شغّله مرة واحدة فقط، على الملف الأصلي الذي نزّلته.

افحص النتيجة

  1. ابحث في الملف المُصلح عن Ø و Ù. في نص أغلبه عربي يجب أن تجد منهما القليل جداً.
  2. افتح بعض الرسائل العربية التي تتذكرها واقرأها كاملة.
  3. انظر إلى أسماء المرسلين. هي نصوص في الملف نفسه وتتشوه بالطريقة نفسها.
  4. ابحث عن رسالة فيها رمز تعبيري. كل رمز منها 4 بايتات في ملف التصدير، ويجب أن يعود حرفاً واحداً.
  5. أبقِ ensure_ascii=False عند الحفظ. من دونه يكتب Python رموزاً مثل \u0645، وهي صحيحة لكن قراءتها بالعين صعبة.

أسئلة شائعة

هل بياناتي على فيسبوك تالفة؟

البايتات كلها موجودة. أصلحها من ملف JSON الأصلي وستعود الحروف كلها. وإذا كان برنامج آخر قد حفظ النص المشوه بترميز مختلف، فارجع إلى الملف الأصلي الذي نزّلته.

هل ملفات تصدير إنستغرام فيها المشكلة نفسها؟

نعم. تنزيل إنستغرام بصيغة JSON يستخدم الترميز نفسه، والكود نفسه يصلحه.

هل المشكلة خاصة بالعربية؟

كل حرف خارج الإنجليزية الأساسية يتشوه بالطريقة نفسها، ومنه الحروف اللاتينية بعلامات النطق، والسيريلية، والهندية، والرموز التعبيرية. ملف التجربة كان فيه café ورمز تعبيري، وعاد كلاهما سليماً.

لماذا لا يظهر خطأ في برنامج عرض JSON؟

لأن الملف JSON صحيح، وكل رمز فيه حرف مقبول. الخطأ وقع عند كتابة الملف، ولا يملك أي قارئ وسيلة لرؤيته.

المشروع الذي خرج منه هذا

ظهر هذا الخطأ داخل مسار بيانات أكبر حوّل سبع صيغ تصدير إلى بنية رسائل واحدة، عبر نحو 40 برنامجاً صغيراً بلغة Python. القصة كاملة في دراسة حالة الأرشيف الشخصي.

إن كانت لديك ملفات تصدير أو أرشيفات عربية تحتاج تنظيفاً لتصبح قابلة للبحث، فهذا عمل أتولاه.