دليل شامل: استخلاص البيانات من الويب باستخدام بايثون (Web Scraping)

دقائق القراءة: 12

تُعد بايثون لغة برمجة رائعة ومميزة، حيث تتمتع بنظام بيئي غني بالمكتبات والوحدات، وتقدم تجربة برمجية أكثر نقاءً مقارنة بلغات أخرى، فضلاً عن سهولة استخدامها الفائقة. تُستخدم بايثون في مجالات متعددة، بدءًا من تحليل البيانات وصولاً إلى برمجة الخوادم. ومن بين الاستخدامات المثيرة للاهتمام لبايثون هو استخلاص البيانات من الويب (Web Scraping).

في هذا المقال، سنتناول كيفية استخدام بايثون في عملية استخلاص البيانات، وسنقدم دليلًا عمليًا شاملاً خطوة بخطوة.

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

مقدمة إلى استخلاص البيانات من الويب عمليًا

واجهة فصل codedamn التعليمي لاستخلاص البيانات من الويب

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

في هذا الفصل التعليمي، ستستخدم هذه الصفحة لاختبار استخلاص البيانات من الويب: https://codedamn-classrooms.github.io/webscraper-python-codedamn-classroom-website/

يتكون هذا الفصل من 7 مختبرات، وستحل مختبرًا في كل جزء من أجزاء هذا المقال. سنستخدم Python 3.8 و BeautifulSoup 4 لاستخلاص البيانات من الويب.

الجزء الأول: تحميل صفحات الويب باستخدام وحدة Requests

هذا هو رابط المختبر الأول. تتيح لك وحدة requests إرسال طلبات HTTP باستخدام بايثون. يعيد طلب HTTP كائن استجابة (Response Object) يحتوي على جميع بيانات الاستجابة (المحتوى، الترميز، حالة الطلب، وما إلى ذلك).

فيما يلي مثال على كيفية الحصول على محتوى HTML لصفحة:

import requests
res = requests.get(
  'https://codedamn.com'
)
print(res.text)
print(res.status_code)

متطلبات اجتياز المختبر الأول:

  • احصل على محتويات عنوان URL التالي باستخدام وحدة requests: https://codedamn-classrooms.github.io/webscraper-python-codedamn-classroom-website/
  • خزّن استجابة النص (كما هو موضح أعلاه) في متغير يُسمى txt.
  • خزّن رمز الحالة (كما هو موضح أعلاه) في متغير يُسمى status.
  • اطبع المتغيرين txt و status باستخدام دالة print.

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

import requests
# Make a request to https://codedamn-classrooms.github.io/webscraper-python-codedamn-classroom-website/
# Store the result in 'res' variable
res = requests.get(
  'https://codedamn-classrooms.github.io/webscraper-python-codedamn-classroom-website/'
)
txt = res.text
status = res.status_code
print(txt, status)
# print the result

لننتقل الآن إلى الجزء الثاني حيث ستبني المزيد على الكود الحالي.

الجزء الثاني: استخلاص العنوان باستخدام BeautifulSoup

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

  • توفر العديد من الطرق البسيطة والتعبيرات البرمجية ‘البايثونية’ (Pythonic idioms) للتنقل والبحث وتعديل شجرة نموذج كائن المستند (DOM tree).
  • لا تتطلب الكثير من الأكواد لكتابة تطبيق.
  • تعتمد Beautiful Soup على محللات بايثون الشائعة مثل lxml و html5lib، مما يتيح لك تجربة استراتيجيات تحليل مختلفة أو الموازنة بين السرعة والمرونة.

بشكل أساسي، يمكن لـ BeautifulSoup تحليل أي شيء على الويب تقدمه لها. إليك مثال بسيط على استخدام BeautifulSoup:

from bs4 import BeautifulSoup
page = requests.get(
  "https://codedamn.com"
)
soup = BeautifulSoup(page.content, 'html.parser')
title = soup.title.text # gets you the text of the <title>(...)</title>

متطلبات اجتياز المختبر الثاني:

