Пауки¶
Пауки — это классы, которые определяют, как будет сканироваться определенный сайт (или группа сайтов), в том числе как выполнять сканирование (т. е. переходить по ссылкам) и как извлекать структурированные данные со своих страниц (т. е. извлекать элементы). Другими словами, пауки — это место, где вы определяете настраиваемое поведение для сканирования и анализа страниц для определённого сайта (или, в некоторых случаях, группы сайтов).
У пауков цикл сканирования проходит примерно так:
Вы начинаете с создания начальных запросов для сканирования первых URL-адресов и указываете функцию обратного вызова, которая будет вызываться с ответом, загруженным из данных запросов.
Первые запросы для выполнения получаются путём вызова метода
start_requests()
, который (по умолчанию) генерируетRequest
для URL-адресов, указанных в методахstart_urls
иparse
в качестве функции обратного вызова для запросов.В функции обратного вызова вы анализируете ответ (веб-страницу) и возвращаете объекты элементов,
Request
или итерацию данных объектов. Данные запросы также будут содержать обратный вызов (возможно, такой же), а затем будут загружены Scrapy, а затем их ответ будет обработан указанным обратным вызовом.В функциях обратного вызова вы анализируете содержимое страницы, обычно используя Селекторы (но вы также можете использовать BeautifulSoup, lxml или любой другой механизм, который вам нравится) и генерируете элементы с проанализированными данными.
Наконец, элементы, возвращаемые пауком, обычно сохраняются в базе данных (в некоторых случаях Конвейер элементов) или записываются в файл с помощью Экспорта фидов.
Несмотря на то, что данный цикл применяется (более или менее) к любому виду пауков, существуют различные типы пауков по умолчанию, объединённые в Scrapy для разных целей. Мы поговорим об данных типах далее.
scrapy.Spider¶
- class scrapy.spiders.Spider¶
- class scrapy.Spider¶
Это простейший паук, от которого должен унаследоваться любой другой паук (включая пауков, поставляемых в комплекте с Scrapy, а также пауков, которых вы пишете сами). Никаких особых функций он не предоставляет. Он просто предоставляет реализацию по умолчанию
start_requests()
, которая отправляет запросы от атрибута паукаstart_urls
и вызывает метод паукаparse
для каждого из полученных ответов.- name¶
Строка, определяющая имя этого паука. Имя паука — это то, как паук находится (и создаётся) Scrapy, поэтому он должен быть уникальным. Однако ничто не мешает вам создать более одного экземпляра одного и того же паука. Это самый важный атрибут паука и он обязателен.
Если паук сканирует один домен, обычная практика состоит в том, чтобы назвать паука в честь домена, с TLD или без него. Так, например, паук, который ползает по
mywebsite.com
, часто будет называтьсяmywebsite
.
- allowed_domains¶
Необязательный список строк, содержащих домены, которые данный паук может сканировать. Запросы URL-адресов, не принадлежащих указанным в этом списке доменным именам (или их поддоменам), не будут выполняться, если включён
OffsiteMiddleware
.Допустим, ваш целевой URL-адрес —
https://www.example.com/1.html
, затем добавить в список'example.com'
.
- start_urls¶
Список URL-адресов, с которых паук начнёт сканирование, если не указаны конкретные URL-адреса. Итак, первыми загруженными страницами будут те, которые перечислены далее. Последующий
Request
будет генерироваться последовательно из данных, содержащихся в начальных URL.
- custom_settings¶
Словарь настроек, которые будут переопределены из конфигурации всего проекта при запуске этого паука. Он должен быть определён как атрибут класса, поскольку настройки обновляются перед созданием экземпляра.
Список доступных встроенных настроек см .: Справочник по встроенным настройкам.
- crawler¶
Данный атрибут устанавливается методом класса
from_crawler()
после инициализации класса и связывается с объектомCrawler
, к которому привязан данный экземпляр паука.Парсеры инкапсулируют в проект множество компонентов для единого доступа (например, расширения, промежуточное ПО, менеджеры сигналов и т. д.). См. Crawler API, чтобы узнать о них больше.
- settings¶
Конфигурация для запуска этого паука. Это экземпляр
Settings
, подробное введение по этому вопросу см. в теме Настройки.
- logger¶
Логгер Python, созданный с помощью Spider
name
. Вы можете использовать его для отправки сообщений журнала через него, как приведено на Журналирование от пауков.
- state¶
Словарь, который вы можете использовать для сохранения некоторого состояния паука между пакетами. См. Сохранение постоянного состояния между партиями, чтобы узнать об этом больше.
- from_crawler(crawler, *args, **kwargs)¶
Метод класса используется Scrapy для создания ваших пауков.
Вероятно, вам не потребуется переопределять это напрямую, потому что реализация по умолчанию действует как прокси для метода
__init__()
, вызывая его с заданными аргументамиargs
и именованными аргументамиkwargs
.Тем не менее, данный метод устанавливает атрибуты
crawler
иsettings
в новом экземпляре, чтобы к ним можно было получить доступ позже внутри кода паука.- Параметры
crawler (
Crawler
instance) – краулер, к которому будет привязан паукargs (list) – аргументы, переданные методу
__init__()
kwargs (dict) – ключевые аргументы, переданные методу
__init__()
- start_requests()¶
Данный метод должен возвращать итерацию с первыми запросами на сканирование для этого паука. Он вызывается Scrapy, когда паук открывается для сканирования. Scrapy вызывает его только один раз, поэтому можно безопасно реализовать
start_requests()
в качестве генератора.Реализация по умолчанию генерирует
Request(url, dont_filter=True)
для каждого URL-адреса вstart_urls
.Если вы хотите изменить запросы, используемые для запуска сканирования домена, данный метод следует переопределить. Например, если вам нужно начать с входа в систему с помощью запроса POST, вы можете это сделать:
class MySpider(scrapy.Spider): name = 'myspider' def start_requests(self): return [scrapy.FormRequest("http://www.example.com/login", formdata={'user': 'john', 'pass': 'secret'}, callback=self.logged_in)] def logged_in(self, response): # here you would extract links to follow and return Requests for # each of them, with another callback pass
- parse(response)¶
Это обратный вызов по умолчанию, используемый Scrapy для обработки загруженных ответов, когда в их запросах не указан обратный вызов.
Метод
parse
отвечает за обработку ответа и возврат извлеченных данных и/или дополнительных URL-адресов. Другие обратные вызовы запросов имеют те же требования, что и классSpider
.Данный метод, как и любой другой обратный вызов Request, должен возвращать итерацию
Request
и/или объекты элементов.- Параметры
response (
Response
) – ответ на парсинг
- log(message[, level, component])¶
Оболочка, которая отправляет сообщение журнала через
logger
Spider, сохраненная для обратной совместимости. Для получения дополнительной информации см. Журналирование от пауков.
- closed(reason)¶
Вызывается, когда паук закрывается. Данный метод обеспечивает ярлык для signal.connect() для сигнала
spider_closed
.
Посмотрим на пример:
import scrapy
class MySpider(scrapy.Spider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = [
'http://www.example.com/1.html',
'http://www.example.com/2.html',
'http://www.example.com/3.html',
]
def parse(self, response):
self.logger.info('A response from %s just arrived!', response.url)
Возврат нескольких запросов и элементов из одного обратного вызова:
import scrapy
class MySpider(scrapy.Spider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = [
'http://www.example.com/1.html',
'http://www.example.com/2.html',
'http://www.example.com/3.html',
]
def parse(self, response):
for h3 in response.xpath('//h3').getall():
yield {"title": h3}
for href in response.xpath('//a/@href').getall():
yield scrapy.Request(response.urljoin(href), self.parse)
Вместо start_urls
можно напрямую использовать start_requests()
; чтобы придать данным больше структуры, вы можете использовать объекты Item
:
import scrapy
from myproject.items import MyItem
class MySpider(scrapy.Spider):
name = 'example.com'
allowed_domains = ['example.com']
def start_requests(self):
yield scrapy.Request('http://www.example.com/1.html', self.parse)
yield scrapy.Request('http://www.example.com/2.html', self.parse)
yield scrapy.Request('http://www.example.com/3.html', self.parse)
def parse(self, response):
for h3 in response.xpath('//h3').getall():
yield MyItem(title=h3)
for href in response.xpath('//a/@href').getall():
yield scrapy.Request(response.urljoin(href), self.parse)
Аргументы паука¶
Пауки могут получать аргументы, изменяющие их поведение. Некоторые распространенные варианты использования аргументов паука — определение начальных URL-адресов или ограничение сканирования определенными разделами сайта, но их можно использовать для настройки любых функций паука.
Аргументы паука передаются через команду crawl
с использованием параметра -a
. Например:
scrapy crawl myspider -a category=electronics
Пауки могут получить доступ к аргументам в своих методах __init__:
import scrapy
class MySpider(scrapy.Spider):
name = 'myspider'
def __init__(self, category=None, *args, **kwargs):
super(MySpider, self).__init__(*args, **kwargs)
self.start_urls = [f'http://www.example.com/categories/{category}']
# ...
По умолчанию метод __init__ принимает любые аргументы паука и копирует их в паук как атрибуты. Приведённый выше пример также можно записать следующим образом:
import scrapy
class MySpider(scrapy.Spider):
name = 'myspider'
def start_requests(self):
yield scrapy.Request(f'http://www.example.com/categories/{self.category}')
Если вы запускаете Scrapy из скрипта, вы можете указать аргументы паука при вызове CrawlerProcess.crawl
или CrawlerRunner.crawl
:
process = CrawlerProcess()
process.crawl(MySpider, category="electronics")
Имейте в виду, что аргументы паука — это всего лишь строки. Паук не будет выполнять парсинг сам по себе. Если бы вам нужно было установить атрибут start_urls
из командной строки, вам пришлось бы самостоятельно проанализировать его в список, используя что-то вроде ast.literal_eval()
или json.loads()
, а затем установить его как атрибут. В противном случае вы вызовете итерацию строки start_urls
(очень распространенная ошибка Python), в результате чего каждый символ будет отображаться как отдельный URL-адрес.
Допустимый вариант использования — установить учётные данные http-аутентификации, используемые HttpAuthMiddleware
или пользовательским агентом, используемым UserAgentMiddleware
:
scrapy crawl myspider -a http_user=myuser -a http_pass=mypassword -a user_agent=mybot
Аргументы паука также можно передавать через API Scrapyd schedule.json
. См. документацию по Scrapyd.
Универсальные пауки¶
Scrapy поставляется с некоторыми полезными универсальными пауками, которые вы можете использовать для создания подклассов своих пауков. Их цель — предоставить удобные функции для нескольких распространенных случаев парсинга, таких как переход по всем ссылкам на сайте на основе определенных правил, сканирование с Sitemaps.xml или парсинг фида XML/CSV.
Для примеров, используемых в следующих пауках, мы предполагаем, что у вас есть проект с TestItem
, объявленным в модуле myproject.items
:
import scrapy
class TestItem(scrapy.Item):
id = scrapy.Field()
name = scrapy.Field()
description = scrapy.Field()
CrawlSpider¶
- class scrapy.spiders.CrawlSpider¶
Это наиболее часто используемый паук для сканирования обычных веб-сайтов, т. к. он обеспечивает удобный механизм перехода по ссылкам путём определения набора правил. Возможно, он не лучше всего подходит для вашего конкретного веб-сайта или проекта, но он достаточно универсален для нескольких случаев, поэтому вы можете начать с него и переопределить его по мере необходимости для получения дополнительных функций или просто реализовать свой собственный паук.
Помимо атрибутов, унаследованных от Spider (которые вы должны указать), данный класс поддерживает новый атрибут:
- rules¶
Это список из одного (или нескольких) объектов
Rule
. КаждыйRule
определяет определенное поведение при сканировании сайта. Объекты правил описаны ниже. Если несколько правил соответствуют одной и той же ссылке, будет использоваться первое в соответствии с порядком, в котором они определены в этом атрибуте.
Данный паук также предоставляет замещаемый метод:
Правила сканирования¶
Пример CrawlSpider¶
Давайте теперь посмотрим на пример CrawlSpider с правилами:
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
class MySpider(CrawlSpider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com']
rules = (
# Extract links matching 'category.php' (but not matching 'subsection.php')
# and follow links from them (since no callback means follow=True by default).
Rule(LinkExtractor(allow=('category\.php', ), deny=('subsection\.php', ))),
# Extract links matching 'item.php' and parse them with the spider's method parse_item
Rule(LinkExtractor(allow=('item\.php', )), callback='parse_item'),
)
def parse_item(self, response):
self.logger.info('Hi, this is an item page! %s', response.url)
item = scrapy.Item()
item['id'] = response.xpath('//td[@id="item_id"]/text()').re(r'ID: (\d+)')
item['name'] = response.xpath('//td[@id="item_name"]/text()').get()
item['description'] = response.xpath('//td[@id="item_description"]/text()').get()
item['link_text'] = response.meta['link_text']
url = response.xpath('//td[@id="additional_data"]/@href').get()
return response.follow(url, self.parse_additional_page, cb_kwargs=dict(item=item))
def parse_additional_page(self, response, item):
item['additional_data'] = response.xpath('//p[@id="additional_data"]/text()').get()
return item
Данный паук начнет сканировать домашнюю страницу example.com, собирая ссылки на категории и ссылки на элементы, анализируя последние с помощью метода parse_item
. Для каждого ответа элемента некоторые данные будут извлечены из HTML с помощью XPath, и они будут заполнены Item
.
XMLFeedSpider¶
- class scrapy.spiders.XMLFeedSpider¶
XMLFeedSpider разработан для синтаксического анализа XML-фидов путём их итерации по определенному имени узла. Итератор можно выбрать из:
iternodes
,xml
иhtml
. Рекомендуется использовать итераторiternodes
по соображениям производительности, поскольку итераторыxml
иhtml
генерируют сразу всю DOM для её анализа. Однако использованиеhtml
в качестве итератора может быть полезно при парсинге XML с плохой разметкой.Чтобы установить итератор и имя тега, необходимо определить следующие атрибуты класса:
- iterator¶
Строка, определяющая используемый итератор. Это может быть и то, и другое:
'iternodes'
— быстрый итератор на основе регулярных выражений'html'
— итератор, использующийSelector
. Имейте в виду, что при этом используется анализ DOM и необходимо загружать всю DOM в память, что может быть проблемой для больших фидов'xml'
— итератор, использующийSelector
. Имейте в виду, что при этом используется анализ DOM и необходимо загружать всю DOM в память, что может быть проблемой для больших фидов
По умолчанию:
'iternodes'
.
- itertag¶
Строка с именем узла (или элемента) для итерации. Пример:
itertag = 'product'
- namespaces¶
Список кортежей
(prefix, uri)
, которые определяют пространства имён, доступные в этом документе, которые будут обрабатываться этим пауком.prefix
иuri
будут использоваться для автоматической регистрации пространств имён с помощью методаregister_namespace()
.Затем вы можете указать узлы с пространствами имён в атрибуте
itertag
.Пример:
class YourSpider(XMLFeedSpider): namespaces = [('n', 'http://www.sitemaps.org/schemas/sitemap/0.9')] itertag = 'n:url' # ...
Помимо данных новых атрибутов, у этого паука также есть следующие переопределяемые методы:
- adapt_response(response)¶
Метод, который получает ответ, как только он поступает от промежуточного программного обеспечения паука, до того, как паук начнет его анализировать. Его можно использовать для изменения тела ответа перед его анализом. Данный метод получает ответ, а также возвращает ответ (он может быть таким же или другим).
- parse_node(response, selector)¶
Данный метод вызывается для узлов, соответствующих указанному имени тега (
itertag
). Получает ответ иSelector
для каждого узла. Переопределение этого метода обязательно. Иначе у вас, паук, ничего не получится. Данный метод должен возвращать объект элемента,Request
или итерацию, содержащую любой из них.
- process_results(response, results)¶
Данный метод вызывается для каждого результата (элемента или запроса), возвращаемого пауком, и он предназначен для выполнения любой последней обработки, необходимой перед возвратом результатов в ядро фреймворка, например, установка идентификаторов элементов. Он получает список результатов и ответ, который вызвал данные результаты. Он должен возвращать список результатов (элементов или запросов).
Предупреждение
Из-за его внутренней реализации вы должны явно устанавливать обратные вызовы для новых запросов при написании пауков на основе
XMLFeedSpider
; В противном случае может произойти неожиданное поведение.
Пример XMLFeedSpider¶
Данные пауки довольно просты в использовании, давайте рассмотрим один пример:
from scrapy.spiders import XMLFeedSpider
from myproject.items import TestItem
class MySpider(XMLFeedSpider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com/feed.xml']
iterator = 'iternodes' # This is actually unnecessary, since it's the default value
itertag = 'item'
def parse_node(self, response, node):
self.logger.info('Hi, this is a <%s> node!: %s', self.itertag, ''.join(node.getall()))
item = TestItem()
item['id'] = node.xpath('@id').get()
item['name'] = node.xpath('name').get()
item['description'] = node.xpath('description').get()
return item
По сути, мы создали паука, который загружает фид из заданного start_urls
, а затем выполняет итерацию по каждому из своих тегов item
, распечатывает их и сохраняет некоторые случайные данные в Item
.
CSVFeedSpider¶
- class scrapy.spiders.CSVFeedSpider¶
Данный паук очень похож на XMLFeedSpider, за исключением того, что он выполняет итерацию по строкам, а не по узлам. На каждой итерации вызывается метод
parse_row()
.- delimiter¶
Строка с символом-разделителем для каждого поля в файле CSV. По умолчанию используется
','
(запятая).
- quotechar¶
Строка с символом вложения для каждого поля в файле CSV. По умолчанию используется
'"'
(кавычки).
- headers¶
Список имён столбцов в файле CSV.
- parse_row(response, row)¶
Получает ответ и текст (представляющий каждую строку) с ключом для каждого предоставленного (или обнаруженного) заголовка CSV-файла. Данный паук также дает возможность переопределить методы
adapt_response
иprocess_results
для целей предварительной и постобработки.
Пример CSVFeedSpider¶
Давайте посмотрим на пример, аналогичный предыдущему, но с использованием CSVFeedSpider
:
from scrapy.spiders import CSVFeedSpider
from myproject.items import TestItem
class MySpider(CSVFeedSpider):
name = 'example.com'
allowed_domains = ['example.com']
start_urls = ['http://www.example.com/feed.csv']
delimiter = ';'
quotechar = "'"
headers = ['id', 'name', 'description']
def parse_row(self, response, row):
self.logger.info('Hi, this is a row!: %r', row)
item = TestItem()
item['id'] = row['id']
item['name'] = row['name']
item['description'] = row['description']
return item
SitemapSpider¶
- class scrapy.spiders.SitemapSpider¶
SitemapSpider позволяет сканировать сайт, обнаруживая URL-адреса с помощью Sitemaps.xml.
Он поддерживает вложенные карты сайта и обнаружение URL-адресов карты сайта из robots.txt.
- sitemap_urls¶
Список URL-адресов, указывающих на карты сайта, URL-адреса которых вы хотите сканировать.
Вы также можете указать на robots.txt, и он будет проанализирован для извлечения из него URL-адресов карты сайта.
- sitemap_rules¶
Список кортежей
(regex, callback)
, где:regex
— это регулярное выражение для соответствия URL-адресам, извлеченным из карт сайта.regex
может быть либо строкой, либо скомпилированным объектом регулярного выражения.обратный вызов — это обратный вызов, который используется для обработки URL-адресов, соответствующих регулярному выражению.
callback
может быть строкой (указывающей имя метода паука) или вызываемым.
Например:
sitemap_rules = [('/product/', 'parse_product')]
Правила применяются по порядку, и будет использоваться только первое совпадение.
Если вы опустите данный атрибут, все URL-адреса, найденные в файлах Sitemap, будут обработаны обратным вызовом
parse
.
- sitemap_follow¶
Список регулярных выражений карты сайта, которым необходимо следовать. Это только для сайтов, использующих Sitemap индексные файлы, которые указывают на другие файлы Sitemap.xml.
По умолчанию отслеживаются все карты сайта.
- sitemap_alternate_links¶
Указывает, следует ли использовать альтернативные ссылки для одного
url
. Это ссылки на тот же веб-сайт на другом языке, переданные в том же блокеurl
.Например:
<url> <loc>http://example.com/</loc> <xhtml:link rel="alternate" hreflang="de" href="http://example.com/de"/> </url>
Если задано значение
sitemap_alternate_links
, будут извлечены оба URL-адреса. Еслиsitemap_alternate_links
отключён, будет извлечен толькоhttp://example.com/
.По умолчанию
sitemap_alternate_links
отключён.
- sitemap_filter(entries)¶
Это функция фильтра, которую можно переопределить для выбора записей карты сайта на основе их атрибутов.
Например:
<url> <loc>http://example.com/</loc> <lastmod>2005-01-01</lastmod> </url>
Мы можем определить функцию
sitemap_filter
для фильтрацииentries
по дате:from datetime import datetime from scrapy.spiders import SitemapSpider class FilteredSitemapSpider(SitemapSpider): name = 'filtered_sitemap_spider' allowed_domains = ['example.com'] sitemap_urls = ['http://example.com/sitemap.xml'] def sitemap_filter(self, entries): for entry in entries: date_time = datetime.strptime(entry['lastmod'], '%Y-%m-%d') if date_time.year >= 2005: yield entry
Это позволит получить только
entries
, измененные в 2005 году и в последующие годы.Записи — это объекты dict, извлеченные из документа карты сайта. Обычно ключ — это имя тега, а значение — текст внутри него.
Это важно заметить:
поскольку атрибут loc является обязательным, записи без этого тега отбрасываются
альтернативные ссылки хранятся в списке с ключом
alternate
(см.sitemap_alternate_links
)пространства имён удаляются, поэтому теги lxml с именем
{namespace}tagname
становятся толькоtagname
Если вы опустите данный метод, все записи, найденные в файлах Sitemap, будут обработаны с учётом других атрибутов и их настроек.
Примеры SitemapSpider¶
Самый простой пример: обработать все URL-адреса, обнаруженные через карты сайта, с помощью обратного вызова parse
:
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/sitemap.xml']
def parse(self, response):
pass # ... scrape item here ...
Обработать некоторые URL-адреса с определенным обратным вызовом и другие URL-адреса с другим обратным вызовом:
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/sitemap.xml']
sitemap_rules = [
('/product/', 'parse_product'),
('/category/', 'parse_category'),
]
def parse_product(self, response):
pass # ... scrape product ...
def parse_category(self, response):
pass # ... scrape category ...
Следуйте картам сайта, определенным в файле robots.txt, и следовать только за картами сайта, URL-адрес которых содержит /sitemap_shop
:
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/robots.txt']
sitemap_rules = [
('/shop/', 'parse_shop'),
]
sitemap_follow = ['/sitemap_shops']
def parse_shop(self, response):
pass # ... scrape shop here ...
Совместить SitemapSpider с другими источниками URL-адресов:
from scrapy.spiders import SitemapSpider
class MySpider(SitemapSpider):
sitemap_urls = ['http://www.example.com/robots.txt']
sitemap_rules = [
('/shop/', 'parse_shop'),
]
other_urls = ['http://www.example.com/about']
def start_requests(self):
requests = list(super(MySpider, self).start_requests())
requests += [scrapy.Request(x, self.parse_other) for x in self.other_urls]
return requests
def parse_shop(self, response):
pass # ... scrape shop here ...
def parse_other(self, response):
pass # ... scrape other here ...