كيفية تنزيل الملفات من عناوين URL باستخدام بايثون

عندما يتعلق الأمر باسترجاع الملفات، توفر لغة بايثون مجموعة قوية من الأدوات والحزم المفيدة في مجموعة متنوعة من التطبيقات، بدءًا من استخراج البيانات من الويب وحتى أتمتة البرامج النصية وتحليل البيانات المستردة. يعد تنزيل الملفات من عنوان URL برمجيًا مهارة مفيدة لتعلم العديد من مشاريع البرمجة والبيانات وسير العمل.

في هذا البرنامج التعليمي، سوف تتعلم كيفية:

  • تنزيل الملفات من الويب باستخدام المكتبة القياسية بالإضافة إلى مكتبات الطرف الثالث في Python.
  • دفق البيانات لتنزيل الملفات الكبيرة في أجزاء يمكن التحكم فيها.
  • تنفيذ التنزيلات الموازية.
  • إجراء تنزيلات غير متزامنة لجلب ملفات متعددة بكميات كبيرة.

في هذا البرنامج التعليمي، ستقوم بتنزيل مجموعة من البيانات الاقتصادية من منصة البيانات المفتوحة للبنك الدولي.

تسهيل تنزيل الملفات باستخدام لغة بايثون

في حين أنه من الممكن تنزيل الملفات من عناوين URL باستخدام أدوات سطر الأوامر التقليدية، توفر Python العديد من المكتبات التي تسهل استرجاع الملفات. يوفر استخدام Python لتنزيل الملفات العديد من المزايا.

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

سبب آخر هو قابلية النقل. قد تواجه مواقف تعمل فيها على تطبيقات مشتركة بين الأنظمة الأساسية. في مثل هذه الحالات، يعد استخدام Python خيارًا جيدًا لأنها لغة برمجة متعددة المنصات. و هذا يعني أن كود Python يمكن تشغيله باستمرار عبر أنظمة تشغيل مختلفة، مثل Windows و Linux و macOS.

يوفر استخدام Python أيضًا إمكانية أتمتة عملياتك، مما يوفر لك الوقت والجهد. تتضمن بعض الأمثلة أتمتة عمليات إعادة المحاولة في حالة فشل التنزيل، واسترداد ملفات متعددة وحفظها من عناوين URL، ومعالجة بياناتك وتخزينها في مواقع محددة.

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

تنزيل ملف من عنوان URL في بايثون

في هذا القسم، ستتعلم أساسيات تنزيل ملف ZIP يحتوي على بيانات الناتج المحلي الإجمالي من منصة البيانات المفتوحة للبنك الدولي. ستستخدم أداتين شائعتين في بايثون ، urllib و requests ، لتنزيل الناتج المحلي الإجمالي حسب البلد.

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

إستخدام urllib من المكتبة القياسية

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

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

تعتبر حزمة urllib متعددة الاستخدامات. حيث تتكامل بشكل جيد مع الوحدات الأخرى في مكتبة Python القياسية، مثل re لبناء ومعالجة التعبيرات العادية، بالإضافة إلى json للعمل مع بيانات JSON. هذا الأخير مفيد بشكل خاص عندما تحتاج إلى استخدام واجهات برمجة تطبيقات JSON.

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

لتنزيل ملف من عنوان URL باستخدام حزمة urllib، يمكنك استدعاء urlretrieve() من وحدة urllib.request. تقوم هذه الدالة بجلب مورد ويب من عنوان URL المحدد ثم حفظ الاستجابة في ملف محلي. للبدء، قم باستيراد urlretrieve() من urlllib.request:

from urllib.request import urlretrieve

بعد ذلك، حدد عنوان URL الذي تريد استرداد البيانات منه. إذا لم تحدد مسارًا إلى ملف محلي تريد حفظ البيانات فيه، فستقوم الدالة بإنشاء ملف مؤقت لك. و نظرًا لأنك تعلم أنك ستقوم بتنزيل ملف ZIP من عنوان URL هذا، فاستمر وقدم مسارًا اختياريًا للملف الهدف:

