بيثون - مثال برمجي: حفظ صفحات ويكيبيديا بالجملة
تختلف عادات تصفح الإنترنت من شخص لآخر، حسب احتياجاته وظروفه. في حالتي أجد نفسي بعد فترة قصيرة وقد تجمعت لدي روابط كثيرة لصفحات من موسوعة ويكيبيديا إنجليزي، أرغب في حفظها offline للرجوع إليها فيما بعد
(وتقسيمها لتصنيفات Categories، والبحث داخل متونها كلها مرة واحدة لإيجاد اسم معين أو إشارة محددة، إلخ)
وهي طريقة مفيدة جدا في دراسة الموضوعات التي تثير اهتمامك، وفي نفس الوقت تتيح لك نسخة شخصية مصغرة من الموسوعة تناسب اهتماماتك أنت المحددة، دون الاضطرار للولوج للإنترنت وخوض بحر الإنترنت الشاسع (المشتت للانتباه والمضيع للوقت) كلما أردت تذكر معلومة نسيتها.
افترض مثلا أنك تريد تذكر اسم كتاب كنت قد سمعت عنه في الماضي لكن ذاكرتك الضعيفة لا تحوي اسمه ولا اسم المؤلف، بل مجرد فكرة ضبابية عامة عن محتواه أو اسم إحدى شخصياته.. فهل الأفضل أن تبدأ بحثك على جوجل أو ويكيبيديا من الصفر (وهي عملية بحث قد تستمر لساعات وربما لا تؤدي للنتيجة المرجوة، بل وربما تفقدك تركيزك على مبتغاك وتجرك لموضوعات جانبية أخرى، أو ما يسمى "مسيرة ويكيبيدية" Wiki Walk)، أم أن تبحث في ملفاتك الشخصية المحدودة المحفوظة عندك والتي تجمعت لديك على مر السنين والتي تناسب ذوقك وما يثير اهتمامك؟
من المنطقي طبعا أن البحث في بضعة مئات من الصفحات التي توافق اهتماماتك المعتادة سيكون أفضل كثيرا من البحث في ملايين الصفحات العامة التي تشمل كل اهتمامات البشر!
هذه العادة مفيدة جدا خصوصا لو كنت من الباحثين أو المؤلفين أو المدونين أو المناظرين، أو أي نشاط ثقافي آخر يحتاج معه الشخص للرجوع للمعلومات القديمة التي مرت عليه في الماضي. أما الشخص العادي فيكتفي عادة بالبحث عن المعلومات عندما تخطر على باله أو يقابله موضوعها، ثم سرعان ما ينساها وتتطاير من ذاكرته وكأن لم تكن، ولو سألته عن تفاصيلها بعد أسبوعين مثلا سيضطر لبدء البحث عنها من جديد، بل ربما يشعر بالملل من التكرار فيترك الموضوع كله دون أن يتعلم شيئا. فالتعلم الحقيقي يتم بالتكرار والتذكرة.
والسؤال الآن: هذه الروابط كيف أحفظ صفحاتها عندي؟
الطريقة اليدوية هي أن تنسخ كل رابط على حدة ثم تلصقه في المتصفح ثم تحفظ الصفحة Save As، وهكذا. بالطبع هذه العملية "غير عملية" بالمرة عندما تكون قائمة الروابط كبيرة. هنا يأتي دور الأكواد البرمجية التي تقوم بـ "أتمتة" العمليات المملة Automate the Boring Stuff
لكن قبل اللجوء للحل البرمجي هناك حل أيسر، وهو إضافات جاهزة في المتصفحات، مثل Firefox Add-ons، والتي بإمكانها أن تأخذ ملفا نصيا به كل الروابط ثم تقوم بالنيابة عنك بتحميل وحفظ كل الصفحات. المشكلة في كثير من هذه البرامج الجاهزة أنها لا تحفظ الصفحة بالاسم السليم عندما يكون الرابط من ويكيبيديا، وذلك لأنها لا تفتح الصفحة في المتصفح بل تذهب مباشرة لسرفرات الموقع وتنقل محتوى الصفحة لجهازك. وللأسف فإن الطريقة التي تستخدمها مواقع الويكي بشكل عام لتسمية ملفاتها داخليا غير مفيدة. فأحيانا تكون كل الملفات لها نفس الاسم (page.html مثلا) ولا يظهر الاسم الصحيح إلا بعد إظهار الصفحة داخل المتصفح. وأحيانا يكون الاسم الداخلي محذوفا منه المسافات الفاصلة بين الكلمات (كما هو الحال عند حفظ صفحات من موقع TvTropes مثلا)، وغالبا تقوم البرامج المساعدة هذه بحفظ الصفحة بطريقة ما يسمى Percent-encoding أي تحويل كل الرموز الواردة في العنوان إلى مقابلها التشفيري (والذي قد يظهر لك كأنه رموز عشوائية فيها علامة النسبة المئوية %)
ما أريده هو: عندما أعطي البرنامج رابطا مثل هذا
(وتقسيمها لتصنيفات Categories، والبحث داخل متونها كلها مرة واحدة لإيجاد اسم معين أو إشارة محددة، إلخ)
وهي طريقة مفيدة جدا في دراسة الموضوعات التي تثير اهتمامك، وفي نفس الوقت تتيح لك نسخة شخصية مصغرة من الموسوعة تناسب اهتماماتك أنت المحددة، دون الاضطرار للولوج للإنترنت وخوض بحر الإنترنت الشاسع (المشتت للانتباه والمضيع للوقت) كلما أردت تذكر معلومة نسيتها.
افترض مثلا أنك تريد تذكر اسم كتاب كنت قد سمعت عنه في الماضي لكن ذاكرتك الضعيفة لا تحوي اسمه ولا اسم المؤلف، بل مجرد فكرة ضبابية عامة عن محتواه أو اسم إحدى شخصياته.. فهل الأفضل أن تبدأ بحثك على جوجل أو ويكيبيديا من الصفر (وهي عملية بحث قد تستمر لساعات وربما لا تؤدي للنتيجة المرجوة، بل وربما تفقدك تركيزك على مبتغاك وتجرك لموضوعات جانبية أخرى، أو ما يسمى "مسيرة ويكيبيدية" Wiki Walk)، أم أن تبحث في ملفاتك الشخصية المحدودة المحفوظة عندك والتي تجمعت لديك على مر السنين والتي تناسب ذوقك وما يثير اهتمامك؟
من المنطقي طبعا أن البحث في بضعة مئات من الصفحات التي توافق اهتماماتك المعتادة سيكون أفضل كثيرا من البحث في ملايين الصفحات العامة التي تشمل كل اهتمامات البشر!
هذه العادة مفيدة جدا خصوصا لو كنت من الباحثين أو المؤلفين أو المدونين أو المناظرين، أو أي نشاط ثقافي آخر يحتاج معه الشخص للرجوع للمعلومات القديمة التي مرت عليه في الماضي. أما الشخص العادي فيكتفي عادة بالبحث عن المعلومات عندما تخطر على باله أو يقابله موضوعها، ثم سرعان ما ينساها وتتطاير من ذاكرته وكأن لم تكن، ولو سألته عن تفاصيلها بعد أسبوعين مثلا سيضطر لبدء البحث عنها من جديد، بل ربما يشعر بالملل من التكرار فيترك الموضوع كله دون أن يتعلم شيئا. فالتعلم الحقيقي يتم بالتكرار والتذكرة.
والسؤال الآن: هذه الروابط كيف أحفظ صفحاتها عندي؟
الطريقة اليدوية هي أن تنسخ كل رابط على حدة ثم تلصقه في المتصفح ثم تحفظ الصفحة Save As، وهكذا. بالطبع هذه العملية "غير عملية" بالمرة عندما تكون قائمة الروابط كبيرة. هنا يأتي دور الأكواد البرمجية التي تقوم بـ "أتمتة" العمليات المملة Automate the Boring Stuff
لكن قبل اللجوء للحل البرمجي هناك حل أيسر، وهو إضافات جاهزة في المتصفحات، مثل Firefox Add-ons، والتي بإمكانها أن تأخذ ملفا نصيا به كل الروابط ثم تقوم بالنيابة عنك بتحميل وحفظ كل الصفحات. المشكلة في كثير من هذه البرامج الجاهزة أنها لا تحفظ الصفحة بالاسم السليم عندما يكون الرابط من ويكيبيديا، وذلك لأنها لا تفتح الصفحة في المتصفح بل تذهب مباشرة لسرفرات الموقع وتنقل محتوى الصفحة لجهازك. وللأسف فإن الطريقة التي تستخدمها مواقع الويكي بشكل عام لتسمية ملفاتها داخليا غير مفيدة. فأحيانا تكون كل الملفات لها نفس الاسم (page.html مثلا) ولا يظهر الاسم الصحيح إلا بعد إظهار الصفحة داخل المتصفح. وأحيانا يكون الاسم الداخلي محذوفا منه المسافات الفاصلة بين الكلمات (كما هو الحال عند حفظ صفحات من موقع TvTropes مثلا)، وغالبا تقوم البرامج المساعدة هذه بحفظ الصفحة بطريقة ما يسمى Percent-encoding أي تحويل كل الرموز الواردة في العنوان إلى مقابلها التشفيري (والذي قد يظهر لك كأنه رموز عشوائية فيها علامة النسبة المئوية %)
ما أريده هو: عندما أعطي البرنامج رابطا مثل هذا
https://en.wikipedia.org/wiki/A_Midsummer_Night%27s_Dream_(1999_film)
فإن عليه أن يحفظ الملف بحيث يكون اسمه مثل هذا
A Midsummer Night's Dream (1999 film) - Wikipedia.html
كل هذه المشاكل تحتاج حلا إن كنت تريد تجميع موسوعتك الصغيرة النظيفة، ذات الأسماء المقروءة الواضحة والتي ستساعدك فيما بعد في البحث بسهولة عن مرادك.
فما الحل؟
اكتب كودك البرمجي القصير المناسب لاحتياجاتك، وشغله كلما تجمعت لديك قائمة جديدة من الروابط.
لكن أي لغة برمجية ستختار؟.. البعض قد يفضل الاختيار السهل جدا، وهو أوامر Shell المشهورة، والبعض قد يختار لغة قوية ومعقدة مثل الجافا أو مشتقات الـ C، لكن في حالتي فأنا لا أعرف سوى اللغة المتوسطة بين الطرفين، أي لغة البَيثون Python، والمصممة بحيث تكون مفهومة لمن يفهم الإنجليزية، وبها مكتبات برمجية مساعدة كثيرة جدا ومفيدة للغاية ولها شروح واضحة. وهي بشكل عام مشهورة جدا بأنها لغة "جميلة"، أي الاسكريبتات المكتوبة بها تظهر واضحة ومقروءة ومنطقية، وكأنها ما يسمى Pseudo-code أي الأكواد البرمجية التي يستخدمها المدرسون لشرح أصول البرمجة العامة للطلبة دون تعقيدات.. لكن الفرق هنا هو أن الـ سودو-كود لا يعمل فعليا (مجرد أداة شرح) أما بيثون فأكوادها البسيطة قابلة للتنفيذ وقوية دون تعقيد!
اكتب كودك البرمجي القصير المناسب لاحتياجاتك، وشغله كلما تجمعت لديك قائمة جديدة من الروابط.
لكن أي لغة برمجية ستختار؟.. البعض قد يفضل الاختيار السهل جدا، وهو أوامر Shell المشهورة، والبعض قد يختار لغة قوية ومعقدة مثل الجافا أو مشتقات الـ C، لكن في حالتي فأنا لا أعرف سوى اللغة المتوسطة بين الطرفين، أي لغة البَيثون Python، والمصممة بحيث تكون مفهومة لمن يفهم الإنجليزية، وبها مكتبات برمجية مساعدة كثيرة جدا ومفيدة للغاية ولها شروح واضحة. وهي بشكل عام مشهورة جدا بأنها لغة "جميلة"، أي الاسكريبتات المكتوبة بها تظهر واضحة ومقروءة ومنطقية، وكأنها ما يسمى Pseudo-code أي الأكواد البرمجية التي يستخدمها المدرسون لشرح أصول البرمجة العامة للطلبة دون تعقيدات.. لكن الفرق هنا هو أن الـ سودو-كود لا يعمل فعليا (مجرد أداة شرح) أما بيثون فأكوادها البسيطة قابلة للتنفيذ وقوية دون تعقيد!
وبدلا من أن أكتفي بإعطائك نسخة من البرنامج النهائي، سأستغل هذه المناسبة لتعريفك عمليا بلغة البايثون. بعض الناس يحبون طريقة التعلم بالأمثلة العملية المفيدة، ويملون من الدروس النظرية.
البرنامج (أو الاسكربت Script كما يقال في اللغات التي تشبه البيثون) لا يتعدى الـ 15 أو العشرين سطرا، وما يفعله هو:
- يقرأ ملفا نصيا Text File فيه الروابط المجمعة
محتوى الملف سيشبه هذا:
- يقرأ ملفا نصيا Text File فيه الروابط المجمعة
محتوى الملف سيشبه هذا:
https://en.wikipedia.org/wiki/OpenTTD
https://en.wikipedia.org/wiki/Florida_Straits_(film)
https://en.wikipedia.org/wiki/ReStructuredText
- يأخذ كل سطر/رابط، بالترتيب، ويتعامل معه على انفراد
- يستعين بمكتبة برمجية مساعدة (تأتي جاهزة مع لغة البيثون) في تحميل محتوى صفحة الإنترنت التي يشير لها الرابط
(تحدث هذه العملية في الخلفية، دون أن تلاحظها أنت)
- يفتح ملفا جديدا فارغا ويحفظ فيه محتوى هذه الصفحة
- لكن قبل الخطوة السابقة سيكون عليه أن يستعين بمكتبة برمجية أخرى (لا تأتي مع بيثون، بل هي مكتبة منفصلة شهيرة جدا وشائعة الاستخدام) في دراسة سريعة لمحتوى الصفحة، لاستخلاص اسمها المناسب والصحيح. بهذه الخطوة سيمكنه أن يحفظ الصفحة في الملف الجديد باسم منطقي وصحيح، خالي من الرموز الغريبة، ودون أن يحذف المسافات بين الكلمات، ويتأكد أن ملف الصفحة المحفوظ سيكون له الامتداد Extension الصحيح (أي html)
- تحسبا لبعض المشاكل التي تحدث أحيانا عند التعامل مع الروابط، سيكون على البرنامج أيضا - عندما يجد الصفحة المطلوبة محذوفة مثلا ولم تعد موجودة على الإنترنت، أو عندما تكون أنت قد أخطأت إملائيا أثناء كتابة عنوان الرابط، أو ربما عندما يكون بالرابط رموزا خاصة يصعب على المكتبة البرمجية التعامل معها - أن يتجاوز هذا الرابط المشكل، وأن يتعامل مع الذي يليه، لا أن يتوقف البرنامج كله عن العمل!
هذه المشاكل تسمى Exceptions أو استثناءات، ويمكنك التعامل معها وحلها بقليل من الجهد دون الحاجة لتجاوزها، لكني هنا أكتب سكريبتا قصيرا سريعا ولن أخوض في الحالات الاستثنائية النادرة.
مثال: ماذا لو كانت صفحة ويكيبيديا التي تريدها هي صفحة معلومات عن الفيلم المسمى V/H/S ؟
https://en.wikipedia.org/wiki/V/H/S
لاحظ أن اسم الصفحة نفسه يحوى رمز "/" (سلاش) والذي يستخدم أيضا في روابط الإنترنت بشكل عام للإشارة للمجلدات الفرعية! كيف سيفهم البرنامج البسيط الفارق بين الاثنين؟! يمكن أن نقوم بعمل escape للرمز لكنه موضوع فرعي ولن يهمنا هنا الآن، لأنه مثال نادر كما قلت.
والآن، كيف سنكتب البرنامج؟
والآن، كيف سنكتب البرنامج؟
أولا لنفترض أن ملفك النصي الذي يحوي الروابط اسمه MyFile.txt
فعلينا أن نقنع لغة البيثون بأن تفتح هذا الملف، باستخدام الأمر البرمجي open
فنقول مثلا:
فنقول مثلا:
open('MyFile.txt'):
لاحظ أننا وضعنا اسم الملف داخل علامتي تنصيص. لماذا؟ لأن هذه الأمر - في صورته البدائية البسيطة هذه - يتطلب أن يكون اسم الملف في صورة نصية، وأن يكون موجودا في نفس المجلد الذي يوجد فيه اسكربت البرنامج الذي تكتبه. طبعا هذا التعميم خاطئ من جانبي، لأن لغة بيثون قوية جدا وبكل سهولة يمكنك أن تأمرها بفتح الملف حتى لو كان موجودا في مجلد آخر بل وفي جهاز كمبيوتر آخر.. ويمكنك أيضا ألا تستخدم علامات التنصيص إن كان اسم الملف موضوعا في متغير Variable قمت بتعريفه مسبقا.. لكن كل هذه التفاصيل لن تعنينا الآن، فأنا أفترض أنك جديد تماما على عالم البرمجة ولا تعرف ما هي المتغيرات ولا تعرف كيف تتعامل مع (مسارات Paths) المجلدات والملفات.
والآن علينا أن نخبر بيثون أن يقرأ الروابط كلها مرة واحدة في ذاكرته، تمهيدا لأن يتعامل معها فيما بعد سطرا سطرا.
ولهذا سنستخدم الأمر
والآن علينا أن نخبر بيثون أن يقرأ الروابط كلها مرة واحدة في ذاكرته، تمهيدا لأن يتعامل معها فيما بعد سطرا سطرا.
ولهذا سنستخدم الأمر
readlines()
لاحظ أن البرمجة ما هي إلا طريقة لإخبار الحاسوب بما تريده أن يفعل. فهي في حقيقتها مجرد كلمات إقناعية.
مشكلة الكمبيوتر أنه عديم الذكاء، وبالتالي يحتاج أن تكون كل أوامرك ورغباتك مكتوبة بالتفصيل، وبطريقة محددة يفهمها هو.
الجميل في لغة بيثون أنها وسيطة بينك وبين الكمبيوتر، فهي تقنعه برغباتك لكنها أيضا في نفس الوقت مكتوبة بلغة إنجليزية سهلة تفهمها أنت! لاحظ مثلا أن أمر قراءة السطور يقول بكل وضوح وصراحة readlines (أي: اقرأ السطور)! بينما اللغات البرمجية الأخرى تعقد المسألة أكثر من اللازم.
والآن علينا أن نخبره أن يمر بالترتيب على كل سطر، ثم أن يلتقط محتوى صفحته من الإنترنت
"المرور بالترتيب" هذا يسمى loop (أي حلقة) وأمره مشترك بين أكثر اللغات البرمجية في العالم، ويسمى for
بينك وبين نفسك يمكن أن تتذكره بالتعبير الإنجليزي for each ، أي: لكل واحد. أي أننا نقول: لكل واحد من هذه السطور قم بتنفيذ أمر جديد (هو هنا أمر الالتقاط من الإنترنت)
لتنفيذ الالتقاط سنستخدم المكتبة البرمجية البيثونية المسماة urlopen
لكن قبل استخدامها علينا استيرادها أولا. فلتخفيف الحمل على البرامج فإن لغات البرمجة لا تقوم باستيراد كل المكتبات المساعدة من البداية، لأن أغلبها لن يفيدك في برامجك الصغيرة، وإلا أصبحت حملا على الذاكرة وتبطئ البرنامج بشكل عام، ولهذا عليك أن تكتب في رأس الاسكربت أنك ستحتاج المكتبة الفلانية.
أمر الاستيراد هذا هو import
(لكن هذه المكتبة المتخصصة في التحميل من الإنترنت موجودة ضمن حزمة مكتبات أخرى، ولهذا علينا تحديد الأمر بصورة أوضح قليلا، فنقول
بينك وبين نفسك يمكن أن تتذكره بالتعبير الإنجليزي for each ، أي: لكل واحد. أي أننا نقول: لكل واحد من هذه السطور قم بتنفيذ أمر جديد (هو هنا أمر الالتقاط من الإنترنت)
لتنفيذ الالتقاط سنستخدم المكتبة البرمجية البيثونية المسماة urlopen
لكن قبل استخدامها علينا استيرادها أولا. فلتخفيف الحمل على البرامج فإن لغات البرمجة لا تقوم باستيراد كل المكتبات المساعدة من البداية، لأن أغلبها لن يفيدك في برامجك الصغيرة، وإلا أصبحت حملا على الذاكرة وتبطئ البرنامج بشكل عام، ولهذا عليك أن تكتب في رأس الاسكربت أنك ستحتاج المكتبة الفلانية.
أمر الاستيراد هذا هو import
(لكن هذه المكتبة المتخصصة في التحميل من الإنترنت موجودة ضمن حزمة مكتبات أخرى، ولهذا علينا تحديد الأمر بصورة أوضح قليلا، فنقول
from urllib.request import urlopen
أي: من حزمة المكتبات المتعلقة بالإنترنت وطلباته عليك يا بيثون أن تستورد لي المكتبة الداخلية الصغيرة المتخصصة في فتح روابط الإنترنت
وبالمرة أيضا، ما دمنا تكلمنا عن استيراد المكتبات، فلنستورد أيضا المكتبة الخارجية التي تكلمنا عنها، والتي تدرس محتوى الصفحة وتستخلص الاسم الصحيح للصفحة من داخل غابة أكواد الـ HTML التي توجد في الغالب داخل كل صفحات الإنترنت هذه الأيام. (في الماضي كانت صفحات الإنترنت بسيطة، وأكواد الـ HTML – التي يحولها المتصفح في الخلفية للصفحات المقروءة التي تراها أنت - كانت مفهومة للهواة. أما اليوم فعالم بناء صفحات الإنترنت معقد جدا، وبه لغات كثيرة متداخلة، فتجد مثلا HTML5 لعرض الميديا، وتجد CSS وجافا-سكربت للتعامل مع الألوان والأزرار، والأجزاء المتحركة والأجزاء التي تظهر وتختفي والتي تقوم بإعادة تحميل نفسها تلقائيا لعرض المنشورات الجديدة، إلخ)
اسم هذه المكتبة الخارجية هو "الحساء الجميل"! وهي تقوم بالعمليات القذرة هذه بالنيابة عنك، وتغنيك عن الغوص في أكواد صفحات الإنترنت.
سنستوردها بهذا الأمر:
from bs4 import BeautifulSoup
والآن سنفتح ملفا فارغا ونضع فيه محتوى الصفحة الملتقطة ثم سنحفظه تحت اسمه الصحيح المستخلص. وإن واجهتنا مشكلة مع الرابط سنتجاوزها كما قلنا.
الآن علينا الكلام عن المتغيرات. المتغير هو مجرد اسم يحتفظ لك بشيء. أي أنه يشير لمحتوى هذا الشيء بكلمة مختصرة، تماما كما نفعل في الجبر عندما نستخدم حرف "س" مثلا للإشارة للمحصلة النهائية للمعادلة. فربما تكون المعادلة معقدة وفيها حسابات كثيرة، لكن في النهاية علينا أن نجد النتيجة النهائية وأن نحتفظ بها مؤقتا إلى أن نحتاجها.
فلو قلنا مثلا أن
x = 5 * 2
فإن المتغير المسمى "إكس" سيحتفظ لنا بنتيجة هذه الحسبة، أي أن بيثون سيضرب الرقم 5 في الرقم 2 ثم سيضع النتيجة (أي: 10) في متغير اسمه "إكس"
وفيما بعد كل ما عليك هو أن تشير لهذا المتغير مرة أخرى، دون الحاجة لتكرار العملية الحسابية.
ولهذا عندما فتحنا الملف النصي الأصلي وقرأنا ما فيه من سطور، كان يجب علينا ساعتها أن نخزن هذه السطور في متغير، لأننا سنحتاجها فيما بعد عندما نمر عليها واحدا واحدا بالترتيب لالتقاط محتواها.
lines = f.readlines()
المتغير الذي اخترعناه هنا جعلنا اسمه lines، واحتفظنا داخله بالسطور المقروءة
لكن ما هذه الـ f التي أضفناها؟
أثناء فتح الملف النصي يمكن أن نضعه هو أيضا في متغير. في بيثون وسيلة آمنة للتعامل مع فتح الملفات، واسمها طريقة with
فنقوم باستخدام طريقة with هذه مع أمر open المعتاد، حتى نضمن أن الملف الذي فتحناه سيتم إغلاقه بعد أن ننتهي من التعامل معه. الكثير من المبرمجين ينسون خطوة إغلاق الملفات المفتوحة، مما كان يسبب مشاكل في أنظمة التشغيل عندما يظل الملف مفتوحا في الخلفية، ولهذا أضاف مصممو لغة بيثون هذه الطريقة الآمنة للغلق التلقائي للملفات، فلم نعد في حاجة لأمر close
وميزة هذه الطريقة أنها تفتح الملف، وتعطيه متغيرا في نفس السطر. وقد اخترت هنا أن أطلق على هذا المتغير المؤقت اسم حرف الإف، اختصارا لكلمة file
كيف نستخدم طريقة الفتح الآمن هذه؟
لكن ما هذه الـ f التي أضفناها؟
أثناء فتح الملف النصي يمكن أن نضعه هو أيضا في متغير. في بيثون وسيلة آمنة للتعامل مع فتح الملفات، واسمها طريقة with
فنقوم باستخدام طريقة with هذه مع أمر open المعتاد، حتى نضمن أن الملف الذي فتحناه سيتم إغلاقه بعد أن ننتهي من التعامل معه. الكثير من المبرمجين ينسون خطوة إغلاق الملفات المفتوحة، مما كان يسبب مشاكل في أنظمة التشغيل عندما يظل الملف مفتوحا في الخلفية، ولهذا أضاف مصممو لغة بيثون هذه الطريقة الآمنة للغلق التلقائي للملفات، فلم نعد في حاجة لأمر close
وميزة هذه الطريقة أنها تفتح الملف، وتعطيه متغيرا في نفس السطر. وقد اخترت هنا أن أطلق على هذا المتغير المؤقت اسم حرف الإف، اختصارا لكلمة file
كيف نستخدم طريقة الفتح الآمن هذه؟
with open('MyFile.txt') as f:
lines = f.readlines()
لاحظ وجود أربع مسافات فارغة قبل بداية السطر الثاني. لغة بايثون تهتم جدا بموضوع الاستخدام السليم للمسافات الفارغة indentation، لأنها تجعل الاسكربت مفهوما للبشر، أي تحسن "المقروئية" Readability
ففي هذا المثال نعلم أن الفتح والغلق الآمن للملف هو عملية تحوي داخلها أمر قراءة سطوره، وبالتالي "أدخلنا" أمر readlines لليمين قليلا، لأنه أصبح أمرا فرعيا متفرعا عن طريقة with، وأصبحت طريقة with تشمله وتحتضنه داخلها.
وبعد الانتهاء من عملية الفتح الآمن ثم قراءة السطور وتخزينها في متغير اسمه lines ثم الغلق الآمن، يمكن أن نبدأ السطر الثالث من البداية الطبيعية، دون مسافات أولية، لأنه مرحلة جديدة.
والآن انظر للبرنامج حتى الآن
from urllib.request import urlopen
from bs4 import BeautifulSoup
with open('MyFile.txt') as f:
lines = f.readlines()
for line in lines:
page = urlopen(line).read() #binary
سطران لاستيراد المكتبات المساعدة، وسطر للفتح الآمن للملف النصي، وسطر لقراءة السطور وتخزينها مؤقتا في متغير، ثم نبدأ حلقة المرور التدريجي على السطور المخزنة، ثم ننفذ أمر "الالتقاط من الإنترنت" على كل سطر، و”نقرأ” هذا المحتوى القادم إلينا من الإنترنت ونخزن المحصلة النهائية لهذه القراءة في متغير أسميناه "صفحة page"
ممتاز. كل شيء واضح حتى الآن. لكن ما هذه الكلمة الموضوع بذيل آخر سطر، والتي تبدأ بعلامة الهاشتاج؟
هي إضافة من عندي، بلا قيمة للبرنامج نفسه، وبيثون عندما يرى علامة الـ # يتجاهل ما يليها إلى أن ينتهي السطر. فيمكن أن نحذفها وأن يكون السطر بهذه الصورة:
page = urlopen(line).read()
فلماذا إذن وضعتها في الاسكربت البرمجي الذي أكتبه؟ إنها ملاحظة شخصية لي أنا، أي "تعليق Comment" كي لا أنسى معلومة معينة متعلقة بهذا السطر البرمجي.
مشكلة المبرمجين الذين لا يضعون هذه التعليقات هي أنهم يفاجئون بعد بضعة أيام أنهم لم يعودوا يفهمون ما كتبوه! فالإنسان ينسى بسهولة، خصوصا عندما يتعلق الأمر بالبرمجة ولغات الكمبيوتر، ولهذا ينصح المحترفون بأن تضع تعليقات مثل هذه لتذكر نفسك بالمشكلات التي قمت بحلها، ولماذا مثلا اخترت الأمر البرمجي المستخدم وتركت أمرا آخر يقوم بوظيفة مشابهة.
في هذه الحالة أنا أقوم بتذكير نفسي أن بيثون عندما يلتقط محتوى الصفحة من الإنترنت باستخدام المكتبة البرمجية urlopen ثم يقرأ محتواها ويخزنه داخل المتغير المسمى page فإن نوع هذا المحتوى لا يكون نصا (أي String) كما كنت أتوقع، بل يخزنه في صورة ترميز ثنائي Binary
الفروق بين النصي والثنائي ربما لن تهمك الآن في هذه المرحلة المبكرة من التعرف على البرمجة، لكنها مهمة في حالتنا هذه، لأننا بعد بضعة سطور سنقوم بحفظ هذا المحتوى في ملف فارغ جديد، وبالتالي يجب أن نكون على علم بنوعية هذا المحتوى الذي سنضعه داخل الملف. فلو افترضنا أنه محتوى نصي ثم اتضح أنه ثنائي فسيحدث خطأ أثناء عملية الحفظ، لأن كل محتوى يجب أن يوضع في الوعاء المناسب له.
لكن كيف عرفت أن مكتبة urlopen تخرج لنا المحتوى الإنترنتي بصورة ثنائية لا نصية؟ الجواب: عندما حدث الخطأ، رجعت لملفات المساعدة المتعلقة بهذه المكتبة.
بيثون له وثائق Documentation وشروحات مستفيضة لكل أوامره ومكتباته، كتبها متطوعون، وتحميلها مجاني (مثل البيثون نفسه) وحجمها صغير وتنسيقها ممتاز.
الآن أصبح عندنا الصفحة مخزنة في متغير اسمه page، وعلينا أن نجعل المكتبة المساعدة الثانية (الحساء الجميل) تتعامل مع محتوى الصفحة وتستخلص منه الاسم السليم.
للعلم، الاسم داخل أكواد صفحات الـ HTML يكون موجودا داخل (وسم tag) اسمه title. لا نحتاج لمعرفة هذه المعلومة، لأن المكتبة ستتعامل بنفسها مع الأكواد، وستبحث عما نريد. كل ما علينا هو أن نسألها أن تستخلص لنا الاسم
soup = BeautifulSoup(page, "lxml")
page_title = soup.title.string
ما فعلناه هنا هو أننا قلنا لبرنامج الحساء: سنعطيك صفحة إنترنت قمنا بتحميلها ووضعناها في متغير اسمه "بيج"، عليك أن تدرسها، واعلم أنها مكتوبة بأكواد HTML (للدقة، قلنا له أنها مكتوبة بأكواد lxml، لأنها أشمل وأوسع من الـ html، وهو يفهم ما نقصده). وقلنا له أن يخزن نتيجة هذه الدراسة في متغير مؤقت اسمه soup (يمكن أن نختار أي اسم نريد)، ثم في السطر التالي أمرناه بأن ينظر لهذا الفحص والدراسة وأن يعطينا اسم الصفحة (في صورة نصية)، وأن يخزن هذا الاسم في متغير اسمه "عنوان الصفحة"
الآن أصبح عندنا كل ما نحتاجه للخطوة النهائية: عندنا محتوى الصفحة، ونعرف أنه بصيغة ثنائية، وعندنا أيضا الاسم الصحيح للصفحة.
فلنفتح إذن ملفا فارغا ونضع فيه المحتوى ونسميه التسمية اللائقة الصحيحة، ثم نحفظه في مجلد على الجهاز
with open(page_title + ".html", 'wb') as the_file:
the_file.write(page)
ماذا فعلنا هنا؟ فتحنا ملفا فارغا وأعطيناه مؤقتا اسم المتغير the_file كي يسهل التعامل معه في السطر التالي.
لكن ربما يخطر على بالك سؤال: لقد استخدمنا نفس أمر الفتح المعتاد والذي كنا استخدمناه سابقا لفتح الملف النصي الأصلي.. فكيف عرف بيثون هذه المرة أننا لا نقصد فتح ملف موجود بالفعل بل أننا نريد إنشاء ملف جديد فارغ؟
ج: عرف عندما أضفنا حرف الـ "w" للأمر. فنحن هنا نقول له: اكتب write
هذه الإضافة الصغيرة أخبرت البيثون بأننا لا نقصد مجرد الفتح المعتاد للقراءة، بل نريد كتابة شيء.
لاحظ أيضا أن الـ w يرافقها هنا حرف b. وقد أضفناه لأننا نتعامل مع محتوى Binary ثنائي. لو كان المحتوى نصيا فما كنا ساعتها سنحتاج لهذه الإضافة لأن بيثون سيفترض تلقائيا أن المحتوى نصي في العادة.
لكن أين اسم الملف؟ اسم الملف موجود في المتغير page_title كما قلنا وكما استخلصناه، لكننا أضفنا أيضا امتداد الملف (أي وضعنا نقطة في نهاية الاسم المستخلص ثم أضفنا عليها أيضا امتداد صفحات الإنترنت html)
علامة الزائد + تجمع بين النصوص وتخرج لنا نصا جديدا. في هذه الحالة فإننا أخذنا اسم الصفحة وألصقنا به امتدادا مناسبا.
في السطر الثاني قلنا لبيثون: هذا الملف الفارغ الجديد الذي أنشأته وأعطيته الاسم المناسب، اكتب داخله المحتوى الذي كنا قد التقطناه في السابق من الإنترنت وكنا خزناه مؤقتا في المتغير الذي أسميناه page
بهذا نكون قد انتهينا من الأجزاء الأساسية للبرنامج. لكن ماذا عن حالات الأخطاء والاستثناءات؟ ربما من الأفضل أن نتعامل معها.
قلنا أن الأخطاء في بيثون اسمها exceptions، وكما أن بيثون عنده طريقة آمنة لفتح وغلق الملفات، كذلك عنده طريقة آمنة للتعامل مع الأخطاء، واسمها try
فما علينا فعله هو: أن نحيط السطور "المحتمل تسببها في مشاكل" بطريقة الـ try هذه، فيقوم بيثون بتجريب السطر أولا، فإن وجد مشكلة (في هذه الحالة المشكلة هي عجز مكتبة urlopen عن التقاط الملف من الإنترنت لأي سبب من الأسباب) فإنه لن يسارع بإنهاء البرنامج كله كالعادة، بل سيتجاوز هذا السطر عندما يمر عليه بالترتيب في حلقة الـ for
هذا "التجاوز" اسمه continue، أي: "استمر" من البداية ودعك من هذا السطر المزعج. أي أنه سيستأنف العمل دون توقف
(لو أردنا منه أن يتوقف عن عملية المرور التدريجي على سطور الحلقة لكنا استخدمنا الأمر break، أي: اكسر الحلقة واخرج منها)
لكن ربما يخطر على بالك سؤال: لقد استخدمنا نفس أمر الفتح المعتاد والذي كنا استخدمناه سابقا لفتح الملف النصي الأصلي.. فكيف عرف بيثون هذه المرة أننا لا نقصد فتح ملف موجود بالفعل بل أننا نريد إنشاء ملف جديد فارغ؟
ج: عرف عندما أضفنا حرف الـ "w" للأمر. فنحن هنا نقول له: اكتب write
هذه الإضافة الصغيرة أخبرت البيثون بأننا لا نقصد مجرد الفتح المعتاد للقراءة، بل نريد كتابة شيء.
لاحظ أيضا أن الـ w يرافقها هنا حرف b. وقد أضفناه لأننا نتعامل مع محتوى Binary ثنائي. لو كان المحتوى نصيا فما كنا ساعتها سنحتاج لهذه الإضافة لأن بيثون سيفترض تلقائيا أن المحتوى نصي في العادة.
لكن أين اسم الملف؟ اسم الملف موجود في المتغير page_title كما قلنا وكما استخلصناه، لكننا أضفنا أيضا امتداد الملف (أي وضعنا نقطة في نهاية الاسم المستخلص ثم أضفنا عليها أيضا امتداد صفحات الإنترنت html)
علامة الزائد + تجمع بين النصوص وتخرج لنا نصا جديدا. في هذه الحالة فإننا أخذنا اسم الصفحة وألصقنا به امتدادا مناسبا.
في السطر الثاني قلنا لبيثون: هذا الملف الفارغ الجديد الذي أنشأته وأعطيته الاسم المناسب، اكتب داخله المحتوى الذي كنا قد التقطناه في السابق من الإنترنت وكنا خزناه مؤقتا في المتغير الذي أسميناه page
بهذا نكون قد انتهينا من الأجزاء الأساسية للبرنامج. لكن ماذا عن حالات الأخطاء والاستثناءات؟ ربما من الأفضل أن نتعامل معها.
قلنا أن الأخطاء في بيثون اسمها exceptions، وكما أن بيثون عنده طريقة آمنة لفتح وغلق الملفات، كذلك عنده طريقة آمنة للتعامل مع الأخطاء، واسمها try
فما علينا فعله هو: أن نحيط السطور "المحتمل تسببها في مشاكل" بطريقة الـ try هذه، فيقوم بيثون بتجريب السطر أولا، فإن وجد مشكلة (في هذه الحالة المشكلة هي عجز مكتبة urlopen عن التقاط الملف من الإنترنت لأي سبب من الأسباب) فإنه لن يسارع بإنهاء البرنامج كله كالعادة، بل سيتجاوز هذا السطر عندما يمر عليه بالترتيب في حلقة الـ for
هذا "التجاوز" اسمه continue، أي: "استمر" من البداية ودعك من هذا السطر المزعج. أي أنه سيستأنف العمل دون توقف
(لو أردنا منه أن يتوقف عن عملية المرور التدريجي على سطور الحلقة لكنا استخدمنا الأمر break، أي: اكسر الحلقة واخرج منها)
لكن كيف سأعلم أنا أنه حدث خطأ وتم تجاوزه؟ هل سأضطر للنظر في عدد سطور الملف النصي الأصلي ثم النظر في عدد الملفات الجديدة التي نجح الاسكربت في حفظها، ثم أقارن العددين فأعرف عدد الروابط التي فشلت؟
عملية مملة ومرهقة.. ثم كيف سأعرف بالتحديد الروابط التي فشلت؟
أحد الحلول السهلة هو أن أجعل البيثون - عندما يواجه خطأ استثنائيا - أن يكتب اسمه قبل أن يتجاوزه للذي يليه.
إن كنت أقوم بشتغيل اسكربت البيثون من الطرفية terminal فسأجد الاسم معروضا أمامي.
ربما فيما بعد، إن قمنا بتحسين هذه الاسكربت يمكننا أن نخزن هذه الروابط الفاشلة في متغير اسمه problems مثلا، ثم نحفظه في ملف نصي! لكن دعنا من هذه التفاصيل التجميلية الآن، ولنكتفي بعرض الرابط الفاشل على الطرفية (نافذة كتابة الأوامر، التي تكون لها خلفية سوداء في العادة)
أمر العرض هذا بسيط جدا، وهو أمر طباعة print
(لا يطبع على الورق طبعا، بل يطبع على الشاشة. سبب التسمية تاريخي، لأن الحواسيب قديما كانت بلا شاشات! ولهذا كل المخرجات النصية كانت تطبع على ورق)
from bs4 import BeautifulSoup
from urllib.request import urlopen
with open('MyFile.txt') as f:
lines = f.readlines()
for line in lines:
page = urlopen(line).read()
soup = BeautifulSoup(page, "lxml")
page_title = soup.title.string
try:
with open(page_title + ".html", 'wb') as the_file:
the_file.write(page)
except FileNotFoundError:
print(line)
continue
with open('MyFile.txt') as f:
lines = f.readlines()
for line in lines:
page = urlopen(line).read()
soup = BeautifulSoup(page, "lxml")
page_title = soup.title.string
try:
with open(page_title + ".html", 'wb') as the_file:
the_file.write(page)
except FileNotFoundError:
print(line)
continue
ها هو البرنامج النهائي بكل بساطة. كل ما عليك فعله هو نسخه في ملف نصي (باستخدام الــ notepad مثلا. المبرمجون لا يكتبون برامجهم باستخدام برامج الأوفيس والـ Word Processors! بل باستخدام محررات النصوص البسيطة Text Editors، ثم مع مرور الوقت وزيادة خبرتهم يستخدمون برامج الـ IDE المتخصصة والتي تيسر عملية برمجة المشاريع الكبيرة. محررات النصوص البسيطة يوجد منها مثلا gEdit ، والبرامج المتخصصة في كتابة أكواد البيثون منها Spyder)
واحفظ كودك في ملف نصي بامتداد
.py
والآن لتشغيل الاسكربت، إن كانت عندك لغة البيثون جاهزة على جهازك (أغلب توزيعات نظام التشغيل Linux مثل Ubuntu تأتي معها لغة البيثون جاهزة أو يمكن تحميلها بسهولة باستخدام Package Manager مثل Synaptic. البيثون متوفر أيضا على نظام الويندوز، لكنه لا يأتي تلقائيا مع النظام، بل يجب تحميله بشكل منفصل من موقع البيثون على الإنترنت)، عليك فتح المجلد الذي حفظت فيه كلا من الاسكربت وملف الروابط، ثم فتح نافذة طرفية من هذا المجلد (عادة بالضغط على F4) وكتابة أمر تشغيل البيثون والاسكربت
مثال: لو كان اسم الاسكربت هو
WikiDownloader.py
فستكتب الأمر
WikiDownloader.py
فستكتب الأمر
python3 WikiDownloader.py
أو
python WikiDownloader.py
وتضغط زر الإدخال، فستجد أن المجلد قد ظهرت فيه صفحات الموسوعة التي طلبتها
تعليقات
إرسال تعليق