Последния път внедрихме основен уеб скрепер, който изтегли най-новите въпроси от StackOverflow и съхрани резултатите в MongoDB. В тази статия ще разширим нашия скрепер, така че да обхожда връзките за пагинация в долната част на всяка страница и да изтрива въпросите (заглавие на въпроса и URL адрес) от всяка страница.
Безплатен бонус: Щракнете тук, за да изтеглите скелет на проекта Python + MongoDB с пълен изходен код, който ви показва как да получите достъп до MongoDB от Python.
Актуализации:
- 09.06.2015 г. – Актуализирано до най-новата версия на Scrapy (v1.0.3) и PyMongo (v3.0.3) – наздраве!
Преди да започнете каквато и да е работа по изстъргване, прегледайте правилата за използване на сайта и спазвайте файла robots.txt. Освен това се придържайте към етичните практики за остъргване, като не наводнявате сайт с многобройни заявки за кратък период от време. Отнасяйте се към всеки сайт, който изстъргвате, сякаш е ваш собствен.
Това е част от сътрудничеството между хората от Real Python и György - ентусиаст на Python и разработчик на софтуер, който в момента работи в компания за големи данни и в същото време търси нова работа. Можете да му задавате въпроси в twitter - @kissgyorgy.
Първи стъпки
Има два възможни начина да продължим оттам, откъдето спряхме.
Първият е да разширим съществуващия ни Spider, като извлечем всяка следваща връзка към страница от отговора в parse_item
метод с израз на xpath и просто yield
a Request
обект с обратно извикване към същия parse_item
метод. По този начин scrapy автоматично ще направи нова заявка към посочената от нас връзка. Можете да намерите повече информация за този метод в документацията на Scrapy.
Другият, много по-прост вариант е да използвате различен тип паяк - CrawlSpider
(връзка). Това е разширена версия на основния Spider
, проектиран точно за нашия случай на употреба.
CawlSpider
Ще използваме същия проект Scrapy от последния урок, така че вземете кода от репо, ако имате нужда от него.
Създайте шаблона
В директорията „stack“ започнете с генериране на шаблона на паяка от crawl
шаблон:
$ scrapy genspider stack_crawler stackoverflow.com -t crawl
Created spider 'stack_crawler' using template 'crawl' in module:
stack.spiders.stack_crawler
Проектът Scrapy сега трябва да изглежда така:
├── scrapy.cfg
└── stack
├── __init__.py
├── items.py
├── pipelines.py
├── settings.py
└── spiders
├── __init__.py
├── stack_crawler.py
└── stack_spider.py
И stack_crawler.py файл трябва да изглежда така:
# -*- coding: utf-8 -*-
import scrapy
from scrapy.contrib.linkextractors import LinkExtractor
from scrapy.contrib.spiders import CrawlSpider, Rule
from stack.items import StackItem
class StackCrawlerSpider(CrawlSpider):
name = 'stack_crawler'
allowed_domains = ['stackoverflow.com']
start_urls = ['http://www.stackoverflow.com/']
rules = (
Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)
def parse_item(self, response):
i = StackItem()
#i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract()
#i['name'] = response.xpath('//div[@id="name"]').extract()
#i['description'] = response.xpath('//div[@id="description"]').extract()
return i
Просто трябва да направим няколко актуализации на този шаблон...
Актуализирайте start_urls
списък
Първо добавете първата страница с въпроси към start_urls
списък:
start_urls = [
'http://stackoverflow.com/questions?pagesize=50&sort=newest'
]
Актуализирайте rules
списък
След това трябва да кажем на паяка къде може да намери връзките към следващата страница, като добавим регулярен израз към rules
атрибут:
rules = [
Rule(LinkExtractor(allow=r'questions\?page=[0-9]&sort=newest'),
callback='parse_item', follow=True)
]
Scrapy вече автоматично ще изисква нови страници въз основа на тези връзки и ще предава отговора на parse_item
метод за извличане на въпросите и заглавията.
Ако обръщате голямо внимание, този регулярен израз ограничава обхождането до първите 9 страници, тъй като за тази демонстрация не искаме да изстъргваме всички 176 234 страници!
Актуализирайте parse_item
метод
Сега просто трябва да напишем как да анализираме страниците с xpath, което вече направихме в последния урок - така че просто го копирайте:
def parse_item(self, response):
questions = response.xpath('//div[@class="summary"]/h3')
for question in questions:
item = StackItem()
item['url'] = question.xpath(
'a[@class="question-hyperlink"]/@href').extract()[0]
item['title'] = question.xpath(
'a[@class="question-hyperlink"]/text()').extract()[0]
yield item
Това е всичко за паяка, но не започнете още.
Добавяне на забавяне на изтегляне
Трябва да бъдем добри към StackOverflow (и всеки сайт, в този смисъл), като зададем забавяне на изтеглянето в settings.py :
DOWNLOAD_DELAY = 5
Това казва на Scrapy да изчака поне 5 секунди между всяка нова заявка, която прави. Вие по същество ограничавате себе си. Ако не направите това, StackOverflow ще ви ограничи скоростта; и ако продължите да изстъргвате сайта, без да налагате ограничение на скоростта, вашият IP адрес може да бъде забранен. Така че, бъдете мили – Отнасяйте се към всеки сайт, който изстържете, сякаш е ваш собствен.
Сега остава само едно нещо - да съхранявате данните.
MongoDB
Последния път изтеглихме само 50 въпроса, но тъй като този път грабваме много повече данни, искаме да избегнем добавянето на дублиращи се въпроси към базата данни. Можем да направим това, като използваме MongoDB upsert, което означава, че актуализираме заглавието на въпроса, ако то вече е в базата данни, и вмъкваме по друг начин.
Променете MongoDBPipeline
дефинирахме по-рано:
class MongoDBPipeline(object):
def __init__(self):
connection = pymongo.MongoClient(
settings['MONGODB_SERVER'],
settings['MONGODB_PORT']
)
db = connection[settings['MONGODB_DB']]
self.collection = db[settings['MONGODB_COLLECTION']]
def process_item(self, item, spider):
for data in item:
if not data:
raise DropItem("Missing data!")
self.collection.update({'url': item['url']}, dict(item), upsert=True)
log.msg("Question added to MongoDB database!",
level=log.DEBUG, spider=spider)
return item
За простота не оптимизирахме заявката и не се занимавахме с индекси, тъй като това не е производствена среда.
Тест
Стартирайте паяка!
$ scrapy crawl stack_crawler
Сега седнете и гледайте как вашата база данни се пълни с данни!
$ mongo
MongoDB shell version: 3.0.4
> use stackoverflow
switched to db stackoverflow
> db.questions.count()
447
>
Заключение
Можете да изтеглите целия изходен код от хранилището на Github. Коментирайте по-долу с въпроси. Наздраве!
Безплатен бонус: Щракнете тук, за да изтеглите скелет на проекта Python + MongoDB с пълен изходен код, който ви показва как да получите достъп до MongoDB от Python.
Търсите още уеб изстъргване? Не забравяйте да разгледате курсовете по Real Python. Търсите да наемете професионален уеб скрепер? Вижте GoScrape.