url = (
    "https://api.worldbank.org/v2/en/indicator/"
    "NY.GDP.MKTP.CD?downloadformat=csv"
)
filename = "gdp_by_country.zip"

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

بعد ذلك، يمكنك تنزيل الملف وحفظه عن طريق استدعاء urlretrieve() وتمرير عنوان URL واسم الملف اختياريًا:

urlretrieve(url, filename)

تقوم الدالة بإرجاع مجموعة من كائنين: المسار إلى ملف الإخراج الخاص بك وكائن رسالة HTTP. عندما لا تحدد اسم ملف مخصص، فسوف ترى مسارًا إلى ملف مؤقت قد يبدو كالتالي: /tmp/tmps7qjl1tj. يمثل كائن HTTPMessage رؤوس HTTP التي يتم إرجاعها بواسطة الخادم للطلب، والتي يمكن أن تحتوي على معلومات مثل نوع المحتوى وطول المحتوى وبيانات التعريف الأخرى.

يمكنك فك المجموعة في المتغيرات الفردية باستخدام بيان المهمة والتكرار فوق الرؤوس كما لو كانت قاموس بايثون:

path, headers = urlretrieve(url, filename)
for name, value in headers.items():
    print(name, value)

قد تكون هذه المعلومات مفيدة عندما لا تكون متأكدًا من تنسيق الملف الذي قمت بتنزيله للتو وكيف من المفترض أن تفسر محتواه. في هذه الحالة، يكون ملف ZIP بحجم 128 كيلو بايت تقريبًا. يمكنك أيضًا استنتاج اسم الملف الأصلي، وهو API_NY.GDP.MKTP.CD_DS2_en_csv_v2_5551501.zip.

الآن بعد أن رأيت كيفية تنزيل ملف من عنوان URL باستخدام حزمة urllib الخاصة ببايثون، فقد حان الوقت لمعالجة نفس المهمة باستخدام مكتبة تابعة لجهة خارجية. ستكتشف الطريقة الأكثر ملاءمة لك.

استخدام مكتبة requests

على الرغم من أن urllib يعد خيارًا مدمجًا جيدًا، إلا أنه قد تكون هناك سيناريوهات حيث تحتاج إلى استخدام مكتبات الطرف الثالث لتقديم طلبات HTTP أكثر تقدمًا، مثل تلك التي تتطلب شكلاً من أشكال المصادقة. مكتبة requests هي واجهة برمجة تطبيقات شائعة وسهلة الاستخدام لتقديم طلبات HTTP في بايثون. يمكنها التعامل مع تعقيدات اتصالات الشبكة منخفضة المستوى خلف الستار.

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

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

الآن، ستنظر في استخدام مكتبة requests لتنزيل نفس ملف ZIP مع بيانات الناتج المحلي الإجمالي حسب الدولة من منصة البيانات المفتوحة للبنك الدولي. للبدء، قم بتثبيت مكتبة requests في بيئتك الافتراضية النشطة باستخدام pip:

python -m pip install requests

يقوم هذا الأمر بتثبيت أحدث إصدار من مكتبة requests في بيئتك الافتراضية. بعد ذلك، يمكنك بدء جلسة Python REPL جديدة واستيراد مكتبة  requests:

import requests

قبل المضي قدمًا، من المفيد تذكر طرق HTTP المتاحة لأن مكتبة requests تعرضها لك من خلال دوال Python. عندما تقوم بتقديم طلبات HTTP إلى خوادم الويب، فلديك طريقتان شائعتا الاستخدام للاختيار من بينهما:

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

