دمج ملفات Excel متعددة في ملف واحد باستخدام بايثون وPandas تلقائيًا

قراءة جميع ملفات Excel من مجلد باستخدام بايثون وPandas وpathlib

إذا كنت تستلم كل شهر ملف Excel جديدًا للمبيعات أو المخزون أو الحضور، فقد ينتهي بك الأمر مع عشرات الملفات المتشابهة التي تحتاج إلى جمعها في تقرير واحد. فتح كل ملف ونسخ الصفوف يدويًا عملية بطيئة وتزيد احتمال التكرار أو نسيان بعض البيانات.

في هذا الدرس سنجعل بايثون يقرأ جميع ملفات Excel داخل مجلد تلقائيًا، يحول كل ملف إلى DataFrame باستخدام Pandas، ثم يجمعها في ملف واحد باستخدام pd.concat(). وسنضيف تحسينات عملية مثل استبعاد ملف الناتج من التشغيل التالي، تجاهل ملفات Excel المؤقتة، إضافة اسم الملف المصدر، توحيد أسماء الأعمدة، حذف التكرار، ترتيب البيانات، واختيار Sheet محددة.

{alertInfo} الخلاصة السريعة: اجمع مسارات ملفات Excel من المجلد، اقرأ كل ملف باستخدام pd.read_excel()، ضع الجداول داخل قائمة، ثم استخدم pd.concat(frames, ignore_index=True) واحفظ الناتج بواسطة to_excel().

{getToc} $title={محتوى المقال}

متى تحتاج إلى دمج ملفات Excel تلقائيًا؟

  • ملف مبيعات لكل شهر.
  • ملف حضور لكل فرع.
  • تقارير يومية من نظام خارجي.
  • ملفات مخزون من عدة مستودعات.
  • ملفات طلبات أو فواتير أسبوعية.

إذا كانت الملفات تحتوي على نفس النوع من البيانات، فبدل النسخ واللصق يمكنك تشغيل سكربت واحد للحصول على ملف موحد جاهز للتحليل.

شكل المشروع الذي سنستخدمه

monthly_sales/
├── january.xlsx
├── february.xlsx
├── march.xlsx
└── april.xlsx

ولنفترض أن كل ملف يحتوي أعمدة مثل:

date | product | quantity | amount

نريد في النهاية إنشاء:

merged_sales.xlsx

تثبيت Pandas وopenpyxl

سنستخدم Pandas لمعالجة الجداول، وopenpyxl لقراءة وكتابة ملفات .xlsx.

python -m pip install pandas openpyxl

وسنستخدم pathlib للبحث في المجلد، وهي جزء من مكتبات بايثون القياسية.

دمج ملفين Excel أولًا لفهم الفكرة

import pandas as pd

january = pd.read_excel("january.xlsx")
february = pd.read_excel("february.xlsx")

merged = pd.concat(
    [january, february],
    ignore_index=True
)

merged.to_excel(
    "merged.xlsx",
    index=False
)

pd.concat() هنا تضع صفوف الملف الثاني أسفل صفوف الملف الأول. والخيار ignore_index=True ينشئ index جديدة للجدول النهائي.

دمج عدة DataFrame من ملفات Excel باستخدام pd.concat في Pandas

قراءة جميع ملفات Excel من مجلد باستخدام pathlib

from pathlib import Path

folder = Path("monthly_sales")

excel_files = list(
    folder.glob("*.xlsx")
)

for file_path in excel_files:
    print(file_path.name)

glob("*.xlsx") تعني: أعطني كل الملفات الموجودة مباشرة داخل المجلد والتي تنتهي بـ.xlsx.

قراءة جميع ملفات Excel من مجلد باستخدام بايثون وPandas وpathlib

دمج جميع ملفات Excel داخل المجلد تلقائيًا

from pathlib import Path
import pandas as pd

folder = Path("monthly_sales")

excel_files = list(
    folder.glob("*.xlsx")
)

frames = []

for file_path in excel_files:
    df = pd.read_excel(file_path)
    frames.append(df)

