Запросы и ответы

Scrapy использует объекты Request и Response для сканирования веб-сайтов.

Обычно объекты Request генерируются в пауках и проходят через систему, пока не достигнут загрузчика, который выполняет запрос и возвращает объект Response, который возвращается к пауку, отправившему запрос.

Оба класса Request и Response имеют подклассы, которые добавляют функциональность, не требуемую в базовых классах. Они описаны ниже в Запросить подклассы и Подклассы Response.

Объекты запроса

Передача дополнительных данных в функции обратного вызова

Обратный вызов запроса — это функция, которая будет вызываться при загрузке ответа на данный запрос. Функция обратного вызова будет вызываться с загруженным объектом Response в качестве первого аргумента.

Пример:

def parse_page1(self, response):
    return scrapy.Request("http://www.example.com/some_page.html",
                          callback=self.parse_page2)

def parse_page2(self, response):
    # this would log http://www.example.com/some_page.html
    self.logger.info("Visited %s", response.url)

В некоторых случаях вам может быть интересно передать аргументы этим функциям обратного вызова, чтобы вы могли получить аргументы позже, во втором обратном вызове. В следующем примере показано, как этого добиться с помощью атрибута Request.cb_kwargs:

def parse(self, response):
    request = scrapy.Request('http://www.example.com/index.html',
                             callback=self.parse_page2,
                             cb_kwargs=dict(main_url=response.url))
    request.cb_kwargs['foo'] = 'bar'  # add more arguments for the callback
    yield request

def parse_page2(self, response, main_url, foo):
    yield dict(
        main_url=main_url,
        other_url=response.url,
        foo=foo,
    )

Осторожно

Request.cb_kwargs был представлен в версии 1.7. До этого для передачи информации по обратным вызовам рекомендовалось использовать Request.meta. После 1.7 предпочтительным способом обработки пользовательской информации стал Request.cb_kwargs, оставив Request.meta для связи с такими компонентами, как промежуточное ПО и расширения.

Использование errbacks для перехвата исключений при обработке запроса

Ошибка запроса — это функция, которая будет вызываться при возникновении исключения при его обработке.

Он получает Failure в качестве первого параметра и может использоваться для отслеживания тайм-аутов установления соединения, ошибок DNS и т. д.

Вот пример паука, который регистрирует все ошибки и при необходимости улавливает некоторые конкретные ошибки:

import scrapy

from scrapy.spidermiddlewares.httperror import HttpError
from twisted.internet.error import DNSLookupError
from twisted.internet.error import TimeoutError, TCPTimedOutError

class ErrbackSpider(scrapy.Spider):
    name = "errback_example"
    start_urls = [
        "http://www.httpbin.org/",              # HTTP 200 expected
        "http://www.httpbin.org/status/404",    # Not found error
        "http://www.httpbin.org/status/500",    # server issue
        "http://www.httpbin.org:12345/",        # non-responding host, timeout expected
        "https://example.invalid/",             # DNS error expected
    ]

    def start_requests(self):
        for u in self.start_urls:
            yield scrapy.Request(u, callback=self.parse_httpbin,
                                    errback=self.errback_httpbin,
                                    dont_filter=True)

    def parse_httpbin(self, response):
        self.logger.info('Got successful response from {}'.format(response.url))
        # do something useful here...

    def errback_httpbin(self, failure):
        # log all failures
        self.logger.error(repr(failure))

        # in case you want to do something special for some errors,
        # you may need the failure's type:

        if failure.check(HttpError):
            # these exceptions come from HttpError spider middleware
            # you can get the non-200 response
            response = failure.value.response
            self.logger.error('HttpError on %s', response.url)

        elif failure.check(DNSLookupError):
            # this is the original request
            request = failure.request
            self.logger.error('DNSLookupError on %s', request.url)

        elif failure.check(TimeoutError, TCPTimedOutError):
            request = failure.request
            self.logger.error('TimeoutError on %s', request.url)

Доступ к дополнительным данным в функциях errback

В случае сбоя обработки запроса вас может заинтересовать доступ к аргументам функций обратного вызова, чтобы вы могли продолжить обработку на основе аргументов в errback. В следующем примере показано, как этого добиться с помощью Failure.request.cb_kwargs:

