تقشير البيانات vs واجهات برمجة التطبيقات (APIs vs Data Scraping)



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

بعض الطرق المستخدمة في الحصول على البيانات:
  1. إنتاجها: إما عشوائياً (random) أو بناءً على قاعدة ما (rule-based). تستخدم البيانات المنتجة عشوئياً غالباً عند اختبار(testing) تطبيق برنامج أو تطوير بحثي.
  2. تنزيلها: يمكن تنزيل البيانات من المصدر سواءً موقع على الويب أو شخص. فمن الممكن أن تكون المعلومات متوفرة  في السحابة (online) أو على قرص صلب مثلاً (offline).
  3. استخلاصها من الويب: يتم ذلك عن طريق استخراج\تقشير البيانات (data scraping) من الموقع مباشرة عن طريق تصفح الموقع آلياً واستقطاع البيانات المرادة وتخزينها بالشكل المطلوب. 
  4. طلبها من واجهة برمجة تطبيقات API: يتم طلب البيانات من خلال واجهة توفرها الجهة المالكة للبيانات لاستخدام تلك البيانات في مختلف التطبيقات وتسمى رسمياً بـ"واجهة برمجة التطبيقات (Application Programming Interface)". تكون البيانات كاملة في السحابة ويحصل المبرمج فقط على جزء من البيانات حسب الطلب.
استخدام النقطة الأولى محدود جداً في الاختبار عادةً (testing). بينما النقطة الثانية تتطلب مجهود كبير عند التوسيع (scaling) لكمية كبيرة من البيانات. فإذا أردت استخدام بيانات الخرائط من "google"، من غير المعقول أن يجمع المبرمج بيانات الخرائط على جهاز واحد.

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

عادةً، المواقع التي توفر واجهة برمجة تطبيقات (API) للمطورين للوصول للبيانات تكون مستعدة لمثل هذا الضغط على الخوادم وعادة يقوم مالك البيانات بالحد من عدد كمية البيانات التي يمكن لكل مستخدم طلبها خلال اليوم الواحد. تعتبر الطريقة الرابعة الحل المثالي للوصول للبيانات في المواقع ذات البيانات الضخمة مثل مواقع التواصل الإجتماعي "Twitter, Facebook" ومواقع التعهيد الجماعي (Crowdsourcing) مثل "Foursquare, Yelp". أحد أكبر عيوب الـAPI هي محدودية عدد الطلبات في اليوم أو في الساعة في بعض الاحيان. فهذا يحد من إمكانيات المطور في الوصول للبيانات في حال استلزم الأمر تحليل البيانات بشكل متكرر. بالاضافة، فإن الكثير من الشركات توفر باقات مدفوعة للـAPI. بحيث مكن للمستخدم الوصول لبيانات محدودة جداً عن طريق الباقة المجانية  بالاضافة لحد عدد الطلبات بشكل كبير. الباقة المدفوعة عادةً توفر وصول أعلى للبيانات، مع حد أعلى لعدد الطلبات المسموح.


مثال تقشير البيانات (data scraping)
يوجد الكثير من البيانات المفيدة على مواقع متنوعة يمكن الاستفادة منها عن طريق تحليل البيانات واستخراج معلومات قيمة. في المثال التالي أقوم باستعراض مزايا تقشير البيانات عن طريق مثال عملي أقوم فيه بجمع معلومات أكبر عشر مدن في المملكة العربية السعودية وإحداثياتها على الخريطة من موقعين: www.worldatlas.com و www.wikipedia.org

أولاً: يوفر موقع world atlas قائمة بأكبر عشر مدن في المملكة[1]. القائمة ليست طويلة ويمكن استخلاصها يدوياً بسهولة عن طريق النسخ واللصق ولكن الهدف من المثال تطبيق المهارات على بيانات صغيرة الحجم ويمكن تطبيق المهارات ذاتها على بيانات ضخمة يصعب استخلاصها يدوياً. النتيجة المرغوبة هي المقدرة على استخدام البيانات التالية في تصوير خريطة لأكبر مدن المملكة:



نبدأ أولاً في استيراد المكتبات اللازمة لاستكمال التقشير:

1
2
from bs4 import BeautifulSoup
import requests

أحد أشهر مكتبات التقشير وأسهلها استخداماً هي مكتبة beautifulsoup حيث يمكننا من خلالها استخراج البيانات من صفحات الويب بكل سهولة. الخطوة التالية هي استرجاع صفحة الويب المراد تقشير البيانات منها:

1
2
source = requests.get("https://www.worldatlas.com/articles/biggest-cities-in-saudi-arabia.html").text
soup = BeautifulSoup(source, 'lxml')

باستخدام الدالة get من المكتبة requests نقوم بتمرير الرابط المحتوي على البيانات المراد استخراجها. نستخدم بعدها beautifulsoup في تكوين متغير يمثل الصفحة المستخلصة مع توفير العديد من الدوال التي تسهّل تقشير البيانات من الصفحة.
عند طباعة محتويات المتغير soup، تظهر لنا الصفحة المرغوبة بترميز html كالتالي:

ولكن نحن فقط بحاجة للبيانات بداخل الجدول السابق، كيف يمكن الوصول لتلك البيانات؟ خاصية جميلة في جميع متصفحات الويب تقريباً وهي inspect element عن طريق الضغط بالزر الأيمن على أي عنصر في صفحة الويب. في google chorme يمكن استخدام الاختصار ctrl+shift+I ليظهر ترميز html الخاص بالصفحة.

كما هو ظاهر في الصورة، تمرير الفأرة على الجدول (في اليسار) يظهر لنا الوسم (tag) الذي يحتوي على القيم المرادة. فعند تمرير الفأرة على الجدول الذي يحتوي على البيانات المراد استخلاصها من الصفحة، يظهر وسم table كما هو ظاهر في الصورة. يمكن استخدام الوسم في تقشير البيانات كالتالي:


1
Saudi_table = soup.table

كما ذكرت مسبقاً، مكتبة beautifulsoup توفر العديد من الخصائص التي تسهّل عملية التقشير. أحدها هو إمكانية استخراج وسم معين من الصفحة عن طريق اسم الوسم. في السطر السابق استخدمنا .table للوصول للجدول في الصفحة. عند طباعة المتغير Saudi_table نحصل على رموز html الخاصة بالجدول فقط:


الخطوة الأخيرة هي تحويل الجدول المستخلص من رموز html إلى هيكل بيانات (data structure) يمكنّنا من التعامل مع البيانات بسلاسة واستخدامها في تحليل البيانات. المكتبة الشهيرة pandas تمكننا من استخدام هيكل البيانات dataframe والذي يحتوي أيضاً على دالة خاصة تسمى بـread_html يمكنها تحويل جدول html إلى dataframe كالتالي:

1
2
import pandas as pd
Saudi_Cities = pd.read_html(str(Saudi_table), header=0)[0]

عند طباعة المتغير Saudi_Cities ينتج معنا الجدول التالي:

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

حتى نستطيع استخلاص موقع كل مدينة على الخريطة من صفحتها على ويكيبيديا، يجب أن نجد الرابط المشترك بين تلك الصفحات والذي يمكننا من أتمتتة عملية استخلاص القيم لتقاطع خطوط الطول والعرض لكل مدينة. بعد التدقيق، نجد أن الرابط المشترك لكل صفحات ويكيبيديا هو الرابط التالي "https://en.wikipedia.org/wiki/" ويمكن تذييله بأي قيمة المراد الوصول لصفحتها على الموقع. فمثلاً، إذا أردت الوصول لصفحة مدينة الرياض، كل ما علي فعله هو إضافة اسم المدينة "Riyadh" إلى الرابط كالتالي "https://en.wikipedia.org/wiki/Riyadh" ولمدينة جدة على نفس النسق "https://en.wikipedia.org/wiki/Jeddah".