merged_df = pd.concat(
    frames,
    ignore_index=True
)

merged_df.to_excel(
    folder / "merged.xlsx",
    index=False
)

بعد انتهاء الحلقة تصبح frames قائمة تحتوي على DataFrame لكل ملف، ثم تجمعها concat في DataFrame واحدة.

مشكلة مهمة: ملف merged.xlsx قد يدخل في الدمج مرة أخرى

بعد التشغيل الأول يصبح ملف merged.xlsx داخل المجلد. إذا شغلت السكربت مرة أخرى باستخدام *.xlsx سيجده البرنامج أيضًا، وقد تتضاعف البيانات.

لذلك استبعد ملف الناتج:

output_file = folder / "merged.xlsx"

excel_files = [
    file_path
    for file_path in folder.glob("*.xlsx")
    if file_path.name != output_file.name
]

استبعاد ملفات Excel المؤقتة التي تبدأ بـ ~$

عندما يكون ملف Excel مفتوحًا قد يظهر ملف مؤقت مثل:

~$january.xlsx

استبعده أيضًا:

excel_files = [
    file_path
    for file_path in folder.glob("*.xlsx")
    if file_path.name != output_file.name
    and not file_path.name.startswith("~$")
]

التحقق من وجود ملفات قبل concat

if not excel_files:
    print("لا توجد ملفات Excel للدمج.")
    raise SystemExit

هذا يعطي رسالة واضحة بدل ظهور خطأ لأن القائمة فارغة.

إضافة اسم الملف المصدر لكل صف

بعد الدمج قد تحتاج إلى معرفة من أي ملف جاء السجل. أضف عمودًا قبل الدمج:

for file_path in excel_files:
    df = pd.read_excel(file_path)

    df["source_file"] = file_path.name

    frames.append(df)

يمكنك استخدام file_path.stem إذا أردت اسم الملف بدون .xlsx.

هل يجب أن تكون أعمدة الملفات متطابقة؟

ليس شرطًا تقنيًا. إذا كان ملف يحتوي:

name | city | amount

وملف آخر يحتوي:

name | city | amount | phone

فالسلوك الافتراضي لـPandas يحتفظ بكل الأعمدة ويضع قيمًا فارغة في الصفوف التي لا تملك phone.

merged_df = pd.concat(
    frames,
    ignore_index=True,
    join="outer"
)

أما join="inner" فتحتفظ بالأعمدة المشتركة فقط، وقد تؤدي إلى حذف أعمدة مهمة، لذلك استخدمها بحذر.

مشكلة خفية: أسماء الأعمدة فيها مسافات

قد يكون لديك amount في ملف وamount في ملف آخر. بالنسبة إلى Pandas هذان عمودان مختلفان.

نظف أسماء الأعمدة:

df.columns = (
    df.columns
    .str.strip()
    .str.lower()
)

توحيد أسماء أعمدة مختلفة

إذا كان ملف يستخدم total وملف آخر يستخدم amount لنفس المعنى:

df = df.rename(
    columns={
        "total": "amount"
    }
)

لا تفعل ذلك إلا إذا كنت متأكدًا أن العمودين يمثلان المعنى نفسه.

التحقق من الأعمدة المطلوبة

required_columns = {
    "date",
    "product",
    "amount"
}

missing = required_columns - set(df.columns)

if missing:
    print(
        f"{file_path.name}: أعمدة ناقصة: {missing}"
    )

إذا كان الملف غير صالح للتقرير يمكنك تجاهله باستخدام continue أو إيقاف البرنامج حسب أهمية البيانات.

حذف الصفوف المكررة بعد الدمج

merged_df = merged_df.drop_duplicates()

وإذا كان لديك رقم فاتورة يمثل السجل الحقيقي:

merged_df = merged_df.drop_duplicates(
    subset=["invoice_id"],
    keep="last"
)

حدد معنى «السجل المكرر» قبل الحذف. قد تكون عمليتا بيع متشابهتان لكنهما عمليتان حقيقيتان منفصلتان.

