کنیم. شما میتوانید نام عنکبوت خود را بر اساس نیازهای خود تعیین کنید.
ساختار پروژه نهایی چیزی مانند تصویر زیر خواهد بود:
?
همان طور که میبینید یک پوشه مستقل برای هر عنکبوت وجود دارد. شما میتوانید چند عنکبوت را به یک پروژه منفرد اضافه کنید. اگر فایل عنکبوت electronics.py را باز کنیم با چیزی مانند زیر مواجه میشویم:
1
2
3
4
5
6
7
8
9
10
11
# -*- coding: utf-8 -*-
import scrapy
class ElectronicsSpider(scrapy.Spider):
name = "electronics"
allowed_domains = ["www.olx.com.pk"]
start_urls = ['http://www.olx.com.pk/']
def parse(self, response):
pass
چنان که مشاهده میکنید، ElectronicsSpider یک زیرکلاس از scrapy.Spider است. مشخصه name در واقع نام عنکبوت است که در دستور تولید عنکبوت تعیین شده است. این نام در زمانی که خزنده، خود را اجرا میکند به کار میآید. مشخصه allowed_domains تعیین میکند که کدام دامنهها در دسترس این خزنده هستند و start_urls جایی است که URL-های ابتدایی در آنجا نگهداری میشوند. این URL-های ابتدایی در زمان آغاز به کار عنکبوت مورد نیاز هستند. علاوه بر ساختار فایل، این یک قابلیت خوب برای ایجاد کرانهایی برای خزنده است.
متد parse چنان که از نامش برمیآید، محتوای صفحهای را که مورد دسترسی قرار داده است تحلیل خواهد کرد. ما میخواهیم خزندهای بنویسیم که به چندین صفحه برود و به این منظور باید برخی تغییرات ایجاد کنیم.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
class ElectronicsSpider(CrawlSpider):
name = "electronics"
allowed_domains = ["www.olx.com.pk"]
start_urls = [
'https://www.olx.com.pk/computers-accessories/',
'https://www.olx.com.pk/tv-video-audio/',
'https://www.olx.com.pk/games-entertainment/'
]
rules = (
Rule(LinkExtractor(allow=(), restrict_css=('.pageNextPrev',)),
callback="parse_item",
follow=True),)
def parse_item(self, response):
print('Processing..' + response.url)
برای این که خزنده به چندین صفحه سر بزند، به جای scrapy.Spider یک زیرکلاس از آن ایجاد میکنیم. این کلاس موجب میشود که خزش روی صفحههای چندگانه آسانتر باشد. شما میتوانید با کد تولید شده هر کاری که دوست دارید انجام دهید، اما باید مواظب باشید که دوباره به صفحههای قبلی بازنگردید.
گام بعدی این است که متغیرهای قاعده خود را تنظیم کنید. در اینجا قواعد ناوبری وبسایت را بررسی میکنیم. LinkExtractor در واقع پارامترهایی برای رسم کرانها میگیرد. ما در این مثال از پارامتر restrict_css برای تعیین کلاسی جهت صفحه بعدی استفاده میکنیم. اگر به این صفحه (+) مراجعه کنید، چیزی مانند تصویر زیر را مشاهده خواهید کرد:
?
pageNextPrev کلاسی است که برای واکشی لینکها صفحههای بعدی استفاده میشود. پارامتر call_back مشخصی میکند که کدام متد برای دسترسی به عناصر استفاده میشود. این متد را در ادامه بررسی میکنیم.
به خاطر داشته باشید که باید نام متد را از ()parse به ()parse_item با هر چیزی که دوست دارید تغییر دهید تا از override شدن کلاس مبنا جلوگیری شود. در غیر این صورت قاعده شما کار نخواهد کرد حتی اگر مقدار follow=True تنظیم کنید.
تا به اینجا همه چیز به خوبی پیش رفته است. در ادامه خزندهای را که تا به اینجا ساختهایم تست میکنیم. در دایرکتوری پروژه به ترمینال بروید و دستور زیر را وارد کنید:
1
scrapy crawl electronics
پارامتر سوم در واقع نام عنکبوتی است که قبلاً در مشخصه name کلاس ElectronicsSpiders تعیین کردهایم. در ترمینال اطلاعات مفید زیادی مییابید که برای دیباگ کردن خزنده مفید هستند. در صورتی که نخواهید اطلاعات دیباگ کردن را ببینید، میتوانید گزینه debugger را غیرفعال کنید. دستور مشابهی با سوئیچ –nolog وجود دارد:
1
scrapy crawl --nolog electronics
اگر این دستور را در حال حاضر اجرا کنید، خروجی چیزی مانند زیر خواهد بود:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Adnans-MBP:olx AdnanAhmad$ scrapy crawl --nolog electronics
Processing..https://www.olx.com.pk/computers-accessories/?page=2
Processing..https://www.olx.com.pk/tv-video-audio/?page=2
Processing..https://www.olx.com.pk/games-entertainment/?page=2
Processing..https://www.olx.com.pk/computers-accessories/
Processing..https://www.olx.com.pk/tv-video-audio/
Processing..https://www.olx.com.pk/games-entertainment/
Processing..https://www.olx.com.pk/computers-accessories/?page=3
Processing..https://www.olx.com.pk/tv-video-audio/?page=3
Processing..https://www.olx.com.pk/games-entertainment/?page=3
Processing..https://www.olx.com.pk/computers-accessories/?page=4
Processing..https://www.olx.com.pk/tv-video-audio/?page=4
Processing..https://www.olx.com.pk/games-entertainment/?page=4
Processing..https://www.olx.com.pk/computers-accessories/?page=5
Processing..https://www.olx.com.pk/tv-video-audio/?page=5
Processing..https://www.olx.com.pk/games-entertainment/?page=5
Processing..https://www.olx.com.pk/computers-accessories/?page=6
Processing..https://www.olx.com.pk/tv-video-audio/?page=6
Processing..https://www.olx.com.pk/games-entertainment/?page=6
Processing..https://www.olx.com.pk/computers-accessories/?page=7
Processing..https://www.olx.com.pk/tv-video-audio/?page=7
Processing..https://www.olx.com.pk/games-entertainment/?page=7
از آنجا که مقدار follow=True را تنظیم کردهایم، خزنده قاعده صفحه بعد را بررسی میکند و به ناوبری خود ادامه میدهد، مگر این که به صفحهای برخورد کند که قاعده در مورد آن صدق نمیکند که معمولاً صفحه آخر لیست است.
اینک تصور کنید بخواهیم منطق مشابهی را با چیزهایی که در این صفحه (+) اشاره شدهاند بنویسیم، ابتدا باید کدی بنویسیم که روی چندین پردازنده کار کند. همچنین باید کدی بنویسیم که نه تنها به صفحه بعد برود، بلکه اسکریپت را از طریق عدم دسترسی به URL های ناخواسته، در داخل کرانهای تعریف شده نگه دارد. Scrapy همه این وظایف را از دوش ما بر میدارد و کاری میکند که صرفاً روی منطق متمرکز شویم، یعنی خزندهای برای استخراج اطلاعات بنویسیم. اینک قصد داریم کدی بنویسیم که لینکهای آیتم منفرد مانند صفحههای فهرستبندی را واکشی کند بدین ترتیب کدی را که در متد parse_item داشتیم تغییر میدهیم:
1
2
3
item_links = response.css('.large > .detailsLink::attr(href)').extract()
for a in item_links:
yield scrapy.Request(a, callback=self.parse_detail_page)
در این کد ما لینکها را با استفاده از متد css. پاسخ واکشی میکنیم. چنان که گفتیم میتوان از xpath نیز استفاده کرد و بستگی به نظر شما دارد.. در این حالت همه چیز کاملاً ساده خواهد بود:
?
لینک دیگر کلاسی به نام detailsLink دارد. اگر تنها از (‘response.css(‘.detailsLink استفاده کنیم، در این صورت لینکهای تکراری از یک مدخل منفرد گردآوری میشوند، زیرا لینکها در تگهای img و h3 تکرار شدهاند. همچنین به کلاس والد large اشاره کردهایم تا لینکهای یکتا دریافت کنیم. ما از (attr(href:: برای استخراج بخش href خود لینک استفاده میکنیم. سپس از متد ()extract استفاده میکنیم.
دلیل استفاده از این متد آن است که css. و xpath. شیء SelectorList را بازگشت میدهند و ()extract به بازگرداندن DOM واقعی برای پردازش بیشتر کمک میکند. در نهایت لینکها را در scrapy.Request با یک callback به صورت کامل yield میکنیم. ما کد داخلی Scrapy را بررسی نکردهایم، اما احتمالاً از yield به جای return استفاده میکند، زیرا میتوانید چندین آیتم را return کنید. از آنجا که خزنده باید مراقب لینکهای چندگانه همراه با هم نیز باشد، در این صورت yield بهترین انتخاب خواهد بود.
متد parse_detail_page چنان که از نامش هویدا است، اطلاعات منفرد را از صفحه جزییات تحلیل میکند. بنابراین اتفاقی که در عمل میافتد این است که:
از آنجا که تنها پیمایش دوسطحی وجود دارد، قادر شدیم به کمک دو متد به پایینترین سطح برسیم. اگر قصد داشتیم شروع به خزش از صفحه اصلی وبسایت OLX بکنیم، باید سه متد مینوشتیم که دو مورد برای واکشی دستهبندیهای فرعی و مداخل آنها و متد آخر برای تحلیل اطلاعات واقعی بود.
در نهایت قصد داریم اطلاعات واقعی را تحلیل کنیم که روی یکی از مدخلها مانند این (+) در دسترس است.
تحلیل اطلاعات این صفحه کار دشواری نیست، اما این کاری است که باید روی اطلاعات ذخیرهشده صورت بگیرد. ما باید model را برای دادههای خود تعریف کنیم. این بدان معنی است باید به Scrapy بگوییم چه اطلاعاتی را میخواهیم برای استفادههای بعدی ذخیره کنیم. در ادامه فایل item.py را ویرایش میکنیم که قبلاً از سوی Scrapy ایجاد شده است:
1
2
3
4
5
6
import scrapy
class OlxItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
pass
OlxItem کلاسی است که در آن فیلدهای مورد نیاز برای نگهداری اطلاعات را تنظیم خواهیم کرد. ما قصد داریم سه فیلد برای کلاس مدل خود تعریف کنیم.
1
2
3
4
5
6
import scrapy
class OlxItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
pass
در این فیلدها عنوان مطلب، قیمت و خود URL را ذخیره میکنیم. در این مرحله به فایل کلاس خزنده بازمیگردیم و parse_detail_page را ویرایش میکنیم. اکنون یک متد برای آغاز نوشتن کد، یکی برای تست از طریق اجرای کل خزنده و دیگری برای مشاهده درست بودن مسیر است، اما ابزار جالب دیگری نیز وجود دارد که از سوی Scrapy عرضه شده است.
Shell یا پوسته Scrapy (+) یک ابزار خط فرمان است که فرصت تست کد تحلیلشده را بدون اجرای کلی خزنده در اختیار ما قرار میدهد. برخلاف خزنده که به همه لینکها سر میزند، شل Scrapy اقدام به ذخیرهسازی DOM یک صفحه منفرد برای استخراج دادهها میکند:
1
Adnans-MBP:olx AdnanAhmad$ scrapy shell https://www.olx.com.pk/item/asus-eee-pc-atom-dual-core-4cpus-beautiful-laptops-fresh-stock-IDUVo6B.html#4001329891
اکنون میتوان به سادگی کد را بدون مراجعه چندباره به همان URL تست کرد. بدین ترتیب عنوان صفحه را با کد زیر واکشی کردهایم:
1
2
In [8]: response.css('h1::text').extract()[0].strip()
Out[8]: u"Asus Eee PC Atom Dual-Core 4CPU's Beautiful Laptops fresh Stock"
آن response.css آشنا را اینجا هم میتوانید مشاهده کنید. از آنجا که کل DOM موجود است میتوان هر کاری با آن انجام داد. برای نمونه آن را میتوان به صورت زیر واکشی کرد:
1
2
In [11]: response.css('.pricelabel > strong::text').extract()[0]
Out[11]: u'Rs 10,500'
نیازی به انجام هیچ کاری برای واکشی url نیست، زیرا response.url اقدام به بازگشت دادن URL-ی میکند که هم اینک مورد دسترسی قرار گرفته است.
اکنون که همه کد را بررسی کردیم، نوبت آن رسیده است که parse_detail_page را مورد استفاده قرار دهیم:
1
2
3
4
5
6
7
title = response.css('h1::text').extract()[0].strip()
price = response.css('.pricelabel > strong::text').extract()[0]
item = OlxItem()
item['title'] = title
item['price'] = price
item['url'] = response.url
yield item
وهله OlxItem پس از تحلیل کردن اطلاعات لازم ایجاد میشود و مشخصهها تعیین میشوند. اینک که نوبت اجرای خزنده و ذخیرهسازی اطلاعات رسیده است، کمی تغییر در دستور باید ایجاد کرد:
1
scrapy crawl electronics -o data.csv -t csv
ما نام فایل و قالببندی فایل را برای ذخیرهسازی دادهها ارسال میکنیم. زمانی که دستور فوق اجرا شود فایل CSV برای شما میسازد. چنان که میبینید روند سادهای است و برخلاف خزندهای که خود میباید مینوشتیم، در اینجا کافی است رویه مورد نیاز برای ذخیرهسازی دادهها را بنویسیم.
اما زیاد عجله نکنید! کار به همین جا ختم نمیشود. شما میتوانید حتی دادهها را در قالب JSON نیز ذخیره کنید، تنها کاری که به این منظور لازم است ارسال مقدار json با سوئیچ t- است.
Scrapy قابلیتهای دیگری نیز در اختیار ما قرار میدهد. برای نمونه میتوان یک نام فایل ثابت ارسال کرد که در سناریوهای دنیای واقعی هیچ معنایی ندارد. چرا باید برنامهای نوشت که نام فایل ثابتی تولید کند؟ یکی از موارد استفاده آن این است که باید فایل settings.py را اصلاح و این دو مدخل را اضافه کنید:
1
2
FEED_URI = 'data/%(name)s/%(time)s.json'
FEED_FORMAT = 'json'
در ادامه الگوی فایلی که ایجاد کردهایم را ارائه میکنیم. %(name)% نام خود خزنده است، time زمان را نشان میدهد. اکنون زمانی که دستور زیر را اجرا کنیم:
1
scrapy crawl --nolog electronics
و یا دستور زیر را اجرا کنیم:
1
scrapy crawl electronics
یک فایل JSON در پوشه data مانند زیر ایجاد میشود:
1
2
3
4
5
6
7
[
{"url": "https://www.olx.com.pk/item/acer-ultra-slim-gaming-laptop-with-amd-fx-processor-3gb-dedicated-IDUQ1k9.html", "price": "Rs 42,000", "title": "Acer Ultra Slim Gaming Laptop with AMD FX Processor 3GB Dedicated"},
{"url": "https://www.olx.com.pk/item/saw-machine-IDUYww5.html", "price": "Rs 80,000", "title": "Saw Machine"},
{"url": "https://www.olx.com.pk/item/laptop-hp-probook-6570b-core-i-5-3rd-gen-IDUYejF.html", "price": "Rs 22,000", "title": "Laptop HP Probook 6570b Core i 5 3rd Gen"},
{"url": "https://www.olx.com.pk/item/zong-4g-could-mifi-anlock-all-sim-supported-IDUYedh.html", "price": "Rs 4,000", "title": "Zong 4g could mifi anlock all Sim supported"},
...
]