بالنظر إلى المثال أعلاه، يمكنك أن ترى أنه بمجرد تغذية page.content داخل BeautifulSoup، يمكنك البدء في العمل مع شجرة DOM المحللة بطريقة ‘بايثونية’ للغاية. سيكون حل المختبر كالتالي:

import requests
from bs4 import BeautifulSoup
# Make a request to https://codedamn-classrooms.github.io/webscraper-python-codedamn-classroom-website/
page = requests.get(
  "https://codedamn-classrooms.github.io/webscraper-python-codedamn-classroom-website/"
)
soup = BeautifulSoup(page.content, 'html.parser')
# Extract title of page
page_title = soup.title.text
# print the result
print(page_title)

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

الجزء الثالث: استخلاص أقسام Body و Head

هذا هو رابط المختبر الثالث. في المختبر الأخير، رأيت كيف يمكنك استخلاص title من الصفحة. من السهل بنفس القدر استخلاص أقسام معينة أيضًا. لقد رأيت أيضًا أنه يجب عليك استدعاء .text على هذه الأقسام للحصول على النص كـ string، ولكن يمكنك طباعتها دون استدعاء .text أيضًا، وستعطيك العلامات الكاملة (full markup). حاول تشغيل المثال أدناه:

import requests
from bs4 import BeautifulSoup
# Make a request
page = requests.get(
  "https://codedamn.com"
)
soup = BeautifulSoup(page.content, 'html.parser')
# Extract title of page
page_title = soup.title.text
# Extract body of page
page_body = soup.body
# Extract head of page
page_head = soup.head
# print the result
print(page_body, page_head)

دعنا نلقي نظرة على كيفية استخلاص قسمي body و head من صفحاتك.

متطلبات اجتياز المختبر الثالث:

  • كرر التجربة مع عنوان URL التالي: https://codedamn-classrooms.github.io/webscraper-python-codedamn-classroom-website/
  • خزّن عنوان الصفحة (دون استدعاء .text) من URL في المتغير page_title.
  • خزّن محتوى قسم body (دون استدعاء .text) من URL في المتغير page_body.
  • خزّن محتوى قسم head (دون استدعاء .text) من URL في المتغير page_head.

عندما تحاول طباعة page_body أو page_head، سترى أنها تُطبع كـ strings. ولكن في الواقع، عندما تقوم بـ print(type(page_body))، سترى أنها ليست string ولكنها تعمل بشكل جيد. سيكون حل هذا المثال بسيطًا، بناءً على الكود أعلاه:

import requests
from bs4 import BeautifulSoup
# Make a request
page = requests.get(
  "https://codedamn-classrooms.github.io/webscraper-python-codedamn-classroom-website/"
)
soup = BeautifulSoup(page.content, 'html.parser')
# Extract title of page
page_title = soup.title
# Extract body of page
page_body = soup.body
# Extract head of page
page_head = soup.head
# print the result
print(page_title, page_head)

الجزء الرابع: تحديد العناصر باستخدام دالة Select في BeautifulSoup

هذا هو رابط المختبر الرابع. بعد أن استكشفت بعض أجزاء BeautifulSoup، دعنا نرى كيف يمكنك تحديد عناصر DOM باستخدام طرق BeautifulSoup. بمجرد أن يكون لديك متغير soup (كما في المختبرات السابقة)، يمكنك العمل مع دالة .select عليه، وهي بمثابة محدد CSS داخل BeautifulSoup. وهذا يعني أنه يمكنك الوصول إلى شجرة DOM بنفس الطريقة التي تحدد بها العناصر باستخدام CSS. لنلقِ نظرة على مثال:

import requests
from bs4 import BeautifulSoup
# Make a request
page = requests.get(
  "https://codedamn-classrooms.github.io/webscraper-python-codedamn-classroom-website/"
)
soup = BeautifulSoup(page.content, 'html.parser')
# Extract first <h1>(...)</h1> text
first_h1 = soup.select('h1')[0].text

تعيد دالة .select قائمة بايثون (Python list) بجميع العناصر المطابقة. ولهذا السبب قمت بتحديد العنصر الأول فقط هنا باستخدام الفهرس [0].

