خانه / علم داده / دست به کد / دست به کد – استخراج متن وب سایتها با Scrapy
استخراج اطلاعات از وب سایتها با اسکرپی

دست به کد – استخراج متن وب سایتها با Scrapy

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

چرا Scrapy ؟

یکی از منابع اصلی داده در دنیا ، متون و مطالب موجود در سایتهای اینترنتی است مانند نظرات کاربران، اخبار روزانه، فروشگاه ها و محصولات، مقالات تخصصی، شبکه های اجتماعی و مانند آن که نیاز به دسترسی به این منبع بزرگ، ابزارها و کتابخانه های مختلفی را برای استخراج حرفه ای و ساده داده ها از آن ایجاد کرده است. یکی از کتابخانه های معروف پایتون در خواندن و استخراج اطلاعات مفید از صفحات وب ، کتابخانه Scrapy است که به چند دلیل آنرا برای استخراج متون انتخاب کرده ایم ، اول اینکه یک کتابخانه کامل برای خزش در وب (Crawling) با قابلیت دنبال کردن لینک های موجود در هر صفحه و استخراج تمام لینک های یافت شده است . دوم اینکه برای پردازش موازی و همزمان صفحات مختلف و ایجاد خط تولید (pipeline) امکانات مناسب و ساده ای دارد که سرعت کار را بالا می برد و آخرین نکته هم اینکه کار با آن بسیار ساده است و در کمتر از ده خط ، شما یک خزنده وب (برنامه ای که اطلاعات سایت ها را استخراج و لینک های آنها را برای اطلاعات جدید بررسی می کند) تمام عیار می توانید بسازید .

نصب Scrapy

مشابه سایر برنامه های پایتون کافیست با دستور

pip install scrapy

این کتابخانه را نصب کنید .  برای اجرای آن نیاز به نصب بعضی کتابخانه ها و نرم افزارهای موردنیاز آنرا هم داریم که اکثر آنها با دستور فوق به صورت اتومات نصب خواهند شد و تنها چیزی که باقی می ماند نرم افزار PyWin32 است که بعضی امکانات خاص ویندوز را برای برنامه های پایتون، فراهم می کند. آنرا از این آدرس، دانلود و نصب کنید (برای اکثر شما نسخه ۳۲ بیتی نرم افزار مناسب خواهد بود) . اکنون آماده استخراج اطلاعات هستید.

ساخت یک پروژه

در این آموزش، می خواهیم عنوان و متن تعدادی از اخبار سایت ایسنا (خبرگزاری دانشجویان ایران )را استخراج کنیم. بعد از نصب اسکرپی، در خط فرمان، دستور زیر را وارد می کنیم :

1
$ scrapy startproject isna_crawler

با این دستور خروجی زیر را خواهید دید :

1
2
3
4
5
6
7
8
9
10
C:\Users\Mojtaba\>scrapy startproject isna_crawler
2016-05-22 03:37:19 [scrapy] INFO: Scrapy 1.0.3 started (bot: scrapybot)
2016-05-22 03:37:19 [scrapy] INFO: Optional features available: ssl, http11, boto
2016-05-22 03:37:19 [scrapy] INFO: Overridden settings: {}
New Scrapy project 'isna_crawler' created in:
C:\Users\Mojtaba\\isna_crawler

You can start your first spider with:
cd isna_crawler
scrapy genspider example example.com

همانطور که مشاهده می کنید یک پوشه با نام  isna_crawler ساخته خواهد شد که ساختار داخلی این پوشه از قرار زیر است :

1
2
3
4
5
6
7
8
├── scrapy.cfg
└── isna_crawler
     ├── __init__.py
     ├── items.py
     ├── pipelines.py
     ├── settings.py
     └── spiders
          └── __init__.py

تنظیمات متعدد اسکرپی، در فایل settings.py قرار دارد که برای مقاصد فعلی، به پیش فرض ها بسنده می کنیم و به این فایل کاری نداریم . فایل items.py ساختار داده هایی که قصد استخراج آنها را داریم مشخص می کند (چه اطلاعاتی از هر صفحه باید استخراج شوند ) . تنظیمات سراسری اسکرپی مانند مکان قرارگیری فایل تنظیمات هم در فایل scrapy.cfg قرار میگیرد که این فایل را هم دست نخورده باقی می گذاریم. فایل pipelines.py برای ساخت یک خط تولید پیشرفته و مرحله به مرحله استفاده می شود که خوانندگان حرفه ای تر به آن نیاز خواهند داشت (مشابه این مثال که خواندن اطلاعات و ذخیره آنها در مانگو را به صورت خط تولید انجام داده است) و نهایتاً در پوشه spiders کدهای خزنده ما قرار خواهند گرفت .

بنابراین ما کلاً به دو تا فایل items.py و isna_spider.py (در ادامه توضیح داده شده است) نیاز خواهیم داشت .

 

تعریف ساختار داده ها

برای این آموزش، از هر خبر ، به متن ، تاریخ ، گروه و عنوان خیر نیاز داریم . فایل items.py را باز می کنیم و به کلاس IsnaCrawlerItem که فرزند کلاس  scrapy.Item این چهار فیلد را اضافه می کنیم . حاصل کار شبیه به زیر خواهد بود :

1
2
3
4
5
6
7
8
9
import scrapy

class IsnaCrawlerItem(scrapy.Item):
# define the fields for your item here like:
     title = scrapy.Field()
     body = scrapy.Field()
     category = scrapy.Field()
     tags = scrapy.Field()
     pubDate = scrapy.Field()

کار ما در این بخش تمام است .

انتخاب عناصر مورد نیاز یک خبر به کمک XPath

اسکرپی برای انتخاب و استخراج اطلاعات مورد نیاز یک صفحه، در حالت استاندارد، از گرامر XPath که در XML استفاده می شود بهره می برد. اگر با برچسب های HTML آشنا هستید، گرامر ساده آنرا به سرعت درک خواهید کرد و اگر احیاناً در این زمینه هم تازه کار هستید، توصیه می کنم که مروری بر این زبان ساده ای که دنیای وب را تسخیر کرده است ، بیندازید.

یکی از صفحاتی که قصد استخراج اطلاعات آنرا دارید، باز کنید و با کلیک راست روی صفحه و انتخاب گزینه Inspect Element ، پنجره انتخاب و مشاهده عناصر HTML صفحه را باز کنید . حال به ازای هر بخشی که اطلاعات آنرا نیاز دارید باید یک آدرس منحصر بفرد پیدا کنید. مثلاً در سایت ایسنا  :

  • تیتر خبر : در یک پاراگراف قرار گرفته است که این پاراگراف خود در یک  Div با کلاس titr جای گرفته است بنابراین آدرس XPath آن عبارتست از ‘//div[@class=”titr”]//p/text()’ که البته تابع text برای انتخاب متن قرار گرفته در آن عنصر استفاده شده است . لازم به ذکر است در آدرس دهی XPath رابطه فرزند و پدری با // نمایش داده می شود و فرزند بدون واسطه با / . یعنی عنصری که // نمایش داده می شود ممکن است مستقیما فرزند یک عنصر نباشد بلکه نوه یا نسل چندم آن در سلسله مراتب ساختار صفحه باشد اما / به معنای فرزند مستقیم و بلافاصله یک عنصر است .
  • متن خبر : متن خبر درون یک  Div با کلاس body قرار گرفته است و درون پاراگرافهای متعدد قرار گرفته است. برای اینکه مجبور به پردازش تک تک پاراگراف ها و استخراج متن آنها نشویم از آدرس //text استفاده می کنیم که متن تمام فرزندان آنرا برگرداند اما این تابع تمام آنها را درون یک لیست بر می گرداند که برای ایجاد یک متن پشت سر هم و بدون خطوط خالی ، آنها را با دستور strip  از فضاهای خالی ابتدا و انتهای رشته ها، پالایش کرده و با دستور join همه رشته ها را به هم می چسبانیم . نمونه کد نهایی را ادامه خواهید دید.
  • گروه خبر : گروه هر خبر در بالای متن خبر و درون یک Div با کلاس cervees (سرویس؟؟) قرار گرفته است و با آدرس XPath معادل //div[@class=”cervees”]/text() به استخراج سرویس خبری یا همان گروه خبر پرداخته و کلمه “» سرویس: ” را هم از ابتدای آن حذف می کنیم .
  • تاریخ خبر : تاریخ خبر در یک Div با کلاس newsPubDate قرار گرفته است که مشابه قبل آنرا هم استخراج می کنیم .
  • برچسب های خبر : برای استخراج برچسب های خبر ، ابتدا باید ببینیم خبر دارای برچسب هست یا نه . اگر دارای برچسب باشد ، درون Div با کلاس tag قرار گرفته اند  که هر برچسب خود درون تگ a جای گرفته است . پس ابتدا تمام a های درون div با کلاس tag را انتخاب می کنیم و در مرحله بعد با یک حلقه ساده، متن تک تک لینک ها را که همان برچسب ها باشند استخراج کرده و درون یک لیست ذخیره می کنیم .

اگر با انتخابگرهای CSS  آشنا هستید، می توانید برای انتخاب عناصر از آنها هم استفاده کنید یعنی به جای تابع xpath از تابع css روی انتخابگر صفحه استفاده کنید بنابراین آدرس دهی زیر هم  در اسکرپی معتبر است :

response.css(‘span.title a::attr(href’))

برای بررسی مستندات کامل اسکرپی در باب انتخابگرها به این آدرس می توانید مراجعه کنید .

ساخت خزنده

با مقدمه فوق در انتخاب عناصر مورد نیاز صفحه، به مرحله آخر از کار یعنی نوشتن کد خزنده و استخراج اطلاعات می رسیم. کافیست یک فایل با نام isna_spider.py درون پوشه spiders بسازیم و درون آن هم یک کلاس IsnaSpider با محتوای زیر :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# -*- coding: utf-8 -*-
from scrapy.selector import Selector
from Isna_Crawler.items import IsnaCrawlerItem
from scrapy.spiders import CrawlSpider,Rule
from scrapy.linkextractors import LinkExtractor

class IsnaSpider(CrawlSpider):
     name = "isna"
     allowed_domains = ["www.isna.ir"]
     start_urls = [
"http://www.isna.ir/",
# "http://www.isna.ir/page/archive.xhtml?date=1395%2F03%2F01&page=1&lang=fa&pageSize=20&cerveesCode=all",
#"http://www.isna.ir/search?lang=fa&q=%D8%AC%D8%B4%D9%86%D9%88%D8%A7%D8%B1%D9%87"
]
     rules = [Rule(LinkExtractor(allow=('/fa/news/\d+/', )), callback='parse_item', follow=True)]

     def parse_item(self, response):
          item = IsnaCrawlerItem()
          selector = response.selector
          item['title'] = selector.xpath('//div[@class="titr"]//p/text()').extract()[0].strip()
          item['body'] = ' '.join([x.strip() for x in (selector.xpath('//div[@class="body"]//text()').extract())])
          item['body'] = item['body'].replace('\"','').replace("\'",'').replace(u"«","").replace(u"»","").replace(",","").replace(u"انتهای پیام","")
          item['category'] = selector.xpath('//div[@class="cervees"]/text()').extract()  [0].replace(u"سرویس:","").replace(u"«","").replace(u"»","").replace(u'\"',"").strip()
          item['pubDate'] = selector.xpath('//div[@class="newsPubDate"]/text()').extract()[0].strip()
          item["tags"] = []
          tags = selector.xpath('//div[@class="tag"]/a')
          if tags :
               for tag in tags :
                    item["tags"].append(tag.xpath("text()").extract()[0])

          yield item

نگران نباشید . ساختار کد فوق بسیار ساده و قابل فهم است . درون کلاس IsnaSpider کافیست ابتدا نام خزنده را تعیین کنیم که در اینجا نام isna را انتخاب کرده ایم و در مرحله دوم هم نام دامنه ای که فقط لینک های مربوط به آن دامنه ،پردازش می شوند را وارد می کنیم که در اینجا همان آدرس خبرگزاری ایسنا www.isna.ir را وارد کرده ایم .
در مرحله بعد، آدرس شروع اخبار start_urls را باید وارد کنیم که ما از www.isna.ir استفاده کرده ایم اما اگر حرفه ای تر بخواهیم کار کنیم، می توانیم به کمک الگویی که در صفحات آرشیو خبر به دست می آوریم ، با کمک چند حلقه تو در تو، کد لینک های اخبار چند سال گذشته خبرگذاری را تولید کنیم و در متغیر  start_urls ذخیره کنیم.

در مرحله بعد باید الگوی یک صفحه خبری که خزنده ما فقط آن صفحات را بررسی  وپردازش کند وارد می کنیم : (از استاندارد عبارات باقاعده باید استفاده کنیم)

rules = [Rule(LinkExtractor(allow=('/fa/news/\d+/', )), callback='parse_item', follow=True)]

و در مرحله آخر هم کد تابع parse که به ازای هر لینکی که خزنده با شرایط فوق پیدا می کند آن را برای استخراج اطلاعات صفحه صدا می زند را می نویسیم که با توجه به توضیحاتی که در باب انتخابگرها دادیم، نیاز به توضیح خاصی ندارد . متغیری از نوع IsnaCrawlerItem تعریف کرده و تک تک عناصر مورد نیاز آنرا با انتخابگرهای XPath مقدار دهی می کنیم و خروجی را با دستور yield بر میگردانیم .

اجرای خزنده و استخراج اطلاعات