def parse(self, response):
    request = scrapy.Request('http://www.example.com/index.html',
                             callback=self.parse_page2,
                             errback=self.errback_page2,
                             cb_kwargs=dict(main_url=response.url))
    yield request

def parse_page2(self, response, main_url):
    pass

def errback_page2(self, failure):
    yield dict(
        main_url=failure.request.cb_kwargs['main_url'],
    )

Специальные ключи Request.meta

Атрибут Request.meta может содержать любые произвольные данные, но есть некоторые специальные ключи, распознаваемые Scrapy и его встроенными расширениями.

Далее их перечисление:

bindaddress

IP-адрес исходящего IP-адреса, который будет использоваться для выполнения запроса.

download_timeout

Время (в секундах), в течение которого загрузчик будет ждать до истечения времени ожидания. См. также: DOWNLOAD_TIMEOUT.

download_latency

Время, затраченное на получение ответа с момента запуска запроса, т. е. HTTP-сообщения, отправленного по сети. Данный мета-ключ становится доступным только после загрузки ответа. Хотя большинство других мета-ключей используются для управления поведением Scrapy, предполагается, что данный ключ предназначен только для чтения.

download_fail_on_dataloss

Независимо от того, терпеть ли неудачу из-за неработающих ответов. Смотрите: DOWNLOAD_FAIL_ON_DATALOSS.

max_retry_times

Мета-ключ используется для установки времени повтора для каждого запроса. При инициализации мета-ключ max_retry_times имеет более высокий приоритет над параметром RETRY_TIMES.

Остановка загрузки ответа

Вызов исключения StopDownload из обработчика сигналов bytes_received или headers_received остановит загрузку данного ответа. См. следующий пример:

import scrapy


class StopSpider(scrapy.Spider):
    name = "stop"
    start_urls = ["https://docs.scrapy.org/en/latest/"]

    @classmethod
    def from_crawler(cls, crawler):
        spider = super().from_crawler(crawler)
        crawler.signals.connect(spider.on_bytes_received, signal=scrapy.signals.bytes_received)
        return spider

    def parse(self, response):
        # 'last_chars' show that the full response was not downloaded
        yield {"len": len(response.text), "last_chars": response.text[-40:]}

    def on_bytes_received(self, data, request, spider):
        raise scrapy.exceptions.StopDownload(fail=False)

что даёт следующий результат:

2020-05-19 17:26:12 [scrapy.core.engine] INFO: Spider opened
2020-05-19 17:26:12 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2020-05-19 17:26:13 [scrapy.core.downloader.handlers.http11] DEBUG: Download stopped for <GET https://docs.scrapy.org/en/latest/> from signal handler StopSpider.on_bytes_received
2020-05-19 17:26:13 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://docs.scrapy.org/en/latest/> (referer: None) ['download_stopped']
2020-05-19 17:26:13 [scrapy.core.scraper] DEBUG: Scraped from <200 https://docs.scrapy.org/en/latest/>
{'len': 279, 'last_chars': 'dth, initial-scale=1.0">\n  \n  <title>Scr'}
2020-05-19 17:26:13 [scrapy.core.engine] INFO: Closing spider (finished)

По умолчанию результирующие ответы обрабатываются соответствующими ошибками. Чтобы вместо этого вызвать их обратный вызов, как в этом примере, передать fail=False в исключение StopDownload.

Запросить подклассы

Вот список встроенных подклассов Request. Вы также можете создать подкласс для реализации своих собственных функций.

Объекты FormRequest

Класс FormRequest расширяет базовый Request функциями для работы с формами HTML. Он использует формы lxml.html для предварительного заполнения полей формы данными из объектов Response.

class scrapy.http.request.form.FormRequest
class scrapy.http.FormRequest
class scrapy.FormRequest(url[, formdata, ...])

Класс FormRequest добавляет новый ключевой параметр к методу __init__. Остальные аргументы такие же, как для класса Request, и здесь не описаны.

Параметры

formdata (dict or collections.abc.Iterable) – is a dictionary (or iterable of (key, value) tuples) containing HTML Form data which will be url-encoded and assigned to the body of the request.

Объекты FormRequest поддерживают следующий метод класса в дополнение к стандартным методам Request:

classmethod FormRequest.from_response(response[, formname=None, formid=None, formnumber=0, formdata=None, formxpath=None, formcss=None, clickdata=None, dont_click=False, ...])

Возвращает новый объект FormRequest со значениями поля формы, предварительно заполненными значениями, найденными в элементе HTML <form>, содержащемся в данном ответе. Для примера см. Использование FormRequest.from_response() для имитации входа пользователя в систему.

Политика заключается в том, чтобы по умолчанию автоматически имитировать щелчок на любом элементе управления формы, который выглядит интерактивным, например <input type="submit">. Несмотря на то, что это довольно удобно и часто является желаемым поведением, иногда это может вызвать проблемы, которые может быть трудно отладить. Например, при работе с формами, которые заполняются и/или отправляются с использованием javascript, поведение from_response() по умолчанию может быть не самым подходящим. Чтобы отключить это поведение, вы можете установить для аргумента dont_click значение True. Кроме того, если вы хотите изменить выбранный элемент управления (вместо его отключения), вы также можете использовать аргумент clickdata.

Осторожно

Использование этого метода с элементами select, у которых есть начальные или конечные пробелы в значениях параметров, не будет работать из-за ошибки в lxml, который должен быть исправлен в lxml 3.8 и выше.

Параметры
  • response (Response object) – ответ, содержащий форму HTML, которая будет использоваться для предварительного заполнения полей формы

  • formname (str) – если задано, будет использоваться форма с атрибутом name, установленным на это значение.

  • formid (str) – если задано, будет использоваться форма с атрибутом id, установленным на это значение.

  • formxpath (str) – если задано, будет использоваться первая форма, соответствующая xpath.

  • formcss (str) – если задано, будет использоваться первая форма, соответствующая селектору css.

  • formnumber (int) – номер формы для использования, если ответ содержит несколько форм. Первый (также по умолчанию) — 0.

  • formdata (dict) – поля для переопределения в данных формы. Если поле уже присутствовало в элементе ответа <form>, его значение заменяется значением, переданным в этом параметре. Если в данный параметр передано значение None, поле не будет включено в запрос, даже если оно присутствовало в элементе ответа <form>.

  • clickdata (dict) – атрибуты для поиска нажатого элемента управления. Если он не указан, данные формы будут отправлены, имитируя щелчок по первому интерактивному элементу. Помимо атрибутов html, элемент управления может быть идентифицирован по его индексу, отсчитываемому от нуля, относительно других вводимых данных внутри формы с помощью атрибута nr.

  • dont_click (bool) – Если True, данные формы будут отправлены без нажатия на какой-либо элемент.

Остальные параметры этого метода класса передаются непосредственно методу FormRequest __init__.

Примеры использования Request

Использование FormRequest для отправки данных через HTTP POST

Если вы хотите смоделировать POST HTML-формы в своём пауке и отправить пару полей «ключ-значение», вы можете возвращает объект FormRequest (от вашего паука) следующим образом:

return [FormRequest(url="http://www.example.com/post/action",
                    formdata={'name': 'John Doe', 'age': '27'},
                    callback=self.after_post)]

Использование FormRequest.from_response() для имитации входа пользователя в систему

Обычно веб-сайты предоставляют предварительно заполненные поля формы с помощью элементов <input type="hidden">, таких как данные, относящиеся к сеансу, или токены аутентификации (для страниц входа). При парсинге вы хотите, чтобы данные поля автоматически заполнялись заранее и переопределяли только некоторые из них, например имя пользователя и пароль. Для этого задания можно использовать метод FormRequest.from_response(). Вот пример паука, который его использует:

import scrapy

def authentication_failed(response):
    # TODO: Check the contents of the response and return True if it failed
    # or False if it succeeded.
    pass

class LoginSpider(scrapy.Spider):
    name = 'example.com'
    start_urls = ['http://www.example.com/users/login.php']

    def parse(self, response):
        return scrapy.FormRequest.from_response(
            response,
            formdata={'username': 'john', 'password': 'secret'},
            callback=self.after_login
        )

    def after_login(self, response):
        if authentication_failed(response):
            self.logger.error("Login failed")
            return

        # continue scraping with authenticated session...

JsonRequest

Класс JsonRequest расширяет базовый класс Request функциями для работы с запросами JSON.

class scrapy.http.JsonRequest(url[, ... data, dumps_kwargs])

Класс JsonRequest добавляет два новых ключевых параметров к методу __init__. Остальные аргументы такие же, как для класса Request, и здесь не описаны.

Использование JsonRequest установит заголовок Content-Type на application/json и заголовок Accept на application/json, text/javascript, */*; q=0.01

Параметры
  • data (object) – is any JSON serializable object that needs to be JSON encoded and assigned to body. if Request.body argument is provided this parameter will be ignored. if Request.body argument is not provided and data argument is provided Request.method will be set to 'POST' automatically.

  • dumps_kwargs (dict) – Параметры, которые будут переданы базовому методу json.dumps(), который используется для сериализации данных в формат JSON.

Пример использования JsonRequest

Отправка запроса JSON POST с полезными данными JSON:

data = {
    'name1': 'value1',
    'name2': 'value2',
}
yield JsonRequest(url='http://www.example.com/post/action', data=data)

Объекты Response

Подклассы Response

Вот список доступных встроенных подклассов Response. Вы также можете создать подкласс класса Response, чтобы реализовать свои собственные функции.

Объекты TextResponse

class scrapy.http.TextResponse(url[, encoding[, ...]])

Объекты TextResponse добавляют возможности кодирования к базовому классу Response, который предназначен для использования только для двоичных данных, таких как изображения, звуки или любой мультимедийный файл.

Объекты TextResponse поддерживают новый аргумент метода __init__ в дополнение к базовым объектам Response. Остальные функции такие же, как у класса Response, и здесь не описаны.

Параметры

encoding (str) – is a string which contains the encoding to use for this response. If you create a TextResponse object with a string as body, it will be converted to bytes encoded using this encoding. If encoding is None (default), the encoding will be looked up in the response headers and body instead.

Объекты TextResponse поддерживают следующие атрибуты в дополнение к стандартным Response:

text

Тело ответа в виде строки.

То же, что и response.body.decode(response.encoding), но результат кэшируется после первого вызова, поэтому вы можете обращаться к response.text несколько раз без дополнительных затрат.

Примечание

str(response.body) — неправильный способ преобразовать тело ответа в строку:

>>> str(b'body')
"b'body'"
encoding

Строка с кодировкой этого ответа. Кодирование разрешается путём использования следующих механизмов по порядку:

  1. кодировка, переданная в аргументе encoding метода __init__

  2. кодировка, объявленная в HTTP-заголовке Content-Type. Если эта кодировка недействительна (т.е. неизвестна), она игнорируется и пробуется следующий механизм разрешения.

  3. кодировка, объявленная в теле ответа. Класс TextResponse не предоставляет для этого никаких специальных функций. Однако классы HtmlResponse и XmlResponse работают.

  4. кодировка, полученная при просмотре тела ответа. Это более хрупкий метод, но и последний из применявшихся.

selector

Экземпляр Selector, использующий ответ в качестве цели. Селектор лениво создаётся при первом доступе.

Объекты TextResponse поддерживают следующие методы в дополнение к стандартным Response:

xpath(query)

Ярлык на TextResponse.selector.xpath(query):

response.xpath('//p')
css(query)

Ярлык на TextResponse.selector.css(query):

response.css('p')

Объекты HtmlResponse

class scrapy.http.HtmlResponse(url[, ...])

Класс HtmlResponse является подклассом TextResponse, который добавляет поддержку автоматического обнаружения кодировки путём просмотра атрибута HTML meta http-equiv. См. TextResponse.encoding.

Объекты XmlResponse

class scrapy.http.XmlResponse(url[, ...])

Класс XmlResponse является подклассом TextResponse, который добавляет поддержку автоматического обнаружения кодировки путём просмотра строки объявления XML. См. TextResponse.encoding.