دمج ملفات Excel مع اختلاف الأعمدة وحذف التكرار وإضافة اسم الملف كمصدر

ترتيب البيانات بعد الدمج حسب التاريخ

merged_df["date"] = pd.to_datetime(
    merged_df["date"],
    errors="coerce"
)

merged_df = merged_df.sort_values(
    by="date"
)

تحويل التاريخ إلى نوع datetime يجعل الترتيب أكثر موثوقية من ترتيب نصوص قد تكون بتنسيقات مختلفة.

التعامل مع القيم الفارغة

اعرض عدد القيم الناقصة:

print(
    merged_df.isna().sum()
)

وإذا كان من المنطقي استبدال قيمة نصية مفقودة:

merged_df["city"] = (
    merged_df["city"]
    .fillna("غير محدد")
)

قراءة Sheet محددة من كل ملف

إذا كانت كل الملفات تحتوي على Sheet اسمها Sales:

df = pd.read_excel(
    file_path,
    sheet_name="Sales"
)

وإذا لم توجد الورقة في أحد الملفات، يمكنك التقاط الخطأ وتجاهل الملف مع رسالة واضحة.

قراءة ملفات داخل مجلدات فرعية

استخدم rglob بدل glob:

excel_files = list(
    folder.rglob("*.xlsx")
)

نسخة عملية كاملة لدمج ملفات Excel

from pathlib import Path
import pandas as pd

folder = Path("monthly_sales")
output_file = folder / "merged_sales.xlsx"

required_columns = {
    "date",
    "product",
    "amount"
}

excel_files = [
    file_path
    for file_path in folder.glob("*.xlsx")
    if file_path.name != output_file.name
    and not file_path.name.startswith("~$")
]

if not excel_files:
    print("لا توجد ملفات Excel للدمج.")
    raise SystemExit

frames = []

for file_path in excel_files:
    print(f"قراءة: {file_path.name}")

    df = pd.read_excel(file_path)

    df.columns = (
        df.columns
        .str.strip()
        .str.lower()
    )

    missing = required_columns - set(df.columns)

    if missing:
        print(
            f"تم تجاهل {file_path.name}: أعمدة ناقصة {missing}"
        )
        continue

    df["source_file"] = file_path.name

    frames.append(df)

if not frames:
    print("لم يتم العثور على ملفات صالحة للدمج.")
    raise SystemExit

merged_df = pd.concat(
    frames,
    ignore_index=True
)

merged_df = merged_df.drop_duplicates()

merged_df["date"] = pd.to_datetime(
    merged_df["date"],
    errors="coerce"
)

merged_df = merged_df.sort_values(
    by="date"
)

merged_df.to_excel(
    output_file,
    index=False
)

print(f"تم إنشاء الملف: {output_file}")
print(f"عدد الصفوف النهائية: {len(merged_df)}")

لماذا هذه النسخة أفضل من مثال concat بسيط؟

  • لا تعيد قراءة ملف الناتج.
  • تتجاهل ملفات Excel المؤقتة.
  • تتحقق من الأعمدة المهمة.
  • تحفظ اسم مصدر كل صف.
  • تزيل التكرار.
  • ترتب البيانات حسب التاريخ.

دمج ملفات تبدأ باسم معين فقط

إذا كان المجلد يحتوي أنواعًا مختلفة من التقارير:

excel_files = list(
    folder.glob("sales_*.xlsx")
)

بهذا يقرأ sales_january.xlsx ويتجاهل مثلًا employees.xlsx.

حفظ الناتج في CSV بدل Excel

merged_df.to_csv(
    "merged_sales.csv",
    index=False,
    encoding="utf-8-sig"
)

أما إذا كان المستخدم النهائي يحتاج ملف Excel جاهزًا، فاستخدم to_excel().

لماذا نستخدم index=False؟

لأن Pandas تحفظ index كعمود إضافي إذا لم تمنع ذلك. في معظم تقارير Excel لا تحتاج هذا العمود.

