ليس لدي أي بيانات لدعم هذا الادعاء، ولكنني متأكد إلى حد ما من أن Excel هو الطريقة الأكثر شيوعًا لتخزين البيانات ومعالجتها، بل وحتى تمريرها. ولهذا السبب ليس من غير المألوف أن تجد نفسك تقرأ Excel في بايثون. لقد احتجت مؤخرًا إلى ذلك، لذا اختبرت وقارنت بين عدة طرق لقراءة ملفات Excel في بايثون.
ما الذي نقوم باختباره؟
لمقارنة طرق قراءة ملفات Excel باستخدام بايثون، نحتاج أولاً إلى تحديد ما يجب قياسه، وكيف.
نبدأ بإنشاء ملف Excel بحجم 25 ميغابايت يحتوي على 500 ألف صف مع أنواع مختلفة من الأعمدة:

يدعم برنامج Excel تنسيقي الملفات xls وxlsx. سنستخدم التنسيق الأحدث xlsx.
بالنسبة للمعايير المرجعية، سننفذ دوال لاستيراد البيانات من Excel وإرجاع Iterator
للقواميس:
def iter_excel(file: IO[bytes]) -> Iterator[dict[str, object]]:
# TODO...
نقوم بإرجاع Iterator
للسماح للمستهلكين بمعالجة الملف صفًا تلو الآخر. يمكن أن يؤدي هذا إلى تقليل مساحة الذاكرة من خلال عدم تخزين الملف بالكامل في الذاكرة أثناء معالجته. وكما سنرى في معايير الأداء، فإن هذا ليس ممكنًا دائمًا.
لإنتاج توقيت “نظيف”، نقوم بتكرار المولد دون القيام بأي معالجة فعلية:
for row in iter_excel(file):
pass
سيؤدي هذا إلى جعل المولد يقوم بالتقييم الكامل مع الحد الأدنى من الأداء أو تكلفة الذاكرة.
السرعة
الشيء الأكثر وضوحًا للقياس هو الوقت، والطريقة الأكثر دقة لقياس الوقت في بايثون لأغراض الأداء هي استخدام time.perf_counter
:
import time
start = time.perf_counter()
for row in iter_excel(file): pass
elapsed = time.perf_counter() - start
نبدأ تشغيل المؤقت، ونكرر المولد بأكمله ونحسب الوقت المنقضي.
الأنواع
تشتهر بعض التنسيقات مثل parquet وavro بكونها موصوفة ذاتيًا، حيث تحافظ على المخطط داخل الملف، بينما تشتهر التنسيقات الأخرى مثل CSV بعدم الاحتفاظ بأي معلومات حول البيانات التي تخزنها.
يمكن اعتبار Excel تنسيقًا يخزن معلومات النوع حول محتواه – فهناك خلايا التاريخ وخلايا الأرقام وخلايا الأعداد العشرية وغيرها، لذا عند تحميل البيانات من Excel، قد يكون من المفيد تلقي البيانات بنوعها المقصود. وهذا مفيد بشكل خاص للأنواع مثل التاريخ، حيث قد يكون التنسيق غير واضح أو غير معروف، أو السلاسل التي تحتوي على أرقام مثل أرقام الهواتف أو الرموز البريدية. في هذه المواقف، قد تؤدي محاولة شم النوع إلى نتائج غير صحيحة (بسبب تقليم الأصفار الأولية، وافتراض تنسيق غير صحيح وما إلى ذلك).
ولكي نكون منصفين، قد يجادل البعض بأنه عند تحميل البيانات إلى نظامك، يجب أن يكون لديك معرفة بمخططه، لذا فإن الحفاظ على الأنواع قد لا يكون متطلبًا صارمًا بالنسبة للبعض.
الصواب
لاختبار صحة عملية الاستيراد، قمنا بتضمين صف تحكم في بداية ملف Excel. سنستخدم صف التحكم كمرجع للتأكد من استيراد البيانات بشكل صحيح:
# Test correctness of imported data using a control row
for key, expected_value in (
('number', 1),
('decimal', 1.1),
('date', datetime.date(2000, 1, 1)),
('boolean', True),
('text', 'CONTROL ROW'),
):
try:
value = control_row[key]
except KeyError:
print(f'🔴 "{key}" missing')
continue
if type(expected_value) != type(value):
print(f'🔴 "{key}" expected type "{type(expected_value)}" received type "{type(value)}"')
elif expected_value != value:
print(f'🔴 "{key}" expected value "{expected_value}" received "{value}"')
else:
print(f'🟢 "{key}"')
سنقوم بتشغيل هذا الاختبار بعد كل معيار للتأكد من وجود جميع المفاتيح المتوقعة في صف التحكم، وأن الأنواع والقيم كما نتوقع.
قراءة Excel في بايثون
لدينا الآن ملف عينة، وطريقة لاختبار المحتويات وقمنا بتحديد ما يجب قياسه – نحن جاهزون لاستيراد بعض البيانات!
قراءة Excel باستخدام Pandas
Pandas، مكتبة تحليل البيانات لـبايثون، هي الخيار الأمثل لأي شيء يتعلق بالبيانات في بايثون، لذا فهي مكان جيد للبدء.
import pandas
def iter_excel_pandas(file: IO[bytes]) -> Iterator[dict[str, object]]:
yield from pandas.read_excel(file).to_dict('records')
ما عليك سوى ربط أمرين معًا للحصول على قائمة بالقواميس من ملف Excel. وهذا هو شكل صف واحد من النتيجة:
>>> with open('file.xlsx', 'rb') as f:
... rows = iter_excel_pandas(f)
... row = next(rows)
... print(row)
...
{'boolean': True,
'date': Timestamp('2000-01-01 00:00:00'),
'decimal': 1.1,
'number': 1,
'text': 'CONTROL ROW'}
في لمحة سريعة، يمكننا أن نرى أن التاريخ ليس datetime.date
بل هو Timestamp
في pandas. يبدو الباقي جيدًا. إذا كانت Timestamp
مشكلة وأصررت على datetime.date
، فيمكنك توفير دالة تحويل إلى read_excel
:
import pandas
def iter_excel_pandas(file: IO[bytes]) -> Iterator[dict[str, object]]:
yield from pandas.read_excel(file, converters={
'date': lambda ts: ts.date(),
}).to_dict('records')
يقبل المحول Timestamp
من نوع pandas ويحوله إلى datetime.date
. هذا هو صف التحكم مع المحول المخصص:
{
'number': 1,
'decimal': 1.1,
'date': datetime.date(2000, 1, 1),
'boolean': True,
'text': 'CONTROL ROW',
}
إذا كنت تستخدم pandas لقراءة البيانات من Excel، فليس من غير المعقول أن تفترض أنك تريد أيضًا متابعة تحليلك باستخدام pandas، لذا سنقبل Timestamp
كنوع صالح لمعيارنا.
بعد ذلك، قم بتشغيل معيار الأداء على ملف Excel الكبير:
iter_excel_pandas
elapsed 32.98058952600695
🟢 "number"
🟢 "decimal"
🔴 "date" expected type "<class 'datetime.date'>" received type "<class 'pandas._libs.tslibs.timestamps.Timestamp'>"
🟢 "boolean"
🟢 "text"
استغرق الاستيراد حوالي 32 ثانية حتى اكتمل. نوع حقل التاريخ هو Timestamp
وليس datetime.date
، ولكن لا بأس بذلك.
قراءة Excel باستخدام Tablib
Tablib هي إحدى المكتبات الأكثر شهرة في بايثون لاستيراد وتصدير البيانات بتنسيقات مختلفة. تم تطويرها في الأصل بواسطة منشئ مكتبة Requests الشهيرة، وبالتالي تتميز بتركيز مماثل على تجربة المطور وبيئة العمل.
لتثبيت Tablib، قم بتنفيذ الأمر التالي:
$ pip install tablib
قراءة ملف Excel باستخدام tablib:
import tablib
def iter_excel_tablib(file: IO[bytes]) -> Iterator[dict[str, object]]:
yield from tablib.Dataset().load(file).dict
مجرد سطر واحد من التعليمات البرمجية والمكتبة تقوم بكل العمل الشاق.
قبل أن ننتقل إلى تنفيذ معيار الأداء، هذا هو الشكل الذي يبدو عليه الصف الأول من النتائج:
>>> with open('file.xlsx', 'rb') as f:
... rows = iter_excel_tablib(f)
... row = next(rows)
... print(row)
...
OrderedDict([('number', 1),
('decimal', 1.1),
('date', datetime.datetime(2000, 1, 1, 0, 0)),
('boolean', True),
('text', 'CONTROL ROW')])
OrderedDict هي فئة فرعية من قاموس بايثون مع بعض التوابع الإضافية لإعادة ترتيب ترتيب القاموس. يتم تعريفها في وحدة collections
المضمنة وهي ما يعيده tablib عندما تطلب قاموسًا. نظرًا لأن OrderedDict هي فئة فرعية من القاموس ويتم تعريفها في وحدة مضمنة، فإننا لا نمانع ونعتبرها مناسبة لأغراضنا.
الآن إلى المعيار على ملف Excel الكبير:
iter_excel_tablib
elapsed 28.526969947852194
🟢 "number"
🟢 "decimal"
🔴 "date" expected type "<class 'datetime.date'>" received type "<class 'datetime.datetime'>"
🟢 "boolean"
🟢 "text"
استغرق الاستيراد 28 ثانية، وهو أسرع من pandas (32 ثانية). تم إرجاع خلية التاريخ كـ datetime.datetime
بدلاً من datetime.date
، وهو أمر غير معقول.
دعونا نرى ما إذا كان بإمكاننا تقليص هذا التوقيت إلى أبعد من ذلك.
قراءة Excel باستخدام Openpyxl
Openpyxl هي مكتبة لقراءة وكتابة ملفات Excel في بايثون. وعلى عكس Tablib، فإن Openpyxl مخصصة فقط لـ Excel ولا تدعم أي أنواع ملفات أخرى. في الواقع، يستخدم كل من tablib وpandas مكتبة Openpyxl تحت الغطاء عند قراءة ملفات xlsx. ربما يؤدي هذا التخصص إلى أداء أفضل.
قراءة ملف Excel باستخدام openpyxl:
import openpyxl
def iter_excel_openpyxl(file: IO[bytes]) -> Iterator[dict[str, object]]:
workbook = openpyxl.load_workbook(file)
rows = workbook.active.rows
headers = [str(cell.value) for cell in next(rows)]
for row in rows:
yield dict(zip(headers, (cell.value for cell in row)))
هذه المرة يتعين علينا كتابة المزيد من التعليمات البرمجية، لذا دعنا نقوم بتقسيمها:
- تحميل مصنف عمل من الملف المفتوح: تدعم الدالة
load_workbook
كلًا من مسار الملف وتدفق البيانات القابل للقراءة. في حالتنا، نعمل على ملف مفتوح. - الحصول على الورقة النشطة: يمكن أن يحتوي ملف Excel على أوراق متعددة ويمكننا اختيار الورقة التي نريد قراءتها. في حالتنا، لدينا ورقة واحدة فقط.
- إنشاء قائمة بالرؤوس: يتضمن الصف الأول في ملف Excel الرؤوس. لاستخدام هذه الرؤوس كمفاتيح لقاموسنا، نقرأ الصف الأول وننتج قائمة بالرؤوس.
- إرجاع النتائج: كرر الصفوف وقم بإنشاء قاموس لكل صف باستخدام الرؤوس وقيم الخلايا. يستخدم openpyxl نوع Cell الذي يتضمن كلًا من القيمة وبعض البيانات الوصفية. يمكن أن يكون هذا مفيدًا لأغراض أخرى، لكننا نحتاج فقط إلى القيم. للوصول إلى قيمة الخلية، نستخدم
cell.value
.
وهذا هو الشكل الذي يبدو عليه الصف الأول من النتائج:
>>> with open('file.xlsx', 'rb') as f:
... rows = iter_excel_openpyxl(f)
... row = next(rows)
... print(row)
{'boolean': True,
'date': datetime.datetime(2000, 1, 1, 0, 0),
'decimal': 1.1,
'number': 1,
'text': 'CONTROL ROW'}
يبدو واعدًا! قم بتشغيل الاختبار على الملف الكبير:
iter_excel_openpyxl
elapsed 35.62
🟢 "number"
🟢 "decimal"
🔴 "date" expected type "<class 'datetime.date'>" received type "<class 'datetime.datetime'>"
🟢 "boolean"
🟢 "text"
استغرق استيراد ملف Excel الكبير باستخدام openpyxl حوالي 35 ثانية، وهي مدة أطول من Tablib (28 ثانية) وpandas (32 ثانية).
كشف بحث سريع في الوثائق عن قسم واعد بعنوان “الأداء“. في هذا القسم، يصف openpyxl “الأوضاع المحسنة” لتسريع الأمور عند قراءة أو كتابة ملف فقط:
import openpyxl
def iter_excel_openpyxl(file: IO[bytes]) -> Iterator[dict[str, object]]:
workbook = openpyxl.load_workbook(file, read_only=True)
rows = workbook.active.rows
headers = [str(cell.value) for cell in next(rows)]
for row in rows:
yield dict(zip(headers, (cell.value for cell in row)))
تم تحميل ورقة العمل الآن في وضع “القراءة فقط”. نظرًا لأننا نريد فقط قراءة المحتويات وليس الكتابة، فهذا أمر مقبول. دعنا نجري اختبار الأداء مرة أخرى ونرى ما إذا كان ذلك يؤثر على النتائج:
iter_excel_openpyxl
elapsed 24.79
🟢 "number"
🟢 "decimal"
🔴 "date" expected type "<class 'datetime.date'>" received type "<class 'datetime.datetime'>"
🟢 "boolean"
🟢 "text"
يؤدي فتح الملف في وضع “القراءة فقط” إلى تقليل التوقيت من 35 ثانية إلى 24 ثانية – أسرع من tablib (28 ثانية) وpandas (32 ثانية).
قراءة Excel باستخدام LibreOffice
لقد استنفدنا الآن الطرق التقليدية والواضحة لاستيراد Excel إلى بايثون. لقد استخدمنا المكتبات المخصصة وحصلنا على نتائج جيدة. لقد حان الوقت الآن للتفكير خارج الصندوق.
LibreOffice هو بديل مجاني ومفتوح المصدر لمجموعة Office الأخرى. يمكن لـ LibreOffice معالجة ملفات xls وxlsx، كما يتضمن وضعًا بدون واجهة مع بعض خيارات سطر الأوامر المفيدة:
$ libreoffice --help
LibreOffice 7.5.8.2 50(Build:2)
Usage: soffice [argument...]
argument - switches, switch parameters and document URIs (filenames).
...
أحد خيارات سطر أوامر LibreOffice هو تحويل الملفات بين تنسيقات مختلفة. على سبيل المثال، يمكننا استخدام libreoffice لتحويل ملف xlsx إلى ملف csv:
$ libreoffice --headless --convert-to csv --outdir . file.xlsx
convert file.xlsx -> file.csv using filter: Text - txt - csv (StarCalc)
$ head file.csv
number,decimal,date,boolean,text
1,1.1,01/01/2000,TRUE,CONTROL ROW
2,1.2,01/02/2000,FALSE,RANDOM TEXT:0.716658989024692
3,1.3,01/03/2000,TRUE,RANDOM TEXT:0.966075283958641
رائع! دعنا نربط الأمر معًا باستخدام بايثون. سنقوم أولاً بتحويل ملف xlsx إلى CSV ثم استيراد ملف CSV إلى بايثون:
import subprocess, tempfile, csv
def iter_excel_libreoffice(file: IO[bytes]) -> Iterator[dict[str, object]]:
with tempfile.TemporaryDirectory(prefix='excelbenchmark') as tempdir:
subprocess.run([
'libreoffice', '--headless', '--convert-to', 'csv',
'--outdir', tempdir, file.name,
])
with open(f'{tempdir}/{file.name.rsplit(".")[0]}.csv', 'r') as f:
rows = csv.reader(f)
headers = list(map(str, next(rows)))
for row in rows:
yield dict(zip(headers, row))
دعونا نحللها:
- إنشاء دليل مؤقت لتخزين ملف CSV الخاص بنا: استخدم وحدة tempfile المضمنة لإنشاء دليل مؤقت سيتم تنظيفه تلقائيًا عند الانتهاء. من الناحية المثالية، قد نرغب في تحويل ملف معين إلى كائن يشبه الملف في الذاكرة، لكن سطر أوامر libreoffice لا يوفر أي طريقة للتحويل إلى ملف معين، فقط إلى دليل.
- التحويل إلى CSV باستخدام سطر أوامر libreoffice: استخدم وحدة
subprocess
المضمنة لتنفيذ أمر نظام التشغيل. - اقرأ ملف CSV الناتج: افتح ملف CSV الذي تم إنشاؤه حديثًا، وقم بتحليله باستخدام وحدة csv المضمنة وإنتاج القواميس.
وهذا هو الشكل الذي يبدو عليه الصف الأول من النتائج:
>>> with open('file.xlsx', 'rb') as f:
... rows = iter_excel_libreoffice(f)
... row = next(rows)
... print(row)
{'number': '1',
'decimal': '1.1',
'date': '01/01/2000',
'boolean': 'TRUE',
'text': 'CONTROL ROW'}
نلاحظ على الفور أننا فقدنا جميع معلومات النوع – كل القيم عبارة عن سلاسل.
دعنا نجري اختبارًا معياريًا لمعرفة ما إذا كان الأمر يستحق ذلك:
iter_excel_libreoffice
convert file.xlsx -> file.csv using filter : Text - txt - csv (StarCalc)
elapsed 15.279242266900837
🔴 "number" expected type "<class 'int'>" received type "<class 'str'>"
🔴 "decimal" expected type "<class 'float'>" received type "<class 'str'>"
🔴 "date" expected type "<class 'datetime.date'>" received type "<class 'str'>"
🔴 "boolean" expected type "<class 'bool'>" received type "<class 'str'>"
🟢 "text"
لأكون صادقًا، كان هذا أسرع مما كنت أتوقع! باستخدام LibreOffice لتحويل الملف إلى CSV ثم تحميله استغرق الأمر 15 ثانية فقط – أسرع من pandas (35 ثانية)، وtablib (28 ثانية)، وopenpyxl (24 ثانية).
لقد فقدنا معلومات النوع عندما قمنا بتحويل الملف إلى CSV وإذا كان علينا أيضًا تحويل الأنواع، فمن المرجح أن يستغرق الأمر بعض الوقت (قد يكون التسلسل بطيئًا كما تعلم). ولكن بشكل عام، ليس خيارًا سيئًا!
قراءة Excel باستخدام DuckDB
إذا كنا بالفعل نسير على طريق استخدام الأدوات الخارجية، فلماذا لا نعطي الوافد الجديد على الساحة فرصة للمنافسة.
DuckDB هو “نظام إدارة قاعدة بيانات SQL OLAP قيد التنفيذ”. لا يوضح هذا الوصف على الفور سبب كون DuckDB مفيدًا في هذه الحالة، ولكنه مفيد بالفعل. DuckDB جيد جدًا في نقل البيانات وتحويلها بين التنسيقات.
قراءة ملف Excel باستخدام duckdb في بايثون:
import duckdb
def iter_excel_duckdb(file: IO[bytes]) -> Iterator[dict[str, object]]:
duckdb.install_extension('spatial')
duckdb.load_extension('spatial')
rows = duckdb.sql(f"""
SELECT * FROM st_read(
'{file.name}',
open_options=['HEADERS=FORCE', 'FIELD_TYPES=AUTO'])
""")
while row := rows.fetchone():
yield dict(zip(rows.columns, row))
دعونا نحللها:
تثبيت وتحميل الامتداد spatial
: لاستيراد البيانات من Excel باستخدام duckdb، تحتاج إلى تثبيت الامتداد spatial
. هذا غريب بعض الشيء لأن الامتداد spatial
يستخدم للتلاعبات الجغرافية، ولكن هذا ما يريده.
الاستعلام عن الملف: عند تنفيذ الاستعلامات مباشرةً باستخدام المتغير العالمي duckdb، سيتم استخدام قاعدة بيانات في الذاكرة افتراضيًا، على غرار استخدام sqlite مع خيار :memory:. لاستيراد ملف Excel فعليًا، نستخدم الدالة st_read
مع المسار إلى الملف كحجة أولى. في خيارات الدالة، نضبط الصف الأول كرؤوس، وننشط خيار الكشف التلقائي عن الأنواع (هذا هو الإعداد الافتراضي أيضًا).
إنشاء النتيجة: كرر الصفوف وقم بإنشاء القواميس باستخدام قائمة الرؤوس وقيم كل صف.
هذا هو الشكل الذي يبدو عليه الصف الأول عند استخدام DuckDB لاستيراد ملف Excel:
>>> with open('file.xlsx', 'rb') as f:
... rows = iter_excel_duckdb(f)
... row = next(rows)
... print(row)
{'boolean': True,
'date': datetime.date(2000, 1, 1),
'decimal': 1.1,
'number': 1,
'text': 'CONTROL ROW'}
الآن بعد أن أصبح لدينا عملية قراءة ملف Excel باستخدام DuckDB إلى بايثون، دعنا نرى كيفية عملها:
iter_excel_duckdb
elapsed 11.36
🟢 "number"
🟢 "decimal"
🟢 "date"
🟢 "boolean"
🟢 "text"
أولاً وقبل كل شيء، لدينا فائز فيما يتعلق بالأنواع! فقد تمكن DuckDB من اكتشاف جميع الأنواع بشكل صحيح. بالإضافة إلى ذلك، سجل DuckDB 11 ثانية فقط، وهو ما يقربنا من توقيت رقم واحد!
كان أحد الأشياء التي أزعجتني في هذا التنفيذ هو أنه على الرغم من بذلي قصارى جهدي، لم أتمكن من استخدام معلمة لاسم الملف باستخدام دالة duckdb.sql
. إن استخدام تجميع السلاسل لتوليد SQL أمر خطير وعرضة للحقن ويجب تجنبه قدر الإمكان.
في إحدى محاولاتي لحل هذه المشكلة، حاولت استخدام duckdb.execute
بدلاً من duckdb.sql
، والذي بدا أنه يقبل المعلمات في هذه الحالة:
import duckdb
def iter_excel_duckdb_execute(file: IO[bytes]) -> Iterator[dict[str, object]]:
duckdb.install_extension('spatial')
duckdb.load_extension('spatial')
conn = duckdb.execute(
"SELECT * FROM st_read(?, open_options=['HEADERS=FORCE', 'FIELD_TYPES=AUTO'])",
[file.name],
)
headers = [header for header, *rest in conn.description]
while row := conn.fetchone():
yield dict(zip(headers, row))
هناك فرقين رئيسيين هنا:
- استخدم
duckdb.execute
بدلاً منduckdb.sql
: باستخدامexecute
تمكنت من توفير اسم الملف كمعلمة بدلاً من استخدام تجميع السلسلة. هذا أكثر أمانًا. - إنشاء الرؤوس: وفقًا لمرجع واجهة برمجة التطبيقات، يقوم
duckdb.sql
بإرجاعDuckDBPyRelation
بينما يقومduckdb.execute
بإرجاعDuckDBPyConnection
. لإنتاج قائمة بالرؤوس من كائن الاتصال، لم أتمكن من الوصول إلى.columns
كما في السابق، لذا كان عليّ إلقاء نظرة على خاصيةdescription
الخاصة بالاتصال، والتي أتخيل أنها تصف مجموعة النتائج الحالية.
أدى تشغيل المعيار باستخدام الدالة الجديدة إلى بعض النتائج المثيرة للاهتمام:
iter_excel_duckdb_execute
elapsed 5.73
🔴 "number" expected type "<class 'int'>" received type "<class 'str'>"
🔴 "decimal" expected type "<class 'float'>" received type "<class 'str'>"
🔴 "date" expected type "<class 'datetime.date'>" received type "<class 'str'>"
🔴 "boolean" expected type "<class 'bool'>" received type "<class 'str'>"
🟢 "text"
باستخدام execute
، تمكنا من معالجة الملف في 5.7 ثانية فقط – أي ضعف سرعة المحاولة الأخيرة، ولكننا فقدنا الأنواع. بدون الكثير من المعرفة والخبرة في استخدام DuckDB، لا يمكنني إلا أن أفترض أن إنشاء العلاقة والتحويل إلى الأنواع الصحيحة يتطلب بعض النفقات الإضافية.
قبل أن ننتقل إلى خيارات أخرى، دعنا نتحقق مما إذا كان التحميل المسبق وتثبيت الإضافات يحدث أي فرق كبير:
import duckdb
+duckdb.install_extension('spatial')
+duckdb.load_extension('spatial')
+
def iter_excel_duckdb_execute(file: IO[bytes]) -> Iterator[dict[str, object]]:
- duckdb.install_extension('spatial')
- duckdb.load_extension('spatial')
rows = duckdb.execute(
"SELECT * FROM st_read(?, open_options=['HEADERS=FORCE', 'FIELD_TYPES=AUTO'])",
تنفيذ الدالة عدة مرات:
iter_excel_duckdb_execute
elapsed 5.28
elapsed 5.69
elapsed 5.28
لم يكن للتحميل المسبق للامتدادات تأثيرًا كبيرًا على التوقيت.
دعونا نرى ما إذا كان إزالة اكتشاف النوع التلقائي له أي تأثير:
duckdb.load_extension('spatial')
def iter_excel_duckdb_execute(file: IO[bytes]) -> Iterator[dict[str, object]]:
conn = duckdb.execute(
- "SELECT * FROM st_read(?, open_options=['HEADERS=FORCE', 'FIELD_TYPES=AUTO'])",
+ "SELECT * FROM st_read(?, open_options=['HEADERS=FORCE', 'FIELD_TYPES=STRING'])",
[file.name],
)
headers = [header for header, *rest in conn.description]
تنفيذ الدالة عدة مرات:
iter_excel_duckdb_execute
elapsed 5.80
elapsed 7.21
elapsed 6.45
يبدو أيضًا أن إزالة اكتشاف النوع التلقائي لم يكن له أي تأثير كبير على التوقيت.
قراءة Excel باستخدام Calamine
في السنوات الأخيرة، يبدو الأمر وكأن كل مشكلة تتعلق بالأداء في بايثون يتم حلها باستخدام لغة أخرى. وبصفتي مطور بايثون، أعتبر هذا نعمة حقيقية. فهذا يعني أنني أستطيع الاستمرار في استخدام اللغة التي اعتدت عليها والاستمتاع بفوائد الأداء التي توفرها جميع اللغات الأخرى!
Calamine هي مكتبة Rust خالصة لقراءة ملفات Excel وجداول البيانات OpenDocument.
قراءة ملف Excel باستخدام calamine في بايثون:
import python_calamine
def iter_excel_calamine(file: IO[bytes]) -> Iterator[dict[str, object]]:
workbook = python_calamine.CalamineWorkbook.from_filelike(file) # type: ignore[arg-type]
rows = iter(workbook.get_sheet_by_index(0).to_python())
headers = list(map(str, next(rows)))
for row in rows:
yield dict(zip(headers, row))
المرور بنفس الروتين مرة أخرى – تحميل المصنف، واختيار الورقة، وجلب الرؤوس من الصف الأول، وتكرار النتائج وإنشاء قاموس من كل صف.
وهذا هو شكل الصف الأول:
>>> with open('file.xlsx', 'rb') as f:
... rows = iter_excel_calamine(f)
... row = next(rows)
... print(row)
{'boolean': True,
'date': datetime.date(2000, 1, 1),
'decimal': 1.1,
'number': 1.0,
'text': 'CONTROL ROW'}
تشغيل المعيار:
iter_excel_calamine
elapsed 3.58
🔴 "number" expected type "<class 'int'>" received type "<class 'float'>"
🟢 "decimal"
🟢 "date"
🟢 "boolean"
🟢 "text"
هذه قفزة كبيرة! باستخدام python-calamine
، قمنا بمعالجة الملف بالكامل في 3.5 ثانية فقط – وهي الأسرع حتى الآن! النقطة الحمراء الوحيدة هنا هي العدد الصحيح لدينا تم تفسيره على أنه عدد عشري – وهو أمر غير غير معقول تمامًا.
بعد البحث قليلاً، كانت المشكلة الوحيدة التي وجدتها مع python-calamine هي أنه لا يمكنه إنتاج نتائج كمكرر. ستقوم الدالة CalamineWorkbook.from_filelike
بتحميل مجموعة البيانات بالكامل في الذاكرة، وهو ما قد يشكل مشكلة اعتمادًا على حجم الملف.
ملخص النتائج
فيما يلي ملخص لطرق قراءة ملفات Excel باستخدام بايثون:
Method | Timing (seconds) | Types | Version |
---|---|---|---|
Pandas | 32.98 | Yes | 2.1.3 |
Tablib | 28.52 | Yes | 3.5.0 |
Openpyxl | 35.62 | Yes | 3.1.2 |
Openpyxl (readonly) | 24.79 | Yes | 3.1.2 |
LibreOffice | 15.27 | No | 7.5.8.2 |
DuckDB (sql) | 11.36 | Yes | 0.9.2 |
DuckDB (execute) | 5.73 | No | 0.9.2 |
Calamine (python-calamine) | 3.58 | Yes | 0.22.1 (0.1.7) |
إذن، أيهما ينبغي عليك استخدامه؟ الأمر يعتمد على… هناك بعض الاعتبارات الإضافية بخلاف السرعة عند اختيار مكتبة للعمل مع ملفات Excel في بايثون:
- القدرة على الكتابة: قمنا باختبار طرق قراءة ملفات Excel، ولكن في بعض الأحيان يكون من الضروري إنتاج ملفات Excel أيضًا. بعض المكتبات التي قمنا باختبارها لا تدعم الكتابة. على سبيل المثال، لا تستطيع Calamine كتابة ملفات Excel، بل القراءة فقط.
- التنسيقات الإضافية: قد يتطلب النظام تحميل وإنتاج ملفات بتنسيقات أخرى غير Excel. تدعم بعض المكتبات، مثل pandas وTablib، مجموعة متنوعة من التنسيقات الإضافية، بينما تدعم calamine وopenpyxl Excel فقط.
اكتشاف المزيد من بايثون العربي
اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.