تتيح لك طريقة POST إرسال البيانات إلى الخادم لمعالجتها أو استخدامها في إنشاء مورد أو تحديثه. في طلبات POST، يتم عادةً إرسال البيانات في نص الطلب بتنسيقات مختلفة مثل JSON أو XML، ولا تكون مرئية في عنوان URL. يمكنك استخدام طلبات POST للعمليات التي تعمل على تعديل بيانات الخادم، مثل إنشاء الموارد الموجودة أو الجديدة أو تحديثها أو إرسالها.

في هذا البرنامج التعليمي، سنستخدم فقط طلبات GET لتنزيل الملفات.

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

url = "https://api.worldbank.org/v2/en/indicator/NY.GDP.MKTP.CD"
query_parameters = {"downloadformat": "csv"}

في المثال أعلاه، قمت بتعريف نفس عنوان URL كما كان من قبل ولكن حددت المعلمة downloadformat=csv بشكل منفصل باستخدام قاموس Python. ستقوم المكتبة بإلحاق هذه المعلمات بعنوان URL بعد تمريرها إلى request.get() باستخدام وسيطة params اختيارية:

response = requests.get(url, params=query_parameters)

يؤدي هذا إلى إنشاء طلب GET لاسترداد البيانات من عنوان URL الذي تم إنشاؤه باستخدام معلمات استعلام اختيارية. تقوم الدالة بإرجاع كائن استجابة HTTP مع استجابة الخادم للطلب. إذا كنت ترغب في رؤية عنوان URL الذي تم إنشاؤه مع تضمين المعلمات الاختيارية، فاستخدم سمة .url لكائن الاستجابة:

response.url

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

>>> response.ok
True

>>> response.status_code
200

حفظ المحتوى الذي تم تنزيله إلى ملف

الآن بعد أن قمت باسترداد المحتوى من عنوان URL باستخدام مكتبة requests، يمكنك حفظه على جهاز الكمبيوتر الخاص بك محليًا. عند حفظ البيانات في ملف في بايثون، يوصى بشدة باستخدام عبارة with. فهو يضمن أن Python تدير الموارد بشكل صحيح، بما في ذلك الملفات، و غلقها تلقائيًا عندما لا تعود بحاجة إليها.

هناك عدة طرق يمكنك من خلالها الوصول إلى البيانات التي تم استردادها من مكتبة requests، اعتمادًا على نوع المحتوى. على وجه الخصوص، عندما تريد حفظ البيانات الأصلية في ملف محلي، فسوف تستخدم سمة .content لكائن الاستجابة الذي تم إرجاعه. نظرًا لأن هذه السمة تحتوي على بايتات أولية، فسوف تفتح ملفًا جديدًا في الوضع الثنائي للكتابة (‘wb‘) ثم تكتب المحتوى الذي تم تنزيله إلى هذا الملف:

>>> with open("gdp_by_country.zip", mode="wb") as file:
...     file.write(response.content)
...
128310

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

تنزيل ملف كبير بطريقة البث المباشر

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

للتغلب على هذه المشكلات، يمكنك تنزيل ملفات كبيرة بطريقة متدفقة لتجنب قراءة محتوى الردود الكبيرة مرة واحدة. تمكنك تدفقات البيانات من معالجة البيانات والتعامل معها في أجزاء يمكن التحكم فيها، مما يجعل عملية التنزيل أكثر كفاءة وتوفير الذاكرة.

يوفر تدفق البيانات أيضًا مزايا في سيناريوهات أخرى عند تنزيل الملفات في Python، مثل القدرة على:

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

لتنزيل ملف كبير بطريقة متدفقة، عليك إبقاء اتصال الطلب مفتوحًا وتنزيل رؤوس الاستجابة فقط عن طريق تعيين وسيطة الكلمة الرئيسية للدفق في دالة request.get(). تفضل وقم بتجربة مثال عن طريق تنزيل ملف ZIP كبير يبلغ حجمه حوالي 72 ميجابايت، ويحتوي على مؤشرات التنمية العالمية من منصة البيانات المفتوحة للبنك الدولي:

url = "https://databank.worldbank.org/data/download/WDI_CSV.zip"
response = requests.get(url, stream=True)

تجعل المعلمة stream=True مكتبة requests ترسل طلب GET على عنوان URL المحدد بطريقة التدفق، والتي تقوم بتنزيل رؤوس استجابة HTTP فقط أولاً. يمكنك عرض رؤوس الاستجابة هذه عن طريق الوصول إلى سمة .headers للكائن المستلم:

>>> response.headers
{'Date': 'Wed, 28 Jun 2023 12:53:58 GMT',
 'Content-Type': 'application/x-zip-compressed',
 'Content-Length': '71855385',
 'Connection': 'keep-alive',
 'Last-Modified': 'Thu, 11 May 2023 14:56:30 GMT',
 'ETag': '0x8DB522FE768EA66',
 'x-ms-request-id': '8490ea74-101e-002f-73bf-a9210b000000',
 'x-ms-version': '2009-09-19',
 'x-ms-lease-status': 'unlocked',
 'x-ms-blob-type': 'BlockBlob',
 'Cache-Control': 'public, max-age=3600',
 'x-azure-ref': '20230628T125357Z-99z2qrefc90b99ypt8spyt0dn40000...8dfa',
 'X-Cache': 'TCP_MISS',
 'Accept-Ranges': 'bytes'}

كما ترون، يخبرك أحد العناوين أن الخادم يبقي الاتصال حيًا بالنسبة لك. هذا هو اتصال HTTP المستمر، والذي يسمح لك بإرسال طلبات HTTP متعددة ضمن اتصال شبكة واحد. بخلاف ذلك، سيتعين عليك إنشاء اتصال TCP/IP جديد لكل طلب صادر، وهي عملية مكلفة وتستغرق وقتًا.

ميزة أخرى لوضع الدفق في مكتبة requests هي أنه يمكنك تنزيل البيانات في أجزاء حتى عند إرسال طلب واحد فقط. للقيام بذلك، استخدم التابع ‎.iter_content()‎ الذي يوفره كائن الاستجابة. يمكّنك هذا من تكرار بيانات الاستجابة في أجزاء يمكن التحكم فيها. بالإضافة إلى ذلك، يمكنك تحديد حجم القطعة باستخدام المعلمة chunk_size، والتي تمثل عدد البايتات التي يجب قراءتها في الذاكرة.

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

with open("WDI_CSV.zip", mode="wb") as file:
    for chunk in response.iter_content(chunk_size=10 * 1024):
        file.write(chunk)

يمكنك تحديد اسم الملف أو المسار المطلوب وفتح الملف في الوضع الثنائي (wb) باستخدام عبارة with لإدارة الموارد بشكل أفضل. بعد ذلك، يمكنك تكرار بيانات الاستجابة باستخدام Response.iter_content()، واختيار حجم قطعة اختياري، وهو 10 كيلو بايت في هذه الحالة. وأخيرًا، تكتب كل قطعة في الملف داخل الحلقة.

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

تنفيذ تنزيلات الملفات الموازية

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

  1. استخدام مجموعة من المواضيع مع مكتبة requests
  2. استخدام التنزيلات غير المتزامنة مع مكتبة aiohttp

استخدام مجموعة من المواضيع مع مكتبة requests

عندما تريد إجراء العديد من طلبات HTTP لنفس الخوادم أو لخوادم مختلفة، يمكنك الاستفادة من تعدد مؤشرات الترابط لتقليل وقت التنفيذ الإجمالي للتعليمات البرمجية الخاصة بك.

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

ستحاول الآن تنزيل ثلاثة ملفات بتنسيق ZIP بشكل متزامن من منصة البيانات المفتوحة للبنك الدولي:

  1. Total population by country
  2. GDP by country
  3. Population density by country

