آموزش ساخت ربات Web Scraping با فریمورک Scrapy


Web Scraping تکنیکی است که به منظور استخراج دیتا و ذخیرهٔ آن‌ها از صفحات وب‌ مورد استفاده قرار می‌گیرد به طوری که اصطلاحاً اِسکرپینگ این صفحات شامل فِچ (دانلود) صفحۀ مذکور و در ادامه استخراج دیتا از آن با به‌کارگیری ابزارهای مناسب است تا دیتای مورد نیاز در یک فرمت مشخص جمع‌آوری شده و پردازش‌های ثانویه روی آن‌ها انجام شود که برای آشنایی بیشتر با این تکنیک می‌توانید به آموزش آشنایی با Screen Scraping مراجعه نمایید. در همین راستا در ادامه قصد داریم ابزاری را معرفی کنیم که برای زبان برنامه‌نویسی پایتون در اختیار دولوپرهای این زبان قرار گرفته که با استفاده از آن به سادگی خواهیم توانست به ذخیرهٔ داده‌های سایت‌های مختلف بپردازیم (جهت آشنایی با ابزارهای اپن‌سورس رایج در این حوزه می‌توانید به مقالۀ معرفی ابزارهای اپن‌سورس جهت جمع‌آوری و استخراج اطلاعات از سطح وب مراجعه نمایید.)

اولین کسی باشید که به این سؤال پاسخ می‌دهید

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

- قابلیت استخراج دیتا از صفحات وب به فرمت HTML و XML 
- امکان دسترسی به دیتای مد نظر با استفاده از XPath
- برخورداری از کنسول شِل تعاملی با امکان دیباگ کردن سورس‌کد به منظور مشاهدۀ عملکرد ابزار اِسکرپینگ در جهت استخراج دیتای مربوط به تگ‌های مد نظر
- امکان استخراج دیتا به فرمت‌های مختلف از جمله JSON ،CSV و XML
- قابلیت ذخیرۀ دیتای جمع‌آوری‌شده در فایل‌سیستم لوکال و ...
- امکان اِنکُدینگ دیتای استخراج‌شده و برخورداری از قابلیت تشخیص خودکار نوع اِنکُدینگ فایل‌ها در جهت شناسایی دیتای آسیب‌دیده و غیر استاندارد
- برخورداری از قابلیت توسعه و به‌کارگیری یکسری API جهت بهبود عملکرد ابزار اِسکرپینگ
- برخورداری از یکسری به اصطلاح Middleware و Extention به منظور هندل کردن کوکی وب‌سایت‌ها برای ریکوئست‌های مختلف در هنگام اِسکرپینگ، ارسال ریکوئست‌های اچ‌تی‌تی‌پی و محدود کردن عمق کِراولینگ صفحات
- برخورداری از افزونۀ Telnet Console که مشابه محیط شِل پایتون است با امکان هندل کردن تَسک‌هایی همچون کنترل وضعیت اِسکرپینگ وب‌سایت، توقف یا انجام مجدد فرآیند اِسکرپینگ 
- امکان استفادۀ مجدد از سورس‌کد اپلیکیشن به منظور کرول کردن صفحات از روی سایت‌مَپ یا سورس پیج مد نظر  و کَش کردن فایل‌ها و تصاویر دانلودشده و جلوگیری از دانلود آن‌ها در اسکرپینگ‌های مجدد

آشنایی با نحوۀ پیاده‌سازی رباتی با ابزار Scrapy
در ادامه قصد داریم تا نحوۀ پیاده‌سازی رباتی به منظور اصطلاحاً اِسکرپ کردن یک وب‌سایت با به‌کارگیری ماژول Scrapy را تشریح کرده تا دیتای مد نظر خود را از آن استخراج کنیم که در این آموزش وب‌سایتی تحت عنوان Quotes to Scrape را مورد استفاده قرار داده‌ایم که قصد داریم تا بدین طریق متن مربوط به نقل‌قول‌های افراد به همراه نام ایشان را استخراج کنیم.