متطلبات اجتياز المختبر الرابع:

  • أنشئ متغيرًا يُسمى all_h1_tags. اجعله قائمة فارغة.
  • استخدم دالة .select لتحديد جميع وسوم <h1> وخزّن نص هذه الوسوم داخل قائمة all_h1_tags.
  • أنشئ متغيرًا يُسمى seventh_p_text وخزّن نص العنصر p السابع (الفهرس 6) بداخله.

الحل لهذا المختبر هو:

import requests
from bs4 import BeautifulSoup
# Make a request
page = requests.get(
  "https://codedamn-classrooms.github.io/webscraper-python-codedamn-classroom-website/"
)
soup = BeautifulSoup(page.content, 'html.parser')
# Create all_h1_tags as empty list
all_h1_tags = []
# Set all_h1_tags to all h1 tags of the soup
for element in soup.select('h1'):
  all_h1_tags.append(element.text)
# Create seventh_p_text and set it to 7th p element text of the page
seventh_p_text = soup.select('p')[6].text
print(all_h1_tags, seventh_p_text)

لنكمل.

الجزء الخامس: استخلاص العناصر الشائعة حاليًا

هذا هو رابط المختبر الخامس. دعنا نمضي قدمًا ونستخلص العناصر الشائعة التي يتم استخلاصها من عنوان URL التالي: https://codedamn-classrooms.github.io/webscraper-python-codedamn-classroom-website/

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

لاجتياز هذا التحدي، اهتم بالنقاط التالية:

  • استخدم دالة .select لاستخلاص العناوين. (تلميح: يمكن أن يكون أحد المحددات لعناوين المنتجات هو a.title)
  • استخدم دالة .select لاستخلاص تسمية عدد التقييمات لهذه المنتجات. (تلميح: يمكن أن يكون أحد المحددات للتقييمات هو div.ratings)
  • ملاحظة: هذه تسمية كاملة (على سبيل المثال، 2 reviews) وليست مجرد رقم.
  • أنشئ قاموسًا جديدًا (dictionary) بالتنسيق التالي:
info = {
  "title" : 'Asus AsusPro Adv... '.strip(),
  "review" : '2 reviews\n\n\n'.strip()
}

لاحظ أنك تستخدم دالة strip لإزالة أي أسطر جديدة إضافية (newlines) أو مسافات بيضاء (whitespaces) قد تكون لديك في المخرجات. هذا مهم لاجتياز هذا المختبر.

  • أضف هذا القاموس إلى قائمة تُسمى top_items.
  • اطبع هذه القائمة في النهاية.

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

import requests
from bs4 import BeautifulSoup
# Make a request
page = requests.get(
  "https://codedamn-classrooms.github.io/webscraper-python-codedamn-classroom-website/"
)
soup = BeautifulSoup(page.content, 'html.parser')
# Create top_items as empty list
top_items = []
# Extract and store in top_items according to instructions on the left
products = soup.select('div.thumbnail')
for elem in products:
  title = elem.select('h4 > a.title')[0].text
  review_label = elem.select('div.ratings')[0].text
  info = {
    "title" : title.strip(),
    "review" : review_label.strip()
  }
  top_items.append(info)
print(top_items)

لاحظ أن هذا هو أحد الحلول فقط. يمكنك محاولة حل هذا بطريقة مختلفة أيضًا. في هذا الحل:

  • أولاً، تقوم بتحديد جميع عناصر div.thumbnail التي تمنحك قائمة بالمنتجات الفردية.
  • ثم تقوم بالتكرار عليها.
  • نظرًا لأن دالة select تسمح لك بسلسلة الاستدعاءات، يمكنك استخدام select مرة أخرى للحصول على العنوان. لاحظ أنه نظرًا لأنك تعمل بالفعل داخل حلقة لـ div.thumbnail، فإن المحدد h4 > a.title سيعطيك نتيجة واحدة فقط، داخل قائمة.
  • تقوم بتحديد العنصر ذي الفهرس 0 من تلك القائمة وتستخلص النص.
  • أخيرًا، تقوم بإزالة أي مسافات بيضاء إضافية وتضيفها إلى قائمتك.