برای اجرای این خزنده که نام isna را به آن داده ایم باید دستور زیر را در خط فرمان وارد کنیم  :

scrapy crawl isna

اما برای راحتی کار، این دستور را هم در فایل main.py ذخیره می کنیم و با اجرای آن فایل درون محیط برنامه نویسی ، بدون نیاز به مراجعه به خط فرمان، کار استخراج اطلاعات را انجام می دهیم .

برای ذخیره اطلاعات در یک فایل csv می توانیم از دستور scrapy crawl isna -o news.csv -t csv استفاده کنیم .

(اگر بخواهیم کنترل کاملتری روی این فایل و خروجی خود داشته باشیم ، می توانیم در تابع parse و در انتهای عملیات استخراج اطلاعات ، خودمان آنها را در یک یا چند فایل ذخیره کنیم .)

سخن آخر :

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

نمونه کدهای نوشته شده برای این آموزش را می توانید در این آدرس از گیت هاب بیابید و در صورت نیاز، آنرا fork کرده و به دلخواه تغییرات خودتان را در کدها اعمال کنید.

 

۱۱ نظرات

  1. با سلام و تشکر از سایت فوق العاده خوبتون.

    مهندس جان من یه سوأل داشتم ممنون میشم اگه راهنماییم کنین.

    میخاستم بدونم میشه بسته ی هضم رو روی یکی از توزیع های پایتون به نام آناکوندا نصب کرد؟؟؟

     

    • سلام و عرض ادب
      اگر با دستوراتی که توی سایت خود کوندا اومده ، هضم را نصب کنید :
      https://anaconda.org/pypi/hazm
      نسخه ۰.۳ هضم نصب می شود و طبق توصیه خود سازندگان هضم :‌
      https://github.com/sobhe/hazm/issues/42
      بهتر است نسخه ۰.۴ آنرا با دستور زیر نصب کنید :
      python -m pip install hazm==0.4 nltk==3.0.0 –upgrade
      اگر پایتون مورد استفاده در دستور فوق، پایتون موجود در آناکوندا باشد، هضم به راحتی روی کوندا نصب خواهد شد و قابل استفاده خواهد بود .

      • خیلی ممنون از راهنماییتون.

        من از دستور خود سایت کوندا نصب کردم و بدون خطا وبه طور کامل نصب شد اما موقع فراخونی پیغام خطای زیر رو میده:

        AttributeError: ‘module’ object has no attribute ‘POSTagger’

        توی اینترنت گشتم ولی علت این خطارو پیدا نکردم. به نظر شما مشکل کجاست؟

        ممکنه اشکال از  بسته ی nltk باشه؟

        ببخشید سوألم ادامه دار شد.

         

  2. راستی یادم رفت بگم مهندس جان سیستم من win 32 bit هستش و ورژن پایتونم هم ۲٫۷ و تا حالا تمامی بسته های مورد نیازمو خیلی راحت نصب کردم و ازشون استفاده کردم ولی هرکاری میکنم هضم فراخونی نمیشه!

    خیلی گشتم ولی دقیقاً نمیدونم هضم درست نصب نشده که این خطارو میده یا اینکه درست نصب شده و اشکال از جای دیگه است!

    AttributeError: ‘module’ object has no attribute ‘POSTagger’

    ممنون میشم راهنمایی کنین. چون به شخص دیگه ای تو این زمینه دسترسی ندارم.

    • با سلام
      من خودم این مشکل را داشتم یادمه هم pyWin32 نسخه ۳۲ بیتی را نصب کردم و هم هضم را پاک کردم (چون توی سایت کوندا نسخه ۳ هضم موجوده و توی خود ریپوزیتوری اصلی نسخه ۵ اما نسخه ۴ این مشکل را حل میکنه) و با این دستور نصبش کردم . مشکلم حل شد :‌
      python -m pip install hazm==0.4 nltk==3.0.0 –upgrade

  3. Thank you for having taken your time to provide us with your valuable information relating to your stay with us.we are sincerely concerned.., Most importantly, you Keepit the major.

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

  5. سلام تشکر از زحماتتون ، مهندس من این مراحل رو دقیقاً به همین نحو انجام دادم منتهی در پایان فایل csv من خالی هست و اطلاعاتی استخراج نکرده .. ممکنه آدرس های کنونی که در قسمت xpath باید بدیم تغییر کرده باشه؟ چون من ادرس های موجود در آموزش رو زدم

  6. سلام کد خروجی نمیده راهنمایی میفرمایید.

پاسخ دهید

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

Time limit is exhausted. Please reload CAPTCHA.