در همین راستا، لازم است تا ابتدا ماژول Scrapy را دانلود کرده و روی سیستم خود نصب کنیم اما پیش از آن باید از نصب بودن پایتون و ابزار pip روی سیستم‌عامل خود اطمینان حاصل کنیم به طوری که مثلاً با اجرای کامند زیر در اوبونتو، می‌توانیم ببینیم کدام نسخه از پایتون روی سیستم نصب است:

$ python3 --version
Python 3.6.7

حال نیاز به ابزار pip داریم که وظیفهٔ مدیریت پکیج‌ها را برای این زبان دارا است که با استفاده از دستور زیر نسخهٔ آن را خواهیم یافت:

$ pip3 --version
pip 9.0.1 from /usr/lib/python3/dist-packages (python 3.6)

اگر هم pip نصب نبود، می‌توانیم با استفاده از کامند آن زیر را نصب نماییم:

$ sudo apt install python3-pip

در ادامه، کامند زیر را اجرا می‌کنیم تا ماژول Scrapy نصب گردد:

$ sudo pip3 install scrapy

برای اطمینان حاصل کردن از نصب صحیح این ماژول، کامند زیر را اجرا می‌کنیم:

$ scrapy version
Scrapy 1.5.1

در گام بعد فایلی با نام دلخواه برای مثال quotes_spider با پسوند py. ایجاد کرده و در ادامه به صورت زیر آن را توسعه می‌دهیم:

import scrapy
class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = [
        'http://quotes.toscrape.com/tag/humor/',
    ]
    def parse(self, response):
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').extract_first(),
                'author': quote.xpath('span/small/text()').extract_first(),
            }

        next_page = response.css('li.next a::attr("href")').extract_first()
        if next_page is not None:
            yield response.follow(next_page, self.parse)

در کد فوق ابتدا ماژول مورد نیاز برای اِسکرپینگ صفحۀ وب را ایمپورت کرده و در ادامه کلاسی تحت عنوان QuotesSpider تعریف کرده و آن را از کلاس Spider مربوط به ماژول scrapy ارث‌بری کرده‌ایم به طوری که از این پس کلاس QuotesSpider تمامی خصوصیات کلاس والد را دارا بوده و در ادامه ویژگی‌های مد نظر خود را به آن اضافه خواهیم کرد.

همان‌طور که می‌بینید، متغیری تحت عنوان name انتخاب کرده‌ایم که در گام‌های بعدی به منظور بازگرداندن نتیجۀ حاصل از اجرای پروژه مورد نیاز خواهد بود که برای هر پروژۀ اسکرپینگ می‌باید منحصربه‌فرد باشد و در ادامه آرایه‌ای تحت عنوان start_urls تعریف کرده‌ایم که آدرس صفحات وب مد نظر برای شروع کراولینگ را نگاه‌داری می‌کند؛ سپس فانکشن ()parse از کلاس Spider را فراخوانی کرده و به عنوان  آرگومان ورودی آبجکتی تحت عنوان response از کلاس TextResponse مربوط به این ماژول را پاس داده‌ایم که ریسپانس حاصل از  ریکوئست‌هایی را نگاه‌داری می‌کند که به ازای هر آدرس یوآرال به صفحۀ وب مد نظر ارسال می‌شوند و در واقع حاوی محتوای دانلودی از پیج مذکور هستند.

در ادامه، فانکشن ()parse را بدین ترتیب پیاده‌سازی کرده‌ایم که گفته‌ایم به ازای تمامی اِلِمان‌های quote در ریسپانس بازگشتی، متن مربوط به تگ‌های <span> و همچنین تگ‌های داخلی آن مانند تگ <small> که مربوط به متن نقل‌قول و نویسندۀ آن است را در قالب دیتااستراکچر دیکشنری استخراج کند.