الأمر بسيط، أليس كذلك؟

الجزء السادس: استخلاص الروابط (Links)

هذا هو رابط المختبر السادس. حتى الآن، رأيت كيف يمكنك استخلاص النص، أو بالأحرى النص الداخلي (innerText) للعناصر. دعنا الآن نرى كيف يمكنك استخلاص السمات (attributes) عن طريق استخلاص الروابط من الصفحة. إليك مثال على كيفية استخلاص جميع معلومات الصور من الصفحة:

import requests
from bs4 import BeautifulSoup
# Make a request
page = requests.get(
  "https://codedamn-classrooms.github.io/webscraper-python-codedamn-classroom-website/"
)
soup = BeautifulSoup(page.content, 'html.parser')
# Create top_items as empty list
image_data = []
# Extract and store in top_items according to instructions on the left
images = soup.select('img')
for image in images:
  src = image.get('src')
  alt = image.get('alt')
  image_data.append({"src" : src, "alt" : alt})
print(image_data)

في هذا المختبر، مهمتك هي استخلاص السمة href للروابط مع نصها (text) أيضًا. تأكد من النقاط التالية:

  • يجب عليك إنشاء قائمة تُسمى all_links.
  • في هذه القائمة، خزّن جميع معلومات القواميس الخاصة بالروابط. يجب أن تكون بالتنسيق التالي:
info = {
  "href" : "<link here>" ,
  "text" : "<link text here>"
}
  • تأكد من إزالة أي مسافات بيضاء من نص الرابط (text) باستخدام strip().
  • تأكد من التحقق مما إذا كان .text هو None قبل استدعاء .strip() عليه.
  • خزّن جميع هذه القواميس في قائمة all_links.
  • اطبع هذه القائمة في النهاية.

تقوم باستخلاص قيم السمات تمامًا كما تستخلص القيم من القاموس (dict)، باستخدام دالة get. دعنا نلقِ نظرة على حل هذا المختبر:

import requests
from bs4 import BeautifulSoup
# Make a request
page = requests.get(
  "https://codedamn-classrooms.github.io/webscraper-python-codedamn-classroom-website/"
)
soup = BeautifulSoup(page.content, 'html.parser')
# Create top_items as empty list
all_links = []
# Extract and store in top_items according to instructions on the left
links = soup.select('a')
for ahref in links:
  text = ahref.text
  text = text.strip() if text is not None else ''
  href = ahref.get('href')
  href = href.strip() if href is not None else ''
  all_links.append({"href" : href, "text" : text})
print(all_links)

هنا، تستخلص السمة href تمامًا كما فعلت في حالة الصورة. الشيء الوحيد الذي تفعله هو أيضًا التحقق مما إذا كانت None. نريد تعيينها إلى سلسلة نصية فارغة (empty string)، وإلا فإننا نريد إزالة المسافات البيضاء.

الجزء السابع: إنشاء ملفات CSV من البيانات المستخلصة

هذا هو رابط المختبر السابع. أخيرًا، دعنا نفهم كيف يمكنك إنشاء ملف CSV من مجموعة من البيانات. ستقوم بإنشاء ملف CSV بالعناوين التالية:

  • اسم المنتج (Product Name)
  • السعر (Price)
  • الوصف (Description)
  • التقييمات (Reviews)
  • صورة المنتج (Product Image)

توجد هذه المنتجات داخل عناصر div.thumbnail. قالب الكود الأساسي لـ CSV موضح أدناه:

import requests
from bs4 import BeautifulSoup
import csv
# Make a request
page = requests.get(
  "https://codedamn-classrooms.github.io/webscraper-python-codedamn-classroom-website/"
)
soup = BeautifulSoup(page.content, 'html.parser')
all_products = []
products = soup.select('div.thumbnail')
for product in products:
  # TODO: Work
  print("Work on product here")
keys = all_products[0].keys()
with open('products.csv', 'w', newline='') as output_file:
  dict_writer = csv.DictWriter(output_file, keys)
  dict_writer.writeheader()
  dict_writer.writerows(all_products)