في هذا المثال، ستستخدم مجموعة من المواضيع مع مكتبة الطلبات لفهم كيفية إجراء تنزيلات الملفات المتوازية. أولاً، قم باستيراد فئة ThreadPoolExecutor من وحدة concurrent.futures ومكتبة requests مرة أخرى:

from concurrent.futures import ThreadPoolExecutor
import requests

بعد ذلك، اكتب الدالة التي ستنفذها داخل كل موضوع لتنزيل ملف واحد من عنوان URL محدد:

def download_file(url):
    response = requests.get(url)
    if "content-disposition" in response.headers:
        content_disposition = response.headers["content-disposition"]
        filename = content_disposition.split("filename=")[1]
    else:
        filename = url.split("/")[-1]
    with open(filename, mode="wb") as file:
        file.write(response.content)
    print(f"Downloaded file {filename}")

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

ستقوم بتنزيل ثلاثة ملفات منفصلة من نفس واجهة برمجة التطبيقات، لذا قم بإنشاء قالب URL وملء قائمة Python به:

template_url = (
    "https://api.worldbank.org/v2/en/indicator/"
    "{resource}?downloadformat=csv"
)

urls = [
    # Total population by country
    template_url.format(resource="SP.POP.TOTL"),

    # GDP by country
    template_url.format(resource="NY.GDP.MKTP.CD"),

    # Population density by country
    template_url.format(resource="EN.POP.DNST"),
]

هنا، يمكنك استدعاء التابع .format() على سلسلة القالب بأسماء موارد مختلفة تتوافق مع ملفات ZIP و بيانات CSV على خادم بعيد.

لتنزيل هذه الملفات بشكل متزامن باستخدام عدة سلاسل تنفيذ، قم بإنشاء تجمع مؤشرات ترابط جديد وقم بتعيين دالة download_file() الخاصة بك على كل عنصر من القائمة:

with ThreadPoolExecutor() as executor:
    executor.map(download_file, urls)

يمكنك استخدام عبارة with للمساعدة في إدارة الموارد لسلاسل الرسائل الخاصة بك. ضمن سياق المنفذ، يمكنك استدعاء التابع .map() مع الدالة التي تريد تنفيذها. يمكنك أيضًا تمريره تكراريًا، وهو قائمة عناوين URL في هذه الحالة. يخصص كائن المنفذ مجموعة من سلاسل الرسائل مقدمًا ويعين موضوعًا منفصلاً لكل مهمة تنزيل.

نظرًا لأن تنزيل البيانات من الشبكة يعد مهمة مرتبطة بالإدخال/الإخراج، وهي محدودة بالسرعة التي يمكن بها قراءة البيانات بدلاً من سرعة وحدة المعالجة المركزية، فإنك تحقق التوازي الحقيقي. على الرغم من قفل المترجم العالمي (GIL) الخاص ببايثون، والذي من شأنه أن يمنع ذلك، إلا أن الخيوط الموجودة في المنفذ تؤدي مهام متعددة بالتوازي، مما يؤدي إلى وقت إكمال إجمالي أسرع.

تعتبر نماذج ملفات ZIP صغيرة نسبيًا ومتشابهة في الحجم، لذا يتم الانتهاء من التنزيل في نفس الوقت تقريبًا. ولكن ماذا لو استخدمت حلقة for لتنزيل الملفات بدلاً من ذلك؟ تابع واتصل بـ download_file() مع كل عنوان URL في سلسلة المحادثات الرئيسية:

for url in urls:
    download_file(url)

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

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

هناك بعض الحالات التي قد لا يؤدي فيها تعدد مؤشرات الترابط إلى تحسين أدائك على الإطلاق. إذا كانت المشكلة الأساسية متسلسلة بطبيعتها، فلا توجد طريقة لموازنتها. علاوة على ذلك، إذا كانت مهامك تتضمن عمليات مرتبطة بوحدة المعالجة المركزية، فلن تتمكن بايثون من الاستفادة من نوى وحدة المعالجة المركزية المتعددة بسبب GIL من قبل. قد تؤدي التكلفة الإضافية لتبديل السياق إلى تقليل الأداء بالفعل!

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

استخدام مكتبة aiohttp غير المتزامنة

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

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

حزمة Python الشائعة لإجراء التنزيلات غير المتزامنة عند استرداد ملفات متعددة هي مكتبة aiohttp. إنها مبنية على أعلى وحدة asyncio في المكتبة القياسية، والتي توفر إطار عمل للبرمجة غير المتزامنة في بايثون.

تستفيد مكتبة aiohttp من ميزات التزامن الموجودة في الحزمة asyncio، مما يسمح لك بكتابة تعليمات برمجية غير متزامنة يمكنها التعامل مع طلبات متعددة بشكل متزامن. يمكن للمكتبة إجراء عمليات شبكة غير محظورة، مما يعني أنها ستسمح بتشغيل تعليمات برمجية أخرى بينما تنتظر مهمة أخرى وصول البيانات من الشبكة.

باستخدام aiohttp API مع الكلمات الأساسية  async و def و await، يمكنك كتابة تعليمات برمجية غير متزامنة تقدم طلبات HTTP متزامنة. بالإضافة إلى ذلك، تدعم مكتبة aiohttp تجمع الاتصالات، وهي ميزة تسمح للطلبات المتعددة باستخدام نفس الاتصال الأساسي. وهذا يساعد على تحسين الأداء وتحسينه.

للبدء، قم بتثبيت مكتبة aiohttp باستخدام pip في سطر الأوامر:

(venv) $ python -m pip install aiohttp

يؤدي هذا إلى تثبيت مكتبة aiohttp في بيئتك الافتراضية النشطة.

بالإضافة إلى مكتبة الطرف الثالث هذه، ستحتاج أيضًا إلى حزمة asyncio من مكتبة Python القياسية لإجراء تنزيلات غير متزامنة. لذلك، قم باستيراد كلا الحزمتين الآن:

import asyncio
import aiohttp

الخطوة التالية هي تحديد وظيفة غير متزامنة لتنزيل ملف من عنوان URL. يمكنك القيام بذلك عن طريق إنشاء مثيل aiohttp.ClientSession، الذي يحتوي على موصل مُعاد استخدامه لاتصالات متعددة. فهو يبقيها على قيد الحياة تلقائيًا لفترة زمنية معينة لإعادة استخدامها في الطلبات اللاحقة إلى نفس الخادم كلما أمكن ذلك. يؤدي ذلك إلى تحسين الأداء وتقليل العبء الناتج عن إنشاء اتصالات جديدة لكل طلب.

تقوم الدالة التالية بإجراء تنزيل غير متزامن باستخدام فئة ClientSession من حزمة aiohttp:

