تجريف الويب الحديث باستخدام Python و Selenium
نشرت: 2022-03-11تم استخدام تجريف الويب لاستخراج البيانات من مواقع الويب تقريبًا منذ وقت ظهور شبكة الويب العالمية. في الأيام الأولى ، كان الكشط يتم بشكل أساسي على الصفحات الثابتة - تلك التي تحتوي على عناصر وعلامات وبيانات معروفة.
ومع ذلك ، في الآونة الأخيرة ، جعلت التقنيات المتقدمة في تطوير الويب المهمة أكثر صعوبة. في هذه المقالة ، سوف نستكشف كيف يمكننا أن نبدأ في تجريف البيانات في حالة أن التكنولوجيا الجديدة وعوامل أخرى تمنع التجريف القياسي.
كشط البيانات التقليدية
نظرًا لأن معظم مواقع الويب تنتج صفحات مخصصة للقراءة البشرية بدلاً من القراءة الآلية ، فإن تجريف الويب يتكون أساسًا من هضم بيانات ترميز صفحة الويب برمجيًا (فكر في النقر بزر الماوس الأيمن ، وعرض المصدر) ، ثم اكتشاف الأنماط الثابتة في تلك البيانات التي من شأنها أن تسمح للبرنامج "لقراءة" أجزاء مختلفة من المعلومات وحفظها في ملف أو قاعدة بيانات.
إذا تم العثور على بيانات التقرير ، في كثير من الأحيان ، يمكن الوصول إلى البيانات عن طريق تمرير إما متغيرات النموذج أو المعلمات مع عنوان URL. علي سبيل المثال:
https://www.myreportdata.com?month=12&year=2004&clientid=24823
أصبحت Python واحدة من أكثر لغات تجريف الويب شيوعًا ويرجع ذلك جزئيًا إلى مكتبات الويب المختلفة التي تم إنشاؤها لها. تم تصميم إحدى المكتبات الشهيرة ، وهي Beautiful Soup ، لسحب البيانات من ملفات HTML و XML من خلال السماح بالبحث والتنقل وتعديل العلامات (مثل شجرة التحليل).
القشط المستند إلى المتصفح
في الآونة الأخيرة ، كان لدي مشروع تجريف بدا واضحًا جدًا وكنت على استعداد تام لاستخدام الكشط التقليدي للتعامل معه. لكن مع تقدمي في الأمر ، وجدت عقبات لا يمكن التغلب عليها بالطرق التقليدية.
منعتني ثلاث مشكلات رئيسية من أساليب الكشط القياسية:
- شهادة. كانت هناك شهادة مطلوبة للتثبيت للوصول إلى جزء موقع الويب حيث كانت البيانات. عند الوصول إلى الصفحة الأولية ، ظهرت مطالبة تطلب مني تحديد الشهادة المناسبة لتلك المثبتة على جهاز الكمبيوتر الخاص بي ، ثم انقر فوق "موافق".
- إطارات Iframes. استخدم الموقع إطارات iframe ، والتي أفسدت عملية الكشط العادية. نعم ، يمكنني محاولة العثور على جميع عناوين URL لإطار iframe ، ثم إنشاء خريطة موقع ، ولكن يبدو أن ذلك قد يصبح صعبًا.
- جافا سكريبت. تم الوصول إلى البيانات بعد ملء نموذج بالمعلمات (على سبيل المثال ، الرقم التعريفي للعميل ، نطاق التاريخ ، إلخ). عادةً ما أتجاوز النموذج وأمرر ببساطة متغيرات النموذج (عبر عنوان URL أو كمتغيرات نموذج مخفية) إلى صفحة النتائج وأرى النتائج. لكن في هذه الحالة ، احتوى النموذج على JavaScript ، والذي لم يسمح لي بالوصول إلى متغيرات النموذج بطريقة عادية.
لذلك ، قررت التخلي عن أساليبي التقليدية وإلقاء نظرة على أداة ممكنة للتجريف المستند إلى المتصفح. سيعمل هذا بشكل مختلف عن المعتاد - فبدلاً من الانتقال مباشرةً إلى صفحة ، وتنزيل شجرة التحليل ، وسحب عناصر البيانات ، أود بدلاً من ذلك "التصرف كإنسان" واستخدام متصفح للوصول إلى الصفحة التي أحتاجها ، ثم أكشط البيانات - وبالتالي تجاوز الحاجة للتعامل مع العوائق المذكورة.
السيلينيوم
بشكل عام ، يُعرف السيلينيوم بإطار عمل اختبار مفتوح المصدر لتطبيقات الويب - مما يتيح لمتخصصي ضمان الجودة إجراء اختبارات آلية وتنفيذ عمليات التشغيل وتنفيذ وظائف التحكم عن بُعد (مما يسمح بالعديد من مثيلات المتصفح لاختبار التحميل وأنواع المستعرضات المتعددة). في حالتي ، يبدو أن هذا قد يكون مفيدًا.
لغة go-to الخاصة بي لاستخراج بيانات الويب هي Python ، حيث إنها تحتوي على مكتبات متكاملة جيدًا يمكنها بشكل عام التعامل مع جميع الوظائف المطلوبة. وبالتأكيد توجد مكتبة سيلينيوم لبايثون. سيسمح لي هذا بإنشاء مثيل لـ "متصفح" - Chrome ، و Firefox ، و IE ، وما إلى ذلك - ثم التظاهر بأنني كنت أستخدم المتصفح بنفسي للوصول إلى البيانات التي كنت أبحث عنها. وإذا لم أرغب في ظهور المتصفح فعليًا ، يمكنني إنشاء المتصفح في وضع "مقطوعة الرأس" ، مما يجعله غير مرئي لأي مستخدم.
إعداد مشروع
لبدء التجربة ، كنت بحاجة إلى إعداد مشروعي والحصول على كل ما أحتاجه. لقد استخدمت جهازًا يعمل بنظام Windows 10 وتأكدت من أن لدي إصدار Python محدثًا نسبيًا (كان الإصدار 3.7.3). لقد قمت بإنشاء نص برمجي فارغ من Python ، ثم قمت بتحميل المكتبات التي اعتقدت أنها قد تكون مطلوبة ، باستخدام PIP (مثبت الحزمة لـ Python) إذا لم يتم تحميل المكتبة بالفعل. هذه هي المكتبات الرئيسية التي بدأت بها:
- الطلبات (لعمل طلبات HTTP)
- URLLib3 (معالجة URL)
- شوربة جميلة (في حالة عدم تمكن السيلينيوم من التعامل مع كل شيء)
- السيلينيوم (للتنقل المستند إلى المتصفح)
أضفت أيضًا بعض معلمات الاستدعاء إلى البرنامج النصي (باستخدام مكتبة argparse) حتى أتمكن من اللعب بمجموعات البيانات المختلفة ، واستدعاء البرنامج النصي من سطر الأوامر بخيارات مختلفة. تلك التي تضمنت الرقم التعريفي للعميل ، من شهر / سنة ، ومن شهر / سنة.
المشكلة 1 - الشهادة
كان الخيار الأول الذي احتجت إلى القيام به هو المتصفح الذي كنت سأخبر السيلينيوم باستخدامه. نظرًا لأنني أستخدم Chrome بشكل عام ، وهو مبني على مشروع Chromium مفتوح المصدر (تستخدمه أيضًا متصفحات Edge و Opera و Amazon Silk) ، فقد اعتقدت أنني سأحاول ذلك أولاً.
تمكنت من بدء تشغيل Chrome في البرنامج النصي عن طريق إضافة مكونات المكتبة التي أحتاجها ، ثم إصدار بضعة أوامر بسيطة:
# Load selenium components from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait, Select from selenium.webdriver.support import expected_conditions as EC from selenium.common.exceptions import TimeoutException # Establish chrome driver and go to report site URL url = "https://reportdata.mytestsite.com/transactionSearch.jsp" driver = webdriver.Chrome() driver.get(url)
نظرًا لأنني لم أقم بتشغيل المتصفح في وضع مقطوعة الرأس ، فقد ظهر المتصفح بالفعل ويمكنني رؤية ما كان يفعله. طلب مني على الفور تحديد شهادة (قمت بتثبيتها سابقًا).
كانت المشكلة الأولى التي يجب معالجتها هي الشهادة. كيف تختار المناسب وتقبله للدخول إلى الموقع؟ في أول اختبار لي للنص ، تلقيت هذا الموجه:
لم يكن هذا جيدًا. لم أرغب في النقر يدويًا على الزر "موافق" في كل مرة أشغل فيها البرنامج النصي.
كما اتضح ، تمكنت من إيجاد حل بديل لهذا - بدون برمجة. بينما كنت آمل أن يكون لدى Chrome القدرة على تمرير اسم الشهادة عند بدء التشغيل ، لم تكن هذه الميزة موجودة. ومع ذلك ، فإن Chrome لديه القدرة على التحديد التلقائي للشهادة في حالة وجود إدخال معين في سجل Windows. يمكنك تعيينه لتحديد الشهادة الأولى التي يراها ، أو أن تكون أكثر تحديدًا. نظرًا لأنه لم يتم تحميل سوى شهادة واحدة ، فقد استخدمت التنسيق العام.
وهكذا ، مع هذه المجموعة ، عندما طلبت من Selenium تشغيل Chrome وظهرت رسالة مطالبة ، سيحدد Chrome الشهادة تلقائيًا ويستمر.
المشكلة 2 - إطارات Iframes
حسنًا ، لقد كنت الآن في الموقع وظهر نموذج يطالبني بكتابة معرف العميل والنطاق الزمني للتقرير.
من خلال فحص النموذج في أدوات المطور (F12) ، لاحظت أنه تم تقديم النموذج ضمن إطار iframe. لذا ، قبل أن أتمكن من ملء النموذج ، كنت بحاجة إلى "التبديل" إلى إطار iframe المناسب حيث يوجد النموذج. للقيام بذلك ، قمت باستدعاء ميزة التبديل إلى السيلينيوم ، مثل:

# Switch to iframe where form is frame_ref = driver.find_elements_by_tag_name("iframe")[0] iframe = driver.switch_to.frame(frame_ref)
جيد ، والآن في الإطار الصحيح ، تمكنت من تحديد المكونات وملء حقل معرف العميل وتحديد القوائم المنسدلة للتاريخ:
# Find the Customer ID field and populate it element = driver.find_element_by_name("custId") element.send_keys(custId) # send a test id # Find and select the date drop-downs select = Select(driver.find_element_by_name("fromMonth")) select.select_by_visible_text(from_month) select = Select(driver.find_element_by_name("fromYear")) select.select_by_visible_text(from_year) select = Select(driver.find_element_by_name("toMonth")) select.select_by_visible_text(to_month) select = Select(driver.find_element_by_name("toYear")) select.select_by_visible_text(to_year)
المشكلة 3 - جافا سكريبت
الشيء الوحيد المتبقي في النموذج هو "النقر" على الزر "بحث" ، ليبدأ البحث. كان هذا صعبًا بعض الشيء حيث يبدو أن زر "بحث" يتحكم فيه جافا سكريبت ولم يكن زرًا عاديًا من نوع "إرسال". عند فحصها في أدوات المطور ، وجدت صورة الزر وتمكنت من الحصول على XPath ، بالنقر بزر الماوس الأيمن.
بعد ذلك ، مسلحًا بهذه المعلومات ، وجدت العنصر في الصفحة ، ثم نقرت عليه.
# Find the 'Find' button, then click it driver.find_element_by_xpath("/html/body/table/tbody/tr[2]/td[1]/table[3]/tbody/tr[2]/td[2]/input").click()
وفويلا ، تم تقديم النموذج وظهرت البيانات! الآن ، يمكنني فقط كشط جميع البيانات الموجودة في صفحة النتائج وحفظها كما هو مطلوب. أم يمكنني؟
الحصول على البيانات
أولاً ، كان عليّ التعامل مع الحالة التي لم يجد فيها البحث شيئًا. كان ذلك واضحًا جدًا. سيعرض رسالة في نموذج البحث دون تركه ، شيء مثل "لم يتم العثور على سجلات". لقد بحثت ببساطة عن هذه السلسلة وتوقفت عند هذا الحد إذا وجدتها.
ولكن إذا ظهرت النتائج ، فسيتم تقديم البيانات في divs بعلامة زائد (+) لفتح معاملة وإظهار جميع تفاصيلها. أظهرت المعاملة المفتوحة علامة الطرح (-) والتي عند النقر عليها ستغلق div. سيؤدي النقر فوق علامة الجمع إلى استدعاء عنوان URL لفتح div الخاص به وإغلاق أي علامة مفتوحة.
وبالتالي ، كان من الضروري العثور على أي علامات زائد على الصفحة ، وجمع عنوان URL بجوار كل علامة ، ثم تكرار كل منها للحصول على جميع البيانات الخاصة بكل معاملة.
# Loop through transactions and count links = driver.find_elements_by_tag_name('a') link_urls = [link.get_attribute('href') for link in links] thisCount = 0 isFirst = 1 for url in link_urls: if (url.find("GetXas.do?processId") >= 0): # URL to link to transactions if isFirst == 1: # already expanded + isFirst = 0 else: driver.get(url) # collapsed +, so expand # Find closest element to URL element with correct class to get tran type tran_type=driver.find_element_by_xpath("//*[contains(@href,'/retail/transaction/results/GetXas.do?processId=-1')]/following::td[@class='txt_75b_lmnw_T1R10B1']").text # Get transaction status status = driver.find_element_by_class_name('txt_70b_lmnw_t1r10b1').text # Add to count if transaction found if (tran_type in ['Move In','Move Out','Switch']) and (status == "Complete"): thisCount += 1
في الكود أعلاه ، كانت الحقول التي استرجعتها هي نوع المعاملة والحالة ، ثم تمت إضافتها إلى العدد لتحديد عدد المعاملات التي تتوافق مع القواعد المحددة. ومع ذلك ، كان بإمكاني استرداد الحقول الأخرى ضمن تفاصيل المعاملة ، مثل التاريخ والوقت ، والنوع الفرعي ، وما إلى ذلك.
بالنسبة لهذا المشروع ، تم إرجاع الجرد مرة أخرى إلى تطبيق الاتصال. ومع ذلك ، كان من الممكن تخزينها والبيانات الأخرى التي تم كشطها في ملف ثابت أو قاعدة بيانات أيضًا.
العوائق والحلول الإضافية الممكنة
قد يتم عرض العديد من العوائق الأخرى أثناء تجريف مواقع الويب الحديثة باستخدام مثيل المتصفح الخاص بك ، ولكن يمكن حل معظمها. وهنا عدد قليل:
تحاول العثور على شيء قبل ظهوره
أثناء تصفحك لنفسك ، كم مرة تجد أنك تنتظر ظهور صفحة ، أحيانًا لعدة ثوانٍ؟ حسنًا ، يمكن أن يحدث نفس الشيء أثناء التنقل برمجيًا. أنت تبحث عن فصل دراسي أو عنصر آخر - وهو ليس موجودًا!
لحسن الحظ ، يمتلك السيلينيوم القدرة على الانتظار حتى يرى عنصرًا معينًا ، ويمكن أن تنتهي المهلة إذا لم يظهر العنصر ، مثل:
element = WebDriverWait(driver, 10). until(EC.presence_of_element_located((By.ID, "theFirstLabel")))
الحصول على كلمة التحقق
تستخدم بعض المواقع Captcha أو ما شابه لمنع الروبوتات غير المرغوب فيها (والتي قد تعتبرك أنت). يمكن أن يؤدي ذلك إلى إعاقة تجريف الويب وإبطائه.
بالنسبة للمطالبات البسيطة (مثل "ما هو 2 + 3؟") ، يمكن قراءتها وفهمها بسهولة بشكل عام. ومع ذلك ، بالنسبة للحواجز الأكثر تقدمًا ، توجد مكتبات يمكن أن تساعد في محاولة كسرها. بعض الأمثلة هي 2Captcha و Death by Captcha و Bypass Captcha.
التغييرات الهيكلية للموقع
تهدف مواقع الويب إلى التغيير - وغالبًا ما يحدث ذلك. لهذا السبب عند كتابة نص برمجي ، من الأفضل أن تضع ذلك في الاعتبار. سترغب في التفكير في الطرق التي ستستخدمها للعثور على البيانات وأيها لن تستخدمها. ضع في اعتبارك تقنيات المطابقة الجزئية ، بدلاً من محاولة مطابقة عبارة كاملة. على سبيل المثال ، قد يغير موقع ويب رسالة من "لم يتم العثور على سجلات" إلى "لا توجد سجلات" - ولكن إذا كانت المطابقة موجودة في "لا توجد سجلات" ، فيجب أن تكون على ما يرام. أيضًا ، ضع في اعتبارك ما إذا كنت تريد التطابق في XPATH أو المعرف أو الاسم أو نص الرابط أو العلامة أو اسم الفئة أو محدد CSS - وأيها أقل احتمالًا للتغيير.
ملخص: بايثون والسيلينيوم
كان هذا عرضًا توضيحيًا موجزًا لإظهار أنه يمكن إلغاء أي موقع ويب تقريبًا ، بغض النظر عن التقنيات المستخدمة وما هي التعقيدات المتضمنة. في الأساس ، إذا كان بإمكانك تصفح الموقع بنفسك ، فيمكن كشطه بشكل عام.
الآن ، كتحذير ، هذا لا يعني أنه يجب كشط كل موقع. بعضها لديه قيود مشروعة ، وهناك العديد من القضايا القضائية التي تقرر شرعية كشط مواقع معينة. من ناحية أخرى ، ترحب بعض المواقع بالبيانات وتشجع على استردادها من موقعها على الويب وفي بعض الحالات توفر واجهة برمجة تطبيقات لتسهيل الأمور.
في كلتا الحالتين ، من الأفضل التحقق من الشروط والأحكام قبل البدء في أي مشروع. ولكن إذا مضت قدمًا ، فتأكد من أنه يمكنك إنجاز المهمة.
الموارد الموصى بها لكشط الويب المعقد:
- تجريف ويب بيثون المتقدم: أفضل الممارسات والحلول
- تجريف قابل للتطوير بنفسك: كيفية بناء وتشغيل كاشطات على نطاق واسع