الآن يمكننا الدخول لصفحة مدينة واحدة، تحديد الوسم المحتوي على قيم خطوط الطول والعرض، ثم أتمتة العملية لاستخلاص القيمة لكل المدن الأخرى. عند فحص صفحة مدينة الرياض على سبيل المثال، نجد أن القيم موجودة في وسم span المخصص بـ" class = geo " كما يظهر في الصورة:


يمكن تكوين جدول لأكبر مدن الممكلة بحيث يحتوي على اسم المدينة وقيمة خط الطول والعرض كالتالي:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
col = ['CityName', 'latitude', 'longitude']
Saudi_loc = pd.DataFrame(columns = col)
for i, row in Saudi_Cities.iterrows():
    city_name = row['City']
    source = requests.get("https://en.wikipedia.org/wiki/"+ city_name).text
    soup = BeautifulSoup(source, 'lxml')
    try:
        loc = soup.find('span', class_="geo").text
        print(city_name , loc)
        Saudi_loc = Saudi_loc.append({col[0]:city_name, 
                                      col[1]:float(loc.split('; ')[0]), 
                                      col[2]:float(loc.split('; ')[1])}, ignore_index=True)
    except:
        print("location was not detected for", city_name)

في السطر 1 و 2 نقوم بتعريف وصف الجدول المرغوب في المتغير Saudi_loc، فهيكله عبارة عن dataframe يحتوي على الأعمدة الثلاثة: اسم المدينة، خط الطول، خط العرض.
في باقي الأسطر، نقوم بمراجعة كل مدينة مستخلصة في الجدول السابق Saudi_Cities ونستخدم مكتبة beautifulsoup و requests مرة أخرى في استخلاص صفحة الويب الخاصة بكل مدينة كما هو موضح في السطرين 5 و 6.
في السطر 8 إلى 12 نقوم بالبحث من الوسم span المخصص بالـ class = geo ونسترجع منه قيم الطول والعرض.
في السطر 13 و 14 استثناء للمدن التي لا تملك صفحة مخصصة على ويكيبيديا، أو ربما تكون الصفحة باسم آخر غير الاسم الموجود في الجدول الموفر من قبل worldatlas.

عند طباعة الجدول Saudi_loc نحصل على النتيجة التالية:
يمكننا الآن إنتاج خريطة المملكة مع تحديد مواقع أكبر المدن عن طريق استخدام مكتبة folium كالتالي:


1
2
3
4
5
import folium
map_Saudi = folium.Map(location=[latitude, longitude], zoom_start=6)

for lat, lng, city in zip(Saudi_loc['latitude'], Saudi_loc['longitude'], Saudi_loc['CityName']):
    folium.Marker([lat, lng], popup = label).add_to(map_Saudi)

بعد استيراد المكتبة، في السطرين 2 و 3 نقوم بتعريف المتغير map_Saudi كخريطة من مكتبة folium تحتوي على الموقع المعرّف بالقيم latitude و longitude وهي قيم قمت بتعريفها مسبقاًً في الكود الكامل المرفق في نهاية المقال. المتغيرين يحملان قيم خط الطول والعرض الممثلة للمملكة العربية السعودية.
في السطر 4 و 5، لكل مدينة في الجدول السابق المحتوي على موقع أكبر مدن المملكة Saudi_loc، نقوم بوضع علامة (marker) يشير إلى المدينة على خريطة الممكلة. النتيجة عند عرض الخريطة تكون كالتالي:


استفدت شخصياً من فيديوهات كوري شافير على اليوتيوب عن طريقة استخدام مكتبة beautifulsoup خصيصاً الفيديو التالي:

Python Tutorial: Web Scraping with BeautifulSoup and Requests


مثال واجهة برمجة التطبيقات (Application Programming Interface API) عن طريق واجهة Foursquare في المقال التالي.




Comments