برای تکمیل این ربات، متغیری تحت عنوان next_page تعریف کرده‌ایم که قرار است تا آدرس یوآرال‌های موجود داخل تگ‌های <href> مربوط به ریسپانس بازگشتی را استخراج کرده و نگاه‌داری کند که در سطر بعدی گفته‌ایم به ازای هر یک از آدرس‌های موجود در متغیر مذکور، یک ریکوئست‌ به آدرس‌ لینک مد نظر ارسال شود و در ادامه پس از بازگشت ریسپانس حاصل از این ریکوئست، فانکشن ()parse به عنوان یک کال‌بک فانکشن مجدداً فراخوانی شده و صفحۀ وب اصطلاحاً پارس می‌شود.

حال که سورس‌کد مربوط به فایل quotes_spider.py را پیاده‌سازی کردیم، با به‌کارگیری کامند زیر می‌توانیم آن را اجرا کنیم:

$ scrapy runspider quotes_spider.py -o quotes.json

نحوۀ عملکرد کامند فوق بدین ترتیب است که به محض اجرا، به دنبال کلاس Spider در فایل مذکور گشته و آن را اجرا می‌کند و در نهایت نتیجۀ حاصل در قالب دیتایی با فرمت جیسون و در  فایلی تحت عنوان quotes.json بازگردانده می‌شود به طوری که داریم:

[  
   {  
      "text":"\u201cThe person, be it gentleman or lady, who has not pleasure in a good novel, must be intolerably stupid.\u201d",
      "author":"Jane Austen"
   },
   {  
      "text":"\u201cA day without sunshine is like, you know, night.\u201d",
      "author":"Steve Martin"
   },
   {  
      "text":"\u201cAnyone who thinks sitting in church can make you a Christian must also think that sitting in a garage can make you a car.\u201d",
      "author":"Garrison Keillor"
   },
   {  
      "text":"\u201cBeauty is in the eye of the beholder and it may be necessary from time to time to give a stupid or misinformed beholder a black eye.\u201d",
      "author":"Jim Henson"
   },
   {  
      "text":"\u201cAll you need is love. But a little chocolate now and then doesn't hurt.\u201d",
      "author":"Charles M. Schulz"
   },
   {  
      "text":"\u201cRemember, we're madly in love, so it's all right to kiss me anytime you feel like it.\u201d",
      "author":"Suzanne Collins"
   },
   {  
      "text":"\u201cSome people never go crazy. What truly horrible lives they must lead.\u201d",
      "author":"Charles Bukowski"
   },
   {  
      "text":"\u201cThe trouble with having an open mind, of course, is that people will insist on coming along and trying to put things in it.\u201d",
      "author":"Terry Pratchett"
   },
   {  
      "text":"\u201cThink left and think right and think low and think high. Oh, the thinks you can think up if only you try!\u201d",
      "author":"Dr. Seuss"
   },
   {  
      "text":"\u201cThe reason I talk to myself is because I\u2019m the only one whose answers I accept.\u201d",
      "author":"George Carlin"
   },
   {  
      "text":"\u201cI am free of all prejudice. I hate everyone equally. \u201d",
      "author":"W.C. Fields"
   },
   {  
      "text":"\u201cA lady's imagination is very rapid; it jumps from admiration to love, from love to matrimony in a moment.\u201d",
      "author":"Jane Austen"
   }
]

جمع‌بندی
از جمله مهم‌ترین ویژگی‌های ماژول Scrapy می‌توان قابلیت پردازش ریکوئست‌های مختلف به صورت اصطلاحاً Asynchronous (غیر هم‌زمان و موازی) را نام برد بدین معنی که این ماژول برای ارسال ریکوئست به وب اپلیکیشن مد نظر منتظر بازگشت ریسپانس از آن نمی‌ماند بلکه توانایی هندل کردن چندین ریکوئست را به صورت موازی دارا است که این امر منجر به افزایش سرعت کراولینگ صفحات وب می‌شود (در پایان اگر علاقه‌مند به یادگیری زبان برنامه‌نویسی پایتون هستید، توصیه می‌کنیم به دورۀ آموزش پایتون در سکان آکادمی مراجعه نمایید.)



اکرم امراه‌نژاد