Работа над ошибками

Как мы видели в простом примере, элементарный способ получить содержимое веб-страницы: requests.get(URL). Эта функция вернет объект requests.Response, чей атрибут text представляет собой содержимое страницы. ...Это в идеальном случае. Нет никакой гарантии, что сетевое соединение не оборвалось, страница не переехала, сервер не пребывает в коме и т.д.

Есть два типа нештатных ситуаций:

  1. Исключение (ошибка) в ходе запроса.
  2. Запрос выполнен, но получен не тот ответ, на который мы рассчитывали

Обработка исключений

Ошибки при вызове requests.get(URL) чаще всего возникают из-за проблем с сетью. Например, если при отключенном Интернете выполнить:

requests.get('http://google.com')
...
ConnectionError: ('Connection aborted.', gaierror(8, 'nodename nor servname provided, or not known'))

А если запросить неправильный адрес:

requests.get('foo')
...
MissingSchema: Invalid URL u'foo': No schema supplied. Perhaps you meant http://foo?

Все исключения, которые генерирует библиотека Requests, являются наследниками базового класса requests.exceptions.RequestException. Можно сделать для них общий обработчик.

import requests
from request.exceptions import RequestException

try:
    r = requests.get(URL)
    ...
except RequestException:
    pass # обработка ошибки

Есть два типа ошибок, на которые стоит обратить особое внимание при разработке краулера — TooManyRedirects и Timeout. Они могут служить признаками того, что сервер не может или не желает иметь с нами дело.

Проверка ответа

Свойство requests.Response.status_code возвращает HTTP-код ответа. Когда все в порядке он равен 200OK. Остальные коды не обязательно означают ошибку. Например, коды 300-307 говорят о перенаправлении (редиректах). Их Requests, как и браузеры, обрабатывает автоматически, если не настроить его иначе. Так что, нас интересует только код 200. Он означает, что сервер вернул нужный контент, с которым можно работать дальше.

try:
    r = requests.get(URL)
    if r.status_code == 200:
        txt = r.text
        pass # работаем с контентом
    else:
        pass # обработка ошибки
except RequestException:
    pass # обработка ошибки

Но этого недостаточно. Сервер может кривить душой: сообщить, что все OK (код 200), но вернуть пустой контент. В этом случае у объекта Response не будет атрибута text. Прийдется добавить еще одну проверку.

try:
    r = requests.get(URL)
    if r.status_code == 200:
        try:
            txt = r.text
        except AttributeError:
            pass # обработка ошибки
        else:
            pass # работаем с контентом
    else:
        pass # обработка ошибки
except RequestException:
    pass # обработка ошибки

Специализированные исключения

Чтобы каждый раз не повторять дюжину одних и тех же строк, код, представленный выше, стоит вынести в отдельную библиотеку. Работать с библиотечной функцией будет удобнее, если сбои любого типа будут приводить к исключениям. Поэтому имеет смысл объявить специализированные классы исключений.

class ScrapperError(Exception):
    '''Common prototype for scrapper exceptions.
    '''
    def __init__(self, url, resp):
        '''Constructor.

        Arguments:
        url : URL
        resp: requests.Response object
        '''
        Exception.__init__(self)
        self._url = url
        self._response = resp

    @property
    def url(self):
        return self._url

    @property
    def response(self):
        return self._response


class BadStatusError(ScrapperError):
    '''Exception for unexpected HTTP status code.
    '''
    def __str__(self):
        return 'Bad status: <%d> at url <%s>' % (self.response.status_code, self.url)


class EmptyContentError(ScrapperError):
    '''Exception raised when HTTP status code is OK, but there's no content.
    ''' 
    def __str__(self):
        return 'No content at url <%s>' % (self.url)

Теперь библиотечная функция может выглядеть так:

def get_request(url):
    r = session.get(url)
    if r.status_code == 200:
        try:
            txt = r.text
        except AttributeError:
            raise EmptyContentError(url, r)
        else:
            return r

    raise BadStatusError(url, r)

Исключения — как стандартные, так и наши, специализированные — обрабатываются "снаружи", например вот так:

try:
    get_request(URL)
except RequestException:
    pass # ошибка сети или протокола
except ScrapperError:
    pass # специализированная ошибка

social

Яндекс.Метрика