كيفية إنشاء واجهة برمجة تطبيقات مخصصة من أي موقع باستخدام محرك العرائس

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

يأتي القشط للإنقاذ في مثل هذه الحالة. واختيار الأداة المناسبة للوظيفة مهم جدًا.

محرك العرائس: ليست مجرد مكتبة كشط أخرى

محرك الدمى هو مكتبة Node.js يديرها فريق Chrome Devtools في Google. إنه يشغل أساسًا مثيل Chromium أو Chrome (ربما يكون الاسم الأكثر شهرة) بطريقة مقطوعة الرأس (أو قابل للتكوين) ويكشف مجموعة من واجهات برمجة التطبيقات عالية المستوى.

من وثائقها الرسمية ، عادة ما يتم الاستفادة من محرك الدمى لعمليات متعددة لا تقتصر على ما يلي:

  • توليد لقطات الشاشة وملفات PDF
  • الزحف إلى SPA وإنشاء محتوى معروض مسبقًا (مثل عرض جانب الخادم)
  • اختبار ملحقات كروم
  • اختبار أتمتة واجهات الويب
  • تشخيص مشكلات الأداء من خلال تقنيات مثل التقاط تتبع الجدول الزمني لموقع الويب

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

تبدو بسيطة؟ التنفيذ ليس بهذا التعقيد أيضًا. لنبدأ.

توتير الكود معًا

يدفعني الولع بمنتجات Amazon إلى استخدام إحدى صفحات قائمة المنتجات الخاصة بهم كعينة هنا. سنقوم بتنفيذ حالة الاستخدام الخاصة بنا في خطوتين:

  • استخرج البيانات من الصفحة وقم بتعيينها في نموذج JSON سهل الاستهلاك
  • أضف القليل من الأتمتة لجعل حياتنا أسهل قليلاً

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

سنقوم باستخراج البيانات من هذا الرابط: //www.amazon.in/s؟k=Shirts&ref=nb_sb_noss_2 (قائمة بأهم القمصان التي تم البحث عنها كما هو موضح في الصورة) في نموذج API قابل للخدمة.

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

  • المتصفح: يقوم بتشغيل مثيل Chrome عندما نستخدم puppeteer.launchأو puppeteer.connect. يعمل هذا كمحاكاة بسيطة للمتصفح.
  • الصفحة: تشبه علامة تبويب واحدة في متصفح Chrome. يوفر مجموعة شاملة من الطرق التي يمكنك استخدامها مع نسخة صفحة معينة ويتم استدعاؤها عند الاتصال browser.newPage. تمامًا كما يمكنك إنشاء عدة علامات تبويب في المتصفح ، يمكنك بالمثل إنشاء نسخ متعددة للصفحة في وقت واحد في محرك الدمى.

إعداد محرك العرائس والانتقال إلى عنوان URL المستهدف

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

const puppeteer = require('puppeteer'); const url = '//www.amazon.in/s?k=Shirts&ref=nb_sb_noss_2'; async function fetchProductList(url) { const browser = await puppeteer.launch({ headless: true, // false: enables one to view the Chrome instance in action defaultViewport: null, // (optional) useful only in non-headless mode }); const page = await browser.newPage(); await page.goto(url, { waitUntil: 'networkidle2' }); ... } fetchProductList(url); 

نستخدمها networkidle2كقيمة waitUntilللخيار أثناء التنقل إلى عنوان URL. يضمن ذلك اعتبار حالة تحميل الصفحة نهائية عندما لا يكون هناك أكثر من اتصالين يعملان لمدة لا تقل عن 500 مللي ثانية.

ملاحظة: لا تحتاج إلى تثبيت Chrome أو نسخة منه على نظامك حتى يعمل محرك الدمى. يتم شحنه بالفعل مع نسخة خفيفة منه مجمعة مع المكتبة.

طرق الصفحة لاستخراج البيانات وتعيينها

تم تحميل DOM بالفعل في نسخة الصفحة التي تم إنشاؤها. سنمضي قدمًا ونستفيد من page.evaluate()الطريقة للاستعلام عن DOM.

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

{ brand: 'Brand Name', product: 'Product Name', url: '//www.amazon.in/url.of.product.com/', image: '//www.amazon.in/image.jpg', price: '₹599', }

لقد وضعنا الهيكل الذي نريد تحقيقه. حان الوقت لبدء فحص DOM للمعرفات. نتحقق من المحددات التي تحدث خلال العناصر المراد تعيينها. ونحن نستخدم في الغالب document.querySelectorو document.querySelectorAllلعبور DOM.

... async function fetchProductList(url) { ... await page.waitFor('div[data-cel-widget^="search_result_"]'); const result = await page.evaluate(() => { // counts total number of products let totalSearchResults = Array.from(document.querySelectorAll('div[data-cel-widget^="search_result_"]')).length; let productsList = []; for (let i = 1; i  0 ? onlyProduct = true : emptyProductMeta = true; } let productsDetails = productNodes.map(el => el.innerText); if (!emptyProductMeta) { product.brand = onlyProduct ? '' : productsDetails[0]; product.product = onlyProduct ? productsDetails[0] : productsDetails[1]; } // traverse for product image let rawImage = document.querySelector(`div[data-cel-widget="search_result_${i}"] .s-image`); product.image =rawImage ? rawImage.src : ''; // traverse for product url let rawUrl = document.querySelector(`div[data-cel-widget="search_result_${i}"] a[target="_blank"].a-link-normal`); product.url = rawUrl ? rawUrl.href : ''; // traverse for product price let rawPrice = document.querySelector(`div[data-cel-widget="search_result_${i}"] span.a-offscreen`); product.price = rawPrice ? rawPrice.innerText : ''; if (typeof product.product !== 'undefined') { !product.product.trim() ? null : productsList = productsList.concat(product); } } return productsList; }); ... } ...

// اجتياز أسماء العلامات التجارية والمنتجات

بعد التحقق من DOM ، نرى أن كل عنصر مدرج محاط بعنصر مع المحدد div[data-cel-widget^="search_result_"]. يبحث هذا المحدد المعين عن جميع divالعلامات ذات السمة data-cel-widgetالتي لها قيمة تبدأ بـ search_result_.

وبالمثل ، فإننا نحدد المحددات للمعلمات التي نطلبها كما هو مدرج. إذا كنت تريد معرفة المزيد عن اجتياز DOM ، فيمكنك الاطلاع على هذه المقالة المفيدة من Zell.

  • إجمالي العناصر المدرجة:div[data-cel-widget^="search_result_"]
  • العلامة التجارية:div[data-cel-widget="search_result_${i}"] .a-size-base-plus.a-color-base ( iتعني رقم العقدة في total listed items)
  • المنتج:div[data-cel-widget="search_result_${i}"] .a-size-base-plus.a-color-base  أو div[data-cel-widget="search_result_${i}"] .a-size-medium.a-color-base.a-text-normal( iتعني رقم العقدة في total listed items)
  • url:div[data-cel-widget="search_result_${i}"] a[target="_blank"].a-link-normal ( iتعني رقم العقدة في total listed items)
  • الصورة:div[data-cel-widget="search_result_${i}"] .s-image ( iتعني رقم العقدة في total listed items)
  • السعر:div[data-cel-widget="search_result_${i}"] span.a-offscreen ( iتعني رقم العقدة في total listed items)
ملاحظة: ننتظر div[data-cel-widget^="search_result_"]حتى تتوفر العناصر المسماة المحدد على الصفحة باستخدام page.waitForالطريقة.

بمجرد page.evaluateاستدعاء الطريقة ، يمكننا رؤية البيانات التي نطلبها مسجلة.

إضافة أتمتة إلى سهولة التدفق

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

ومع ذلك ، فكر للحظة في حالة يتعين عليك فيها الانتقال إلى عنوان URL من عنوان URL آخر عن طريق تنفيذ بعض الإجراءات - ثم حاول استخراج البيانات التي تحتاجها.

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

على عكس المثال السابق ، سنذهب إلى amazon.inالصفحة الرئيسية ونبحث عن "قمصان". سوف يأخذنا إلى صفحة قائمة المنتجات ويمكننا استخراج البيانات المطلوبة من DOM. سهل جدا. لنلق نظرة على الكود.

... async function fetchProductList(url, searchTerm) { ... await page.goto(url, { waitUntil: 'networkidle2' }); await page.waitFor('input[name="field-keywords"]'); await page.evaluate(val => document.querySelector('input[name="field-keywords"]').value = val, searchTerm); await page.click('div.nav-search-submit.nav-sprite'); // DOM traversal and data mapping logic // returns a productsList array ... } fetchProductList('//amazon.in', 'Shirts'); 

We can see that we wait for the search box to be available and then we add the searchTerm passed using page.evaluate. We then navigate to the products listing page by emulating the 'search button' click action and exposing the DOM.

The complexity of automation varies from use case to use case.

Some Notable Gotchas: A Minor Heads Up

Puppeteer's API is pretty comprehensive but there are a few gotchas I came across while working with it. Remember, not all of these gotchas are directly related to puppeteer but tend to work better along with it.

  • Puppeteer creates a Chrome browser instance as already mentioned. However, it is likely that some existing websites might block access if they suspect bot activity. There is this package called user-agents which can be used with puppeteer to randomize the user-agent for the browser.
ملاحظة: تجريف موقع ويب يقع في مكان ما في المناطق الرمادية للقبول القانوني. أوصي باستخدامه بحذر والتحقق من القواعد في المكان الذي تعيش فيه.
const puppeteer = require('puppeteer'); const userAgent = require('user-agents'); ... const browser = await puppeteer.launch({ headless: true, defaultViewport: null }); const page = await browser.newPage(); await page.setUserAgent(userAgent.toString()); ...
  • لقد صادفنا defaultViewport: nullعند تشغيل مثيل Chrome الخاص بنا وأدرجته على أنه اختياري. هذا لأنه يكون مفيدًا فقط عند عرض مثيل Chrome قيد التشغيل. يمنع عرض موقع الويب وارتفاعه من التأثر عند تقديمه.
  • محرك العرائس ليس الحل النهائي عندما يتعلق الأمر بالأداء. بصفتك مطورًا ، سيتعين عليك تحسينه لزيادة كفاءة أدائه من خلال إجراءات مثل تقييد الرسوم المتحركة على الموقع ، والسماح فقط بمكالمات الشبكة الأساسية ، وما إلى ذلك.
  • Remember to always end a puppeteer session by closing the Browser instance by using browser.close. (I happened to miss out on it in the first try) It helps end a running Browser Session.
  • Certain common JavaScript operations like console.log() will not work within the scope of the page methods. The reason being that the page context/browser context differs from the node context in which your application is running.

These are some of the gotchas I noticed. If you have more, feel free to reach out to me with them. I would love to learn more.

Done? Let's run the application.

Website to Your API: Bringing it All Together

The application is run in non-headless mode so you can witness what exactly happens. We will automate the navigation to the product listing page from which we obtain the data.

هناك. لديك إعداد بيانات API القابل للاستهلاك الخاص بك من موقع الويب الذي تختاره. كل ما عليك فعله الآن هو توصيل هذا بإطار عمل جانبي للخادم مثل expressوأنت على ما يرام.

استنتاج

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

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

لا يوجد وقت أفضل من الآن للبدء مع محرك العرائس.

إذا كانت لديك أي أسئلة أو تعليقات ، فيمكنك التواصل معي على LinkedIn أو Twitter.

في غضون ذلك ، استمر في الترميز.