هل تحافظ Pandas على تنسيق Excel الأصلي؟

Pandas ممتازة لدمج البيانات الجدولية، لكنها لا تهدف إلى نسخ workbook كاملًا بتنسيقاته وصوره وخلاياه المدمجة وأزراره كما هو. إذا كان هدفك الحفاظ على تنسيق المصنف نفسه، فقد تحتاج إلى معالجة مباشرة باستخدام أدوات مثل openpyxl بدل الاعتماد على DataFrame فقط.

حل خطأ Missing optional dependency openpyxl

python -m pip install openpyxl

إذا استمر الخطأ، تحقق أن المكتبة مثبتة في نفس بيئة بايثون التي تشغل منها السكربت:

python -m pip show openpyxl

حل PermissionError عند حفظ ملف Excel

إذا كان merged.xlsx مفتوحًا في Excel أثناء محاولة الكتابة عليه، قد تفشل عملية الحفظ. أغلق الملف ثم شغل السكربت مرة أخرى.

كيف تعرف أي ملف سبب خطأ القراءة؟

for file_path in excel_files:
    print(f"جاري قراءة: {file_path.name}")
    df = pd.read_excel(file_path)

آخر اسم ظهر قبل رسالة الخطأ يساعدك في معرفة الملف الذي يحتاج إلى فحص.

أخطاء شائعة عند دمج ملفات Excel باستخدام بايثون وPandas

أخطاء شائعة عند دمج ملفات Excel

  • إدخال ملف الناتج في الدمج مرة أخرى.
  • نسيان تجاهل الملفات المؤقتة ~$.
  • افتراض أن أسماء الأعمدة متطابقة دون تنظيفها.
  • حذف التكرار دون تعريف واضح للسجل المكرر.
  • ترتيب التواريخ كنصوص بدل تحويلها إلى datetime.
  • توقع الحفاظ الكامل على تنسيق Excel عند استخدام Pandas.

كيف أتأكد أن الدمج نجح؟

print("عدد الملفات:", len(excel_files))
print("عدد الصفوف:", len(merged_df))
print("الأعمدة:", list(merged_df.columns))
print(merged_df.head())

افتح الملف النهائي أيضًا وتحقق يدويًا من عدد من السجلات، خصوصًا إذا كانت العملية ستستخدم في تقرير شهري.

حساب عدد الصفوف التي تم حذفها كتكرار

before = len(merged_df)

merged_df = merged_df.drop_duplicates()

after = len(merged_df)

print(
    f"تم حذف {before - after} صفوف مكررة"
)

حفظ النتائج في مجلد منفصل

هذا تصميم أفضل لتجنب إدخال ملف الناتج في الدمج:

input_folder = Path("input")
output_folder = Path("output")

output_folder.mkdir(exist_ok=True)

output_file = output_folder / "merged.xlsx"

فتصبح بنية المشروع:

project/
├── merge_excel.py
├── input/
│   ├── january.xlsx
│   ├── february.xlsx
│   └── march.xlsx
└── output/
    └── merged.xlsx

الفرق بين concat وmerge في Pandas

الأداةمتى تستخدمها؟
pd.concat()لجمع جداول متشابهة فوق بعضها، مثل ملفات المبيعات الشهرية.
pd.merge()لربط جدولين باستخدام مفتاح مشترك، مثل ربط الطلبات بالعملاء عبر customer_id.

هل يمكن دمج مئات ملفات Excel؟

نعم إذا كان حجم البيانات مناسبًا لذاكرة الجهاز. في المثال نحفظ كل DataFrame داخل الذاكرة ثم ندمجها. إذا أصبحت الملفات ضخمة جدًا، فكر في قراءة الأعمدة المطلوبة فقط، أو استخدام تنسيق أخف، أو معالجة البيانات على دفعات، أو نقلها إلى قاعدة بيانات.

قراءة أعمدة محددة فقط

df = pd.read_excel(
    file_path,
    usecols=[
        "date",
        "product",
        "quantity",
        "amount"
    ]
)

