ما هو الـ Web Scraping؟

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

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

كما أريد أن أنوّه أن عملية الـ Scraping الضخمة قد تؤدي إلى وضع وإنهاك السيرفر تحت ضغط مهول، فيؤدي لانهياره.

هذه التدوينة موجّهة ﻷولئك الذين لديهم معرفة بلغة البرمجة Python، إذا لم تكن لديك خلفية عن بايثون فيمكنك دراسة اﻷساسيات ثم العودة هنا.

متطلبات مسبقة

قبل الشروع في العمل، الرجاء القيام بتثبيت المكونات والحزم التالية في بيئة بايثون لديك:

  • beautifulsoup4
  • requests
  • wordcloud
  • selenium

سوف نقوم بعمل استخلاص من هذا الموقع، وفي بنود الخدمة لديهم فإنهم يسمحون بهذه العملية.

الخطوة 1 – تحميل المصدر

أولًا، لقد صنعت ملفًا أسميته urls.txt والذي سيقوم بتخزين جميع الروابط التي يتم تحميلها، هكذا ستبدو الروابط في الملف عند تحميلها:

https://www.gesetze-im-internet.de/gg/art_1.html
https://www.gesetze-im-internet.de/gg/art_2.html
https://www.gesetze-im-internet.de/gg/art_3.html
https://www.gesetze-im-internet.de/gg/art_4.html
https://www.gesetze-im-internet.de/gg/art_5.html
https://www.gesetze-im-internet.de/gg/art_6.html
https://www.gesetze-im-internet.de/gg/art_7.html
https://www.gesetze-im-internet.de/gg/art_8.html
https://www.gesetze-im-internet.de/gg/art_9.html
https://www.gesetze-im-internet.de/gg/art_10.html
https://www.gesetze-im-internet.de/gg/art_11.html
https://www.gesetze-im-internet.de/gg/art_12.html
https://www.gesetze-im-internet.de/gg/art_12a.html
https://www.gesetze-im-internet.de/gg/art_13.html
https://www.gesetze-im-internet.de/gg/art_14.html
https://www.gesetze-im-internet.de/gg/art_15.html
https://www.gesetze-im-internet.de/gg/art_16.html
https://www.gesetze-im-internet.de/gg/art_16a.html
https://www.gesetze-im-internet.de/gg/art_17.html
https://www.gesetze-im-internet.de/gg/art_17a.html
https://www.gesetze-im-internet.de/gg/art_18.html
https://www.gesetze-im-internet.de/gg/art_19.html

واﻵن سوف أقوم بإنشاء ملف اسمه scraper.py لتحميل محتوى الـHTML من تلك الملفات:

from os import path
from pathlib import PurePath

import requests

with open('urls.txt', 'r') as fh:
urls = fh.readlines()
urls = [url.strip() for url in urls] # strip `\n`

for url in urls:
file_name = PurePath(url).name
file_path = path.join('.', file_name)
text = ''

try:
response = requests.get(url)
if response.ok:
text = response.text
except requests.exceptions.ConnectionError as exc:
print(exc)

with open(file_path, 'w') as fh:
fh.write(text)

print('Written to', file_path)

الخطوة 2 – تحليل المصدر

لقد قمنا بتنزيل الملفات، حان الوقت لاستخراج المعلومات اللازمة، انتقل اﻵن ﻷحد الصفحات التي تم تحميلها وافتحها في متصفح الويب ثم اضغط على Ctrl+U لعرض مصدر الصفحة، سيظهر لديك كود HTML.

,وبما أن الموقع الذي نستخلص منه حاليا يقدم معلومات بشأن القوانين في ألمانيا، فإنني أريد استخلاص نصّ القانون منه، إن العنصر الذي يحوي نص القانون داخله يحمل الـ id المسمى container ، وعبر BeautifulSoup مستخدمًا الدالتين find و get_text سوف نتمكن من تأدية العملية المطلوبة…

حدّث الملف scraper.py:

from os import path
from pathlib import PurePath
import sys

from bs4 import BeautifulSoup
import requests


def download_urls(urls, dir):
paths = []

for url in urls:
file_name = PurePath(url).name
file_path = path.join(dir, file_name)
text = ''

try:
response = requests.get(url)
if response.ok:
text = response.text
else:
print('Bad response for', url, response.status_code)
except requests.exceptions.ConnectionError as exc:
print(exc)

with open(file_path, 'w') as fh:
fh.write(text)

paths.append(file_path)

return paths

def parse_html(path):
with open(path, 'r') as fh:
content = fh.read()

return BeautifulSoup(content, 'html.parser')

def download(urls):
return download_urls(urls, '.')

def extract(path):
return parse_html(path)

def transform(soup):
container = soup.find(id='container')
if container is not None:
return container.get_text()

def load(key, value):
d = {}
d[key] = value
return d

def run_single(path):
soup = extract(path)
content = transform(soup)
unserialised = load(path, content.strip() if content is not None else '')
return unserialised

def run_everything():
l = []

with open('urls.txt', 'r') as fh:
urls = fh.readlines()
urls = [url.strip() for url in urls]

paths = download(urls)
for path in paths:
print('Written to', path)
l.append(run_single(path))

print(l)

if __name__ == "__main__":
args = sys.argv

if len(args) is 1:
run_everything()
else:
if args[1] == 'download':
download([args[2]])
print('Done')
if args[1] == 'parse':
path = args[2]
result = run_single(path)
print(result)

 