يجب عليك استخلاص البيانات من الموقع وإنشاء ملف CSV هذا للمنتجات الثلاثة.

متطلبات اجتياز المختبر السابع:

  • اسم المنتج (Product Name) هو نسخة من اسم العنصر بعد إزالة المسافات البيضاء (مثال: Asus AsusPro Adv..).
  • السعر (Price) هو التسمية الكاملة للسعر بعد إزالة المسافات البيضاء (مثال: $1101.83).
  • الوصف (Description) هو نسخة من وصف المنتج بعد إزالة المسافات البيضاء (مثال: Asus AsusPro Advanced BU401LA-FA271G Dark Grey, 14", Core i5-4210U, 4GB, 128GB SSD, Win7 Pro).
  • التقييمات (Reviews) هي نسخة من تقييم المنتج بعد إزالة المسافات البيضاء (مثال: 7 reviews).
  • صورة المنتج (Product image) هي عنوان URL (السمة src) للصورة الخاصة بالمنتج (مثال: /webscraper-python-codedamn-classroom-website/cart2.png).
  • يجب أن يكون اسم ملف CSV هو products.csv ويجب تخزينه في نفس الدليل الذي يوجد به ملف script.py الخاص بك.

دعنا نرى حل هذا المختبر:

import requests
from bs4 import BeautifulSoup
import csv
# Make a request
page = requests.get(
  "https://codedamn-classrooms.github.io/webscraper-python-codedamn-classroom-website/"
)
soup = BeautifulSoup(page.content, 'html.parser')
# Create top_items as empty list
all_products = []
# Extract and store in top_items according to instructions on the left
products = soup.select('div.thumbnail')
for product in products:
  name = product.select('h4 > a')[0].text.strip()
  description = product.select('p.description')[0].text.strip()
  price = product.select('h4.price')[0].text.strip()
  reviews = product.select('div.ratings')[0].text.strip()
  image = product.select('img')[0].get('src')
  all_products.append({"name" : name, "description" : description, "price" : price, "reviews" : reviews, "image" : image})
keys = all_products[0].keys()
with open('products.csv', 'w', newline='') as output_file:
  dict_writer = csv.DictWriter(output_file, keys)
  dict_writer.writeheader()
  dict_writer.writerows(all_products)

كتلة الكود for هي الأكثر إثارة للاهتمام هنا. تقوم باستخلاص جميع العناصر والسمات مما تعلمته حتى الآن في جميع المختبرات. عند تشغيل هذا الكود، ستحصل على ملف CSV منظم وجميل. وهذا كل ما يتعلق بأساسيات استخلاص البيانات من الويب باستخدام BeautifulSoup!

الخلاصة

نأمل أن يكون هذا الفصل التعليمي التفاعلي من codedamn قد ساعدك على فهم أساسيات استخلاص البيانات من الويب باستخدام بايثون. إذا أعجبك هذا الفصل وهذا المقال، يرجى إخباري بذلك على حساباتي في تويتر وإنستغرام. يسعدني سماع ملاحظاتك!

الخلاصة التقنية

يُظهر هذا الدليل العملي قوة بايثون ومرونتها في مجال استخلاص البيانات من الويب. من خلال دمج مكتبات مثل requests لجلب محتوى الصفحات و BeautifulSoup لتحليل بنية HTML، يمكن للمطورين استخراج المعلومات المنظمة بكفاءة من أي موقع ويب تقريبًا. إن استخدام محددات CSS عبر دالة .select() يوفر طريقة بديهية وفعالة للتنقل في شجرة DOM. كما أن القدرة على تصدير البيانات المستخلصة إلى تنسيقات شائعة مثل CSV تفتح الأبواب أمام تطبيقات واسعة في تحليل البيانات، أتمتة المهام، وبناء مجموعات بيانات مخصصة. هذا النهج يمثل حجر الزاوية في العديد من مشاريع البيانات الضخمة والذكاء الاصطناعي.

اترك تعليقاً

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