روابط داخلية مفيدة من بايثون العرب

مصادر رسمية للتوسع

الخلاصة

دمج ملفات Excel متعددة باستخدام بايثون وPandas لا يحتاج إلى فتح كل ملف ونسخ بياناته يدويًا. الفكرة الأساسية هي العثور على الملفات باستخدام pathlib، قراءة كل ملف باستخدام pd.read_excel()، ثم جمع DataFrames داخل قائمة ودمجها باستخدام pd.concat().

ولتحويل المثال إلى أداة عملية: استبعد ملف الناتج والملفات المؤقتة، نظف أسماء الأعمدة، تحقق من الأعمدة المطلوبة، أضف اسم الملف المصدر، حدد معنى التكرار قبل حذفه، وحول التاريخ إلى نوع مناسب قبل الترتيب.

{alertSuccess} القاعدة المهمة: اختبر السكربت أولًا على ملفين أو ثلاثة وتأكد من الأعمدة والتواريخ والتكرار قبل تشغيله على عشرات الملفات. جودة الملف النهائي تعتمد على فهم بنية البيانات أكثر من اعتمادها على سطر concat وحده.

أسئلة شائعة

كيف أدمج عدة ملفات Excel في ملف واحد باستخدام بايثون؟

اقرأ كل ملف باستخدام pd.read_excel()، اجمع DataFrames في قائمة، ثم استخدم pd.concat(frames, ignore_index=True) واحفظ النتيجة باستخدام to_excel().

كيف أقرأ جميع ملفات Excel من مجلد باستخدام بايثون؟

استخدم Path.glob("*.xlsx") من pathlib للحصول على جميع الملفات، ثم مر عليها بحلقة for.

كيف أدمج 100 ملف Excel مرة واحدة؟

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

هل يجب أن تكون أعمدة ملفات Excel متطابقة؟

لا، لكن من الأفضل توحيد أسماء الأعمدة والتحقق من الأعمدة المطلوبة. Pandas ستضع قيمًا فارغة في الأعمدة غير الموجودة في بعض الملفات.

كيف أحذف الصفوف المكررة بعد الدمج؟

استخدم drop_duplicates()، ويمكنك تحديد أعمدة مثل رقم الفاتورة باستخدام subset.

كيف أعرف من أي ملف جاء كل صف؟

قبل الدمج أضف df["source_file"] = file_path.name.

لماذا تتكرر البيانات عند إعادة تشغيل السكربت؟

قد يكون ملف merged.xlsx موجودًا داخل مجلد الإدخال ويتم قراءته مرة أخرى. استبعده أو احفظ النتائج في مجلد منفصل.

ما حل Missing optional dependency openpyxl؟

ثبت المكتبة باستخدام python -m pip install openpyxl من نفس بيئة بايثون التي تشغل منها السكربت.

كيف أدمج Sheet محددة من ملفات متعددة؟

مرر sheet_name="Sales" إلى pd.read_excel() إذا كانت الورقة موجودة في كل الملفات المطلوبة.

ما الفرق بين concat وmerge في Pandas؟

concat تجمع جداول متشابهة رأسيًا، بينما merge تربط جدولين اعتمادًا على مفتاح مشترك.

هل تحافظ Pandas على تنسيق Excel الأصلي؟

الهدف الأساسي هو دمج البيانات الجدولية، وليس نسخ كل تنسيقات المصنف والصور والخلايا المدمجة. الحفاظ على تنسيق workbook يحتاج معالجة مختلفة حسب المطلوب.

كيف أرتب البيانات حسب التاريخ بعد الدمج؟

استخدم pd.to_datetime() لتحويل عمود التاريخ ثم sort_values(by="date").

كيف أتجاهل ملفات Excel المؤقتة؟

استبعد الملفات التي يبدأ اسمها بـ~$.

كيف أحفظ الملف النهائي بدون عمود index؟

استخدم to_excel("merged.xlsx", index=False).

إرسال تعليق

أحدث أقدم