اﻵن يمكنني تشغيل الكود الذي كتبته بثلاث طرق:

  1. بدون أي Arguments (لتنزيل جميع الروابط وحفظها على الهارديسك) عبر اﻷمر python scraper.py
  2. أو باستخدام الـargument المسمى download لتحميل صفحة بعينها مع تخطي عملية معالجة الملف، عبر اﻷمر python scraper.py download https://www.gesetze-im-internet.de/gg/art_1.html 
  3. أو باستخدام الـargument المسمى parse ومسار الملف المراد تحليله، مع تخطي عملية التحميل، عبر اﻷمر python scraper.py art_1.html

الخطوة اﻷخيرة – تنسيق المصدر لمزيد من المعالجة

ماذا لو كنت أريد إنشاء سحابة كلمات (word cloud) لكل مقالة؟ هذه العملية قد تعطينا فكرة حول موضوع النصّ، قم بتثبيت حزمة wordcloud ثمّ حدّث الملف scraper.py ليبدو كالتالي:

from os import path
from pathlib import Path, PurePath
import sys

from bs4 import BeautifulSoup
import requests
from wordcloud import WordCloud

STOPWORDS_ADDENDUM = [
'Das',
'Der',
'Die',
'Diese',
'Eine',
'In',
'InhaltsverzeichnisGrundgesetz',
'im',
'Jede',
'Jeder',
'Kein',
'Sie',
'Soweit',
'Über'
]
STOPWORDS_FILE_PATH = 'stopwords.txt'
STOPWORDS_URL = 'https://raw.githubusercontent.com/stopwords-iso/stopwords-de/master/stopwords-de.txt'


def download_urls(urls, dir):
paths = []

for url in urls:
file_name = PurePath(url).name
file_path = path.join(dir, file_name)
text = ''

try:
response = requests.get(url)
if response.ok:
text = response.text
else:
print('Bad response for', url, response.status_code)
except requests.exceptions.ConnectionError as exc:
print(exc)

with open(file_path, 'w') as fh:
fh.write(text)

paths.append(file_path)

return paths

def parse_html(path):
with open(path, 'r') as fh:
content = fh.read()

return BeautifulSoup(content, 'html.parser')

def download_stopwords():
stopwords = ''

try:
response = requests.get(STOPWORDS_URL)
if response.ok:
stopwords = response.text
else:
print('Bad response for', url, response.status_code)
except requests.exceptions.ConnectionError as exc:
print(exc)

with open(STOPWORDS_FILE_PATH, 'w') as fh:
fh.write(stopwords)

return stopwords

def download(urls):
return download_urls(urls, '.')

def extract(path):
return parse_html(path)

def transform(soup):
container = soup.find(id='container')
if container is not None:
return container.get_text()

def load(filename, text):
if Path(STOPWORDS_FILE_PATH).exists():
with open(STOPWORDS_FILE_PATH, 'r') as fh:
stopwords = fh.readlines()
else:
stopwords = download_stopwords()

# Strip whitespace around
stopwords = [stopword.strip() for stopword in stopwords]
# Extend stopwords with own ones, which were determined after first run
stopwords = stopwords + STOPWORDS_ADDENDUM

try:
cloud = WordCloud(stopwords=stopwords).generate(text)
cloud.to_file(filename.replace('.html', '.png'))
except ValueError:
print('Could not generate word cloud for', key)

def run_single(path):
soup = extract(path)
content = transform(soup)
load(path, content.strip() if content is not None else '')

def run_everything():
with open('urls.txt', 'r') as fh:
urls = fh.readlines()
urls = [url.strip() for url in urls]

paths = download(urls)
for path in paths:
print('Written to', path)
run_single(path)
print('Done')

if __name__ == "__main__":
args = sys.argv

if len(args) is 1:
run_everything()
else:
if args[1] == 'download':
download([args[2]])
print('Done')
if args[1] == 'parse':
path = args[2]
run_single(path)
print('Done')

ماذا عن المواقع المبنية بـ Angular, React, Vue؟ كيف أستخلص المعلومات منها؟

هناك اختلافات بين مواقع الويب التقليدية وتطبيقات الويب وحيدة الصفحة (SPA) التي يمكن بناؤها بأحد إطارات عمل جافاسكربت مثل Angular, React, Vue، بحيث أن استخلاص المعلومات منها مختلف.

في هذه الحالة سنستخدم Selenium، وتأكد من تنزيل الـ Driver خاصته كذلك، قم بتحميل الملف المضغوط وفكّ الضغط عنه داخل مجلد bin من بيئتك الافتراضية (Virtual Env) حتى يستطيع سلينيوم العثور عليه.

وبما أن الكود الذي صنعناه سابقا سيكون أبطأ في حالة هذه المواقع، فسأقوم بإنشاء ملف crawler.py ﻷجل ذلك:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from wordcloud import WordCloud

def extract(url):
elem = None
driver = webdriver.Firefox()
driver.get(url)

try:
found = WebDriverWait(driver, 10).until(
EC.visibility_of(
driver.find_element(By.TAG_NAME, "article")
)
)
# Make a copy of relevant data, because Selenium will throw if
# you try to access the properties after the driver quit
elem = {
"text": found.text
}
finally:
driver.close()

return elem

def transform(elem):
return elem["text"]

def load(text, filepath):
cloud = WordCloud().generate(text)
cloud.to_file(filepath)

if __name__ == "__main__":
url = "https://angular.io/"
filepath = "angular.png"

elem = extract(url)
if elem is not None:
text = transform(elem)
load(text, filepath)
else:
print("Sorry, could not extract data")

أعلاه تقوم بايثون بفتح فايرفوكس وتصفح الموقع والبحث عن العنصر article ويتم نسخ النص إلى الـdictionary والذي يُقرأ أثناء خطوة التحويل ومن ثم يتحول إلى WordCloud.

عندما نريد التعامل مع مواقع جافاسكربت ثقيلة، فإننا نلجأ لاستخدام Waits أو حتى تشغيل execute_script.

[freecodecamp]