async def download_file(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            if "content-disposition" in response.headers:
                header = response.headers["content-disposition"]
                filename = header.split("filename=")[1]
            else:
                filename = url.split("/")[-1]
            with open(filename, mode="wb") as file:
                while True:
                    chunk = await response.content.read()
                    if not chunk:
                        break
                    file.write(chunk)
                print(f"Downloaded file {filename}")

تأخذ الوظيفة المحددة أعلاه عنوان URL كوسيطة. ثم يقوم بعد ذلك بإنشاء جلسة عميل باستخدام عبارة  async with، مما يضمن إغلاق الجلسة بشكل صحيح وتحرير الموارد بعد خروج البرنامج من كتلة التعليمات البرمجية هذه. باستخدام سياق الجلسة، يقوم بإجراء طلب HTTP GET إلى عنوان URL المحدد ويحصل على كائن الاستجابة باستخدام عبارة  async with.

داخل الحلقة اللانهائية، تقرأ البيانات في أجزاء، و تخرج من الحلقة عندما لا يكون هناك المزيد من الأجزاء. تشير الكلمة الأساسية “await” إلى أن هذه العملية غير متزامنة ويمكن تنفيذ مهام أخرى بالتوازي حتى تتوفر البيانات. بعد اشتقاق اسم الملف من كائن الاستجابة، يمكنك حفظ مجموعة البيانات التي تم تنزيلها في ملف محلي.

بعد ذلك، يمكنك إجراء تنزيلات متزامنة باستخدام الإمكانات غير المتزامنة لمكتبات aiohttp و asyncio. يمكنك إعادة استخدام الكود من مثال سابق يعتمد على تعدد مؤشرات الترابط لإعداد قائمة عناوين URL:

template_url = (
    "https://api.worldbank.org/v2/en/indicator/"
    "{resource}?downloadformat=csv"
)

urls = [
    # Total population by country
    template_url.format(resource="SP.POP.TOTL"),

    # GDP by country
    template_url.format(resource="NY.GDP.MKTP.CD"),

    # Population density by country
    template_url.format(resource="EN.POP.DNST"),
]

أخيرًا، قم بتحديد وتشغيل وظيفة main() غير المتزامنة التي ستقوم بتنزيل الملفات بشكل متزامن من عناوين URL هذه:

async def main():
    tasks = [download_file(url) for url in urls]
    await asyncio.gather(*tasks)

asyncio.run(main())

تحدد الدالة main() فهم القائمة لإنشاء قائمة من المهام، حيث تستدعي كل مهمة الدالة download_file() لكل عنوان URL في عناوين url المتغيرة العامة. لاحظ أنك قمت بتحديد الدالة باستخدام الكلمة الأساسية async لجعلها غير متزامنة. بعد ذلك، يمكنك استخدام asyncio.gather() للانتظار حتى اكتمال كافة المهام في القائمة.

يمكنك تشغيل الدالة main() باستخدام asyncio.run()، الذي يبدأ وينفذ المهام غير المتزامنة في حلقة الحدث. يقوم هذا الكود بتنزيل الملفات بشكل متزامن من عناوين URL المحددة دون منع تنفيذ المهام الأخرى.

تحديد الخيار الذي تختاره

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

أحجام الملفات للتحميل

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

عند تنزيل ملفات متعددة، يمكنك إما استخدام مكتبة requests مع مؤشرات ترابط متعددة أو مكتبة aiohttp مع تنزيلات غير متزامنة لإجراء تنزيلات متزامنة. عند استخدامه بشكل صحيح، يمكن لأي منهما تحسين الأداء وتحسين عملية التنزيل.

سهولة الاستخدام

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

على الرغم من أنه يمكنك إنشاء العديد من الوظائف باستخدام urllib، إلا أنه قد يكون من الصعب استخدامه ويتطلب المزيد من الإعداد اليدوي لحالات الاستخدام الأكثر تعقيدًا، مثل تدفق البيانات وتنزيل ملفات متعددة بالتوازي. في الواقع، هناك مكتبات خارجية ذات دوال تدعم هذه الميزات بسهولة، مثل مكتبة requests، والتي يمكنها دعم تدفق البيانات باستخدام وسيطة في التابع request.get().

ميزات إضافية ومرونة

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

يمكنك استخدام Python لأتمتة تنزيلات ملفاتك أو للحصول على تحكم ومرونة أفضل في هذه العملية. تقدم Python العديد من الخيارات لتنزيل الملفات من عناوين URL التي تلبي سيناريوهات مختلفة، مثل تنزيل ملفات كبيرة أو ملفات متعددة أو ملفات خلف صفحات الويب التي تتطلب المصادقة.


اكتشاف المزيد من بايثون العربي

اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

Scroll to Top

اكتشاف المزيد من بايثون العربي

اشترك الآن للاستمرار في القراءة والحصول على حق الوصول إلى الأرشيف الكامل.

Continue reading