چگونه یک «خزنده وب» (Web Crawler) بسیار ابتدایی بسازیم؟

۹۰۱ بازدید
آخرین به‌روزرسانی: ۰۵ تیر ۱۳۹۷
زمان مطالعه: ۶ دقیقه
چگونه یک «خزنده وب» (Web Crawler) بسیار ابتدایی بسازیم؟

آیا تا به حال قصد داشته‌اید اطلاعات خاصی از یک وب‌سایت برای پردازش بیشتر به دست آورید؟ برای مثال اطلاعاتی شبیه امتیازات مسابقات ورزشی، روند بازار سهام، بیت کوین و دیگر قیمت‌های ارز دیجیتال را در نظر بگیرید. اگر اطلاعاتی که نیاز دارید در وب‌سایتی موجود است، می‌توانید یک «خزنده» (crawler) - به‌عنوان یک «scraper» یا عنکبوت نیز شناخته می‌شود - بنویسید تا وب‌سایت را پیمایش و تنها آنچه را که لازم دارید استخراج کنید. بیایید یاد بگیریم با پایتون چگونه این کار را انجام دهیم.

لطفا توجه داشته باشید که وب‌سایت‌های گوناگونی استفاده از خزنده را برای دسترسی به اطلاعاتی که فراهم می‌کند، مجاز نمی‌دانند. پس قبل از ایجاد و فعال کردن یک خزنده روی سایتی معین، ابتدا قوانین و شرایط بهره‌گیری از آن سایت را بررسی کنید.

نصب Scrapy

برای مدیریت خزنده واقعی از ماژولی در پایتون به نام «Scrapy» استفاده می‌کنیم. این کار سریع و ساده است و می‌تواند چندین صفحه وب را دقیقا مانند آنچه با یک مرورگر انجام می‌دهید، مرور کند. توجه داشته باشید با این حال، Scrapy هیچ امکاناتی برای پردازش جاوا اسکریپت هنگام مرور وب‌سایت ندارد. بنابراین وب‌سایت‌ها و برنامه‌هایی که از جاوا اسکریپت برای تنظیم رابط کاربری خود استفاده می‌کنند، نمی‌توانند در این روش به‌درستی مورد استفاده قرار گیرند.

حال نوبت به نصب scrapy می‌رسد و برای این منظور از «virtualenv» کمک می‌گیریم. بهره‌گیری از این شیوه، سبب می‌شود scrapy در دایرکتوری جداگانه نصب شده و تاثیری نیز بر سایر ماژول‌هایی که هم‌اکنون روی سیستم نصب هستند نداشته باشد.

در نخستین گام یک دایرکتوری ایجاد و یک محیط مجازی را در آن دایرکتوری راه‌اندازی کنید.

1mkdir crawler
2cd crawler
3virtualenv venv
4. venv/bin/activate

حال می‌توانید Scrapy را در آن دایرکتوری نصب نمایید.

1pip install scrapy

مطمئن شوید که Scrapy به‌درستی نصب شده است.

1scrapy
2# prints
3Scrapy 1.4.0 - no active project
4
5Usage:
6  scrapy <command> [options] [args]
7
8Available commands:
9  bench         Run quick benchmark test
10  fetch         Fetch a URL using the Scrapy downloader
11  genspider     Generate new spider using pre-defined templates
12  runspider     Run a self-contained spider (without creating a project)
13...

ساخت یک خزنده وب‌سایت (عنکبوت)

هدف این است که یک خزنده برای بارگیری اطلاعات نوشته شود. با scraping برای گرفتن برخی اطلاعات از یک صفحه ویکی‌پدیا در مورد باتری شروع می‌کنیم. صفحه‌ی زیر را در نظر بگیرید.

https://en.wikipedia.org/wiki/Battery_(electricity).

