ساخت کراولر URL برای نگاشت یک وب سایت با پایتون — راهنمای کاربردی
قبل از این که شروع به توضیح مراحل ساخت یک کراولر URL یا «خزنده آدرس» (URL Crawler) بکنیم، ابتدا باید مطمئن شویم که با مفهوم وب اسکرپینگ آشنا هستیم. وب اسکرپینگ به فرایند استخراج دادهها از وبسایتها برای عرضه آن در قالبی که کاربران به سهولت درک کنند گفته میشود.
در این راهنما میخواهیم میزان آسانی ساخت یک کراولر URL ساده را در پایتون نشان دهیم. از این کراولر میتوانید برای نگاشت یک وبسایت استفاده کنید. با این که این برنامه نسبتاً ساده است، اما یک مقدمه عالی برای آشنایی با مفاهیم بنیادی وب اسکرپینگ و اتوماسیون محسوب میشود. ما روی استخراج بازگشتی لینکها از صفحههای وب متمرکز میشویم، اما همین ایدهها را میتوان برای اغلب راهحلهای دیگر نیز مورد استفاده قرار داد.
برنامه ما به صورت زیر است:
- یک صفحه وب را بازدید میکند.
- همه URL-های یکتای پیدا شده روی صفحه وب را گردآوری کرده و آنها را به صف اضافه میکند.
- به صورت بازگشتی URL-ها را یک به یک و تا زمانی که صف خالی شود پردازش میکند.
- نتایج را نمایش میدهد.
شروع
نخستین کاری که باید انجام دهیم ایمپورت کردن همه کتابخانههای لازم است.
ما از BeautifulSoup ،requests و urllib برای وب اسکرپینگ استفاده میکنیم.
from bs4 import BeautifulSoup import requests import requests.exceptions from urllib.parse import urlsplit from urllib.parse import urlparse from collections import deque
سپس باید یک URL برای شروع کراول انتخاب کنیم. با این که میتوان هر صفحه وبی با لینکهای HTML انتخاب کرد، اما ما پیشنهاد میکنیم ابتدا از این وبسایت (+) آغاز کنید. این وبسایت اختصاصاً برای اسکرپ شدن طراحی شده است و برای بهرهگیری از آن مشکلی نخواهید داشت.
url = https://scrapethissite.com
سپس قصد داریم یک شیء deque خلق کنیم تا بتوانیم به سادگی لینکهای جدیداً ایجادشده را اضافه کنیم و زمانی که کار پردازششان به اتمام رسید، آنها را حذف کنیم. ما deque را با متغیر url مقداردهی میکنیم.
1# a queue of urls to be crawled next
2new_urls = deque([url])
سپس میتوانیم از یک set برای ذخیرهسازی URL-های یکتا در زمان پردازش شدن استفاده کنیم:
1# a set of urls that we have already processed
2processed_urls = set()
ما همچنین میخواهیم رد URL-های محلی (همان دامنه هدف)، بیگانه (متفاوت از دامنه هدف) و URL-های شکسته را داشته باشیم:
1# a set of domains inside the target website
2local_urls = set()
3# a set of domains outside the target website
4foreign_urls = set()
5# a set of broken urls
6broken_urls = set()
مرحله کراول کردن
اینک که همه چیز را آماده کردهایم، میتوانیم شروع به نوشتن کد واقعی برای کراول کردن یک وبسایت بکنیم. بدین ترتیب به بررسی همه URL-ها در صف میپردازیم تا ببینیم آیا در آن صفحه لینک دیگری وجود دارد یا نه و همه موارد موجود را به انتهای صف اضافه کنیم تا این که در آن صفحه هیچ لینکی باقی نماند. به محض این که کار اسکرپ کردن یک URL به پایان برسد آن را از صف خارج میکنیم و برای کاربردهای آتی به مجموعه processed_urls اضافه میکنیم.
1# process urls one by one until we exhaust the queue
2while len(new_urls):
3 # move url from the queue to processed url set
4 url = new_urls.popleft()
5 processed_urls.add(url)
6 # print the current url
7 print(“Processing %s” % url)
در ادامه یک استثنا برای به دست آوردن همه صفحههای وب مفقود و اضافه کردن آنها به مجموعه broken_urls برای کاربردهای آتی مشاهده میکنید:
1try:
2 response = requests.get(url)
3except(requests.exceptions.MissingSchema, requests.exceptions.ConnectionError, requests.exceptions.InvalidURL, requests.exceptions.InvalidSchema):
4 # add broken urls to it’s own set, then continue
5 broken_urls.add(url)
6 continue
سپس باید URL مبنای صفحه وب را به دست آوریم تا بتوانیم به سادگی آدرسهای محلی و خارجی را از هم تفکیک کنیم:
1# extract base url to resolve relative links
2parts = urlsplit(url)
3base = “{0.netloc}”.format(parts)
4strip_base = base.replace(“www.”, “”)
5base_url = “{0.scheme}://{0.netloc}”.format(parts)
6path = url[:url.rfind(‘/’)+1] if ‘/’ in parts.path else url
در این مرحله BeautifulSoup را مقداردهی میکنیم تا سند HTML را پردازش کند:
1soup = BeautifulSoup(response.text, “lxml”)
اکنون صفحه وب را برای همه لینکها اسکرپ کرده و آنها را به مجموعه متناظرشان اضافه میکنیم:
1for link in soup.find_all(‘a’):
2 # extract link url from the anchor
3 anchor = link.attrs[“href”] if “href” in link.attrs else ‘’
4if anchor.startswith(‘/’):
5 local_link = base_url + anchor
6 local_urls.add(local_link)
7 elif strip_base in anchor:
8 local_urls.add(anchor)
9 elif not anchor.startswith(‘http’):
10 local_link = path + anchor
11 local_urls.add(local_link)
12 else:
13 foreign_urls.add(anchor)
از آنجا که میخواهیم کراولر را صرفاً به آدرسهای محلی محدود کنیم، کد زیر را نیز درج میکنیم تا URL-های جدید را به صفمان اضافه کنیم:
1for i in local_urls:
2 if not i in new_urls and not i in processed_urls:
3 new_urls.append(i)
اگر میخواهید همه URL-ها را کراول کنید:
1if not link in new_urls and not link in processed_urls:
2 new_urls.append(link)
هشدار: روشی که برنامه هم اینک استفاده میکند و همه لینکهای خارجی را پردازش میکند به مدت زمان زیادی نیاز دارد. همچنین در صورت پردازش وبسایتهایی که مجوز لازم را ندارید احتمالاً با مشکل مواجه خواهید شد. بنابراین این کار را با مسئولیت خودتان انجام دهید.
کد کامل برنامه به صورت زیر است:
1from bs4 import BeautifulSoup
2import requests
3import requests.exceptions
4from urllib.parse import urlsplit
5from urllib.parse import urlparse
6from collections import deque
7import re
8
9url = "https://scrapethissite.com"
10# a queue of urls to be crawled
11new_urls = deque([url])
12
13# a set of urls that we have already been processed
14processed_urls = set()
15# a set of domains inside the target website
16local_urls = set()
17# a set of domains outside the target website
18foreign_urls = set()
19# a set of broken urls
20broken_urls = set()
21
22# process urls one by one until we exhaust the queue
23while len(new_urls):
24 # move next url from the queue to the set of processed urls
25 url = new_urls.popleft()
26 processed_urls.add(url)
27 # get url's content
28 print("Processing %s" % url)
29 try:
30 response = requests.get(url)
31 except (requests.exceptions.MissingSchema, requests.exceptions.ConnectionError, requests.exceptions.InvalidURL, requests.exceptions.InvalidSchema):
32 # add broken urls to it's own set, then continue
33 broken_urls.add(url)
34 continue
35
36 # extract base url to resolve relative links
37 parts = urlsplit(url)
38 base = "{0.netloc}".format(parts)
39 strip_base = base.replace("www.", "")
40 base_url = "{0.scheme}://{0.netloc}".format(parts)
41 path = url[:url.rfind('/')+1] if '/' in parts.path else url
42
43 # create a beutiful soup for the html document
44 soup = BeautifulSoup(response.text, "lxml")
45
46 for link in soup.find_all('a'):
47 # extract link url from the anchor
48 anchor = link.attrs["href"] if "href" in link.attrs else ''
49
50 if anchor.startswith('/'):
51 local_link = base_url + anchor
52 local_urls.add(local_link)
53 elif strip_base in anchor:
54 local_urls.add(anchor)
55 elif not anchor.startswith('http'):
56 local_link = path + anchor
57 local_urls.add(local_link)
58 else:
59 foreign_urls.add(anchor)
60
61 for i in local_urls:
62 if not i in new_urls and not i in processed_urls:
63 new_urls.append(i)
64
65print(processed_urls)
بدین ترتیب ما یک ابزار ساده برای کراول کردن یک وبسایت و نگاشت همه URL-های پیدا شده ساختهایم.
سخن پایانی
شما میتوانید از این کد استفاده کنید و آن را بهبود بدهید. برای نمونه میتوانید برنامه را طوری تغییر دهید که در صفحههای وب به دنبال آدرسهای ایمیل و یا شمارههای تلفن بگردد. حتی میتوانید این کارکرد را با افزودن آرگومانهای خط فرمان، گزینهای برای تعریف فایلهای خروجی، محدودسازی عمق جستجو و موارد دیگر بهبود بدهید. برای کسب اطلاعات بیشتر در مورد روش تغییر اینترفیسها برای پذیرش آرگومانهای خط فرمان به این آموزش مراجعه کنید:
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی پایتون Python
- گنجینه آموزشهای برنامهنویسی پایتون (Python)
- مجموعه آموزشهای برنامهنویسی
- زبان برنامه نویسی پایتون (Python) — از صفر تا صد
- آموزش پایتون (Python) — مجموعه مقالات جامع وبلاگ فرادرس
==
عالیییییییییییییییی