اولین گام در نوشتن یک خزنده، تعریف یک کلاس پایتون توسعه یافته از scrapy.Spider است. اجازه دهید این کلاس را «spider1» نام‌گذاری کنیم.

حداقل چیزهایی که یک کلاس Spider نیاز دارد به شرح زیر است:

  • نامی برای شناسایی spider، در این مورد «ویکی‌پدیا» گزینه‌ی مناسبی به نظر می‌رسد.
  • متغیر start_urls حاوی یک فهرست از URL برای شروع خزیدن است. در اینجا از وب‌سایت ویکی‌پدیا نشان داده شده برای اولین خزیدن استفاده می‌شود.
  • یک متد ()parse برای پردازش صفحه وب و پیدا کردن آنچه می‌خواهیم از آن استخراج کنیم.
1import scrapy
2
3class spider1(scrapy.Spider):
4    name = 'Wikipedia'
5    start_urls = ['https://en.wikipedia.org/wiki/Battery_(electricity)']
6
7    def parse(self, response):
8        pass

اکنون می‌توانیم این عنکبوت را اجرا کنیم تا اطمینان حاصل شود که همه‌چیز درست کار می‌کند. کد مربوطه به شرح زیر اجرا می‌شود:

scrapy runspider spider1.py
# prints
2017-11-23 09:09:21 [scrapy.utils.log] INFO: Scrapy 1.4.0 started (bot: scrapybot)
2017-11-23 09:09:21 [scrapy.utils.log] INFO: Overridden settings: {'SPIDER_LOADER_WARN_ONLY': True}
2017-11-23 09:09:21 [scrapy.middleware] INFO: Enabled extensions:
['scrapy.extensions.memusage.MemoryUsage',
 'scrapy.extensions.logstats.LogStats',
...

غیرفعال کردن Logging

همان‌طور که می‌بینید، اجرای scrapy با کلاس مینیمال و ابتدایی که ایجاد کردیم، باعث ایجاد گروهی از خروجی‌ها می‌شود که مفهوم خاصی را به ما نشان نمی‌دهند. اجازه دهید سطح logging را به «هشدار و تلاش دوباره» (warning and retry) تغییر دهیم؛ خطوط زیر را به ابتدای فایل اضافه کنید.

1import logging
2logging.getLogger('scrapy').setLevel(logging.WARNING)

در هنگام اجرای دوباره عنکبوت، باید یک پیغام لاگ حداقلی را مشاهده کنیم.

استفاده از «Chrome Inspector»

استخراج اطلاعات از یک صفحه وب شامل تعیین موقعیت عنصر HTML است که از آن اطلاعات می‌خواهیم. روش خوب و آسان برای یافتن موقعیت یک عنصر HTML در مرورگر کروم، استفاده از ابزار Inspector است.

  • به صفحه مورد نظر در کروم بروید.
  • موس را روی عنصری قرار دهید که اطلاعات را از آن می‌خواهید.
  • راست کلیک کنید تا منویی برای شما باز شود.
  • Inspect را از منو انتخاب کنید.

این کار باید کنسول توسعه‌دهنده را با «زبانه» (tab) عناصر انتخاب شده باز کند. پایین زبانه‌، باید نوار ابزار وضعیت را با موقعیت عنصر نشان داده‌ شده به‌صورت زیر ببینید:

html body div#content.mw-body h1#firstHeading.firstHeading.

همان‌طور که در اینجا توضیح داده می‌شود، به برخی یا همه بخش‌های این قسمت نیاز دارید.

استخراج عنوان

حالا اجازه دهید یک کد با متد ()parse برای استخراج عنوان صفحه اضافه کنیم.

1...
2    def parse(self, response):
3        print response.css('h1#firstHeading::text').extract()
4...

آرگومان «پاسخ» (Response) یک متد با نام ()css را پشتیبانی می‌کند که می‌تواند عناصر HTML صفحه را بر اساس موقعیت تعیین شده آنها انتخاب کند. برای این مورد، عنصر مورد نظر h1.firstHeading است و به متن آن نیاز داریم؛ بنابراین text:: را به آیتم‌های انتخابی اضافه می‌کنیم. درنهایت، متد ()extract عنصر انتخابی را باز می‌گرداند.

در هنگام اجرای دوباره‌ی scrapy در این کلاس، خروجی زیر دریافت می‌شود:

1[u'Battery (electricity)']

که نشان می‌دهد عنوان از یک فهرست از رشته‌های «Unicode» استخراج‌ شده است.

توصیف (Description) چگونه است؟

برای نشان دادن جنبه‌های بیشتری از استخراج داده‌ها از صفحات وب، اجازه دهید اولین پاراگراف توصیفات را از صفحه ویکی‌پدیای بالا دریافت کنیم. در inspection با استفاده از کنسول توسعه‌دهنده کروم، می‌بینیم که مکان عنصر به‌صورت زیر است:

div#mw-content-text>div>p

این موقعیت تمام عناصر P که همه‌ی توضیحات را شامل می‌شوند، بازمی‌گرداند. از آنجایی‌ که فقط عنصر p را می‌خواهیم، از استخراج‌ کننده زیر استفاده می‌کنیم:

1response.css('div#mw-content-text>div>p')[0]

برای استخراج محتوای متن به صورت جداگانه، CSS extractor ::text را اضافه می‌نماییم:

1response.css('div#mw-content-text>div>p')[0].css('::text')

عبارت نهایی از ()extract که یک فهرست از رشته‌های Unicode را بازمی‌گرداند استفاده می‌کند. از تابع ()join پایتون برای پیوست فهرست استفاده می‌کنیم.

1 def parse(self, response):
2        print ''.join(response.css('div#mw-content-text>div>p')[0].css('::text').extract())

خروجی در حال اجرای Scrapy به همراه این کلاس همان چیزی است که به دنبال آن هستیم:

1An electric battery is a device consisting of one or more electrochemical cells with external connections provided to power electrical devices such as flashlights, smartphones, and electric cars.[1] When a battery is supplying electric power, its positive terminal is
2...

جمع‌آوری داده‌ها با استفاده از «yield»

کد بالا داده‌های استخراج‌شده را به کنسول می‌دهد. هنگامی‌ که نیاز به جمع‌آوری داده‌ها در «JSON» دارید، می‌توانید از دستورالعمل yield استفاده کنید. نحوه عملکرد yield به شرح زیر است:

اجرای یک تابع که حاوی دستورالعمل yield است و «generator» را به «caller» برمی‌گرداند. Generator یک تابع است که caller می‌تواند به‌طور مکرر آن را اجرا کند تا پایان یابد. در این قسمت کد مورد نظر، مشابه بالاست که از دستورالعمل yield برای بازگردانی فهرست عناصر p در HTML استفاده می‌کند.

1...
2    def parse(self, response):
3        for e in response.css('div#mw-content-text>div>p'):
4            yield { 'para' : ''.join(e.css('::text').extract()).strip() }
5...

اکنون می‌توانید عنکبوت را با مشخص کردن یک فایل خروجی JSON به‌صورت زیر به کار ببرید:

1scrapy runspider spider3.py -o joe.json

خروجی تولیدشده به شرح زیر است:

1[
2{"para": "An electric battery is a device consisting of one or more electrochemical cells with external connections provided to power electrical devices such as flashlights, smartphones, and electric cars.[1] When a battery is supplying electric power, its positive terminal is the cathode and its negative terminal is the anode.[2] The terminal marked negative is the source of electrons that when connected to an external circuit will flow and deliver energy to an external device. When a battery is connected to an external circuit, electrolytes are able to move as ions within, allowing the chemical reactions to be completed at the separate terminals and so deliver energy to the external circuit. It is the movement of those ions within the battery which allows current to flow out of the battery to perform work.[3] Historically the term \"battery\" specifically referred to a device composed of multiple cells, however the usage has evolved additionally to include devices composed of a single cell.[4]"},
3{"para": "Primary (single-use or \"disposable\") batteries are used once and discarded; the electrode materials are irreversibly changed during discharge. Common examples are the alkaline battery used for flashlights and a multitude of portable electronic devices. Secondary (rechargeable) batteries can be discharged and recharged multiple
4...

پردازش چندین بیت اطلاعات

اکنون قصد داریم به استخراج چندین بیت مرتبط با اطلاعات نگاه کنیم. برای این مثال، بهترین عناوین «IMDb Box Office» را برای آخر هفته به دست می‌آوریم. این اطلاعات در آدرس زیر در دسترس هستند:

http://www.imdb.com/chart/boxoffice

با استفاده از متد ()parse فیلدهای مختلف را در هر سطر استخراج می‌کنیم. همان‌طور که در بالا توضیح داده شد، مجددا مکان‌های عنصر CSS با استفاده از کنسول توسعه‌دهنده کروم، تعیین می‌شوند:

1...
2    def parse(self, response):
3        for e in response.css('div#boxoffice>table>tbody>tr'):
4            yield {
5                'title': ''.join(e.css('td.titleColumn>a::text').extract()).strip(),
6                'weekend': ''.join(e.css('td.ratingColumn')[0].css('::text').extract()).strip(),
7                'gross': ''.join(e.css('td.ratingColumn')[1].css('span.secondaryInfo::text').extract()).strip(),
8                'weeks': ''.join(e.css('td.weeksColumn::text').extract()).strip(),
9                'image': e.css('td.posterColumn img::attr(src)').extract_first(),
10            }
11...

توجه داشته باشید که انتخاب‌گر تصویر بالا مشخص می‌کند که img زیر رده‌ی td.posterColumn است. باید ویژگی src را با استفاده از عبارت (atr(src:: استخراج کنیم. اجرای عنکبوت، JSON زیر را برمی‌گرداند:

1[
2{"gross": "$93.8M", "weeks": "1", "weekend": "$93.8M", "image": "https://images-na.ssl-images-amazon.com/images/M/MV5BYWVhZjZkYTItOGIwYS00NmRkLWJlYjctMWM0ZjFmMDU4ZjEzXkEyXkFqcGdeQXVyMTMxODk2OTU@._V1_UY67_CR0,0,45,67_AL_.jpg", "title": "Justice League"},
3{"gross": "$27.5M", "weeks": "1", "weekend": "$27.5M", "image": "https://images-na.ssl-images-amazon.com/images/M/MV5BYjFhOWY0OTgtNDkzMC00YWJkLTk1NGEtYWUxNjhmMmQ5ZjYyXkEyXkFqcGdeQXVyMjMxOTE0ODA@._V1_UX45_CR0,0,45,67_AL_.jpg", "title": "Wonder"},
4{"gross": "$247.3M", "weeks": "3", "weekend": "$21.7M", "image": "https://images-na.ssl-images-amazon.com/images/M/MV5BMjMyNDkzMzI1OF5BMl5BanBnXkFtZTgwODcxODg5MjI@._V1_UY67_CR0,0,45,67_AL_.jpg", "title": "Thor: Ragnarok"},
5...
6]

چنین مطالبی را می‌توانید در لینک‌های زیر دنبال کنید.

^^

بر اساس رای ۸ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
MakeUseOf
۱ دیدگاه برای «چگونه یک «خزنده وب» (Web Crawler) بسیار ابتدایی بسازیم؟»

سلام با تشکر از مطالب عالی سایتتون
ایا میشه تعداد بازدید کننده ها رو با وب اسکرپینگ (Web Scraping) با پایتون و کتابخانه Beautiful Soup انجام داد (البته برای هر سایتی نه برای سایت خودمان )

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *