Commits

Gregory Petukhov  committed ae048ec

Update spider documentation

  • Participants
  • Parent commits 8565b7d

Comments (0)

Files changed (6)

File docs/index.rst

     :maxdepth: 2
 
     spider/tutorial
+    spider/task_building
     spider/task
+    spider/task_queue
+    spider/error_handling
+    spider/cache
 
 TODO::
 
-    * Объект задания
-    * Система кэширования в mongodb
-    * Функция повторного выполнения заданий
+    * Работа с прокси
     * Утилиты:
      * process_links
      * process_next_page
      * inc_count/add_item/save_list/render_stats/save_all_lists
      * process_object_image
-    * Работа с прокси
-    * Обработка сетевых ошибок и исключений
     
 
 

File docs/spider/cache.rst

+.. _spider_cache:
+
+====================================
+Система кэширования сетевых запросов
+====================================
+
+В целях ускорения тестирования паука в процессе разработки, а также ускорения повторного парсинга данных, была разработана система кэширования. В данный момент есть ограничение - только GET запросы могут быть закешированными. Важно понимать что кэш в Spider это не полноценный http-прокси-сервер это лишь средство для отладки. Хотя стоит заметить, что даже в таком примитвной реализации система кэширования в большинстве случаев позволяет успешно использовать закэшированные данные для повторного парсинга в случае изменения логики обработки данных.
+
+Бэкенды системы кэширования
+---------------------------
+
+Кэш в Spider разработан в виде отдельньго слоя для того, чтобы можно было подключать различные базы данных. В данный момент доступна только одна реализация кэша - хранение данных в mongodb
+
+Исползование кэша
+-----------------
+
+Для того, чтобы паук мог искать запрашиваемые документы в кэше и сохранять в кэш, полученные данные, нужно вызывать метод `setup_cache` до начала работы паука::
+
+    bot = ExampleSpider()
+    bot.setup_cache(database='some-database')
+    bot.run()
+
+Вышенаписанный код активирует кэш, документы будут искаться и сохраняться в mongodb базе данных с именем 'some-datbase'. Имя коллекции с документами: "cache".
+
+Есть несколько настроек для регулирования работы кэша:
+
+:backend: бэкенд кэша, сейчас ничего кроме "mongo" не работает
+:database: имя mongodb базы данных
+:use_compression: использование gzip для сжатия данных, перед помещением их в кэш.
+
+Сжатие кэшируемых данных
+------------------------
+
+По-умолчанию, сжатие включено. Сжатие позволяет на порядок уменьшить размер места в базе данных, необходимого для хранения закешированных документов. Сжатие снижает скорость работы паука, но не намного.

File docs/spider/error_handling.rst

+.. _spider_error_handling:
+
+================
+Обработка ошибок
+================
+
+Правила обработки запросов
+--------------------------
+
+* Если запрос выполнен успешно, то вызывается метод-обработчик связанный с заданием по его имени.
+* Если запрос был нарушен из-за сетевой ошибки, то задание снова отправляется в очередь заданий.
+* Если произошла непредвиденная ошибка в обработчике задания, то обработка задания прекращается немедленно. Ошибка в обработчике не фатальна: она не влияет на работу обработчиков других заданий.
+
+Сетевые ошибки
+--------------
+
+Сетевой ошибокй считаются следующие случаи:
+* произошла ошибка во время передачи данных, например, сервер отверг запрос на соеденине или оборвал связь до того, как данные были переданы или данные передавались слишком долго
+* данные были переданы, но HTTP-статус ответа отличен от 2xx или 404
+
+Cуществует ряд настроек, для задания критериев, по которым сетеовой запрос помечается как ошибочный.
+
+Вы можете управлять таймаутами: :ref:`option_timeout` и :ref:`option_connect_timeout`. Для задания этих настроек вам нужно конструировать Task-обеъкт с помощью настроенного Grab-объекта::
+    
+    g = Grab(timeout=5, connect_timeout=1, url='http://example.com')
+    t = Task('example', grab=g)
+
+Вы можете указывать дополнительный список HTTP-статусов, которые будут считаться успешными::
+
+    t = Task('example', url='http://example.com', valid_status=(500, 501, 502))
+
+
+Повторно выполнение заданий
+---------------------------
+
+Задание завершившееся сетевой ошибкой повторно отправляется в очередь заданий. Количество попыток зависит от аттрибута `Spider.network_try_limit` и равно десяти, по-умолчанию. Номер попытки хранится в `Task.network_try_count` аттрибуте задания. Если все попытки исчерпаны, то задание больше не добавляетя в очередь. Кроме того, если в пуке определён метод `task_<имя задания>_fallback`, то он вызывается и получает в качестве единственного аргумента Task-объект, невыполненного задания.
+
+Также бывает, что хотя HTTP статус не содержит ошибки, но данные ответа являются неверными, например, когда отправленная форма отображается ещё раз из-за неверно заполненного поля или когда сайт показывает каптчу или сообщение о том, что ваш IP забанен. В таких случаях нужно вручную (из метода-обработчика) отправить это задание ещё раз в очередь. Дабы избежать бесконечного добавления такого задания в очередь существует ещё один счётчик: `Task.task_try_count` и соответсвующее ему ограничение в пауке `Spider.task_try_limit`. Важное замечание, в случае использования `task_try_count` вы должны самостоятельно увеличивать его значение при повторной отправке задания в очередь::
+
+    def task_google(self, grab, task):
+        if captcha_found(grab):
+            yield Task('google', url=grab.config['url'], task_try_count=task.task_try_count + 1)
+
+    def task_google_fallback(self, task):
+        print 'Google is not happy with you IP address'
+
+
+Статистика ошибок
+-----------------
+
+После завершения работы паука или даже во время его работы, вы можете получить суммарную информацию о количестве сделанных запросов и количестве разилчных ошибок с помощью метода `Spider.render_stats`.

File docs/spider/task.rst

 .. _spider_task:
 
-Задания и способы их создания
-=============================
+=======
+Задания
+=======
 
-Spider паук это по сути набор функций-обработчиков сетевых запросов. Каждый обработчик в свою очередь может создать новые запросы или просто сохранить куда-либо данные. Каждый запрос описывается Task-объектом. Spider объект добавляет каждый новый запрос в очередь и выполняет его по мере освобождения сетевых ресурсов. Каждый Task-объект имеет имя: когда становится доступен результат сетевого запроса, то с помощью этого имени определяется имя функции-обработчика и вызывается эта функция.
+Spider паук это по сути набор функций-обработчиков сетевых запросов. Каждый обработчик в свою очередь может создать новые запросы или просто сохранить куда-либо данные. Каждый запрос описывается Task-объектом. Паук добавляет каждый новый запрос в очередь и выполняет его по мере освобождения сетевых ресурсов. Каждому Task-объекту присваиваетя имя. Когда становится доступен результат сетевого запроса, то с помощью этого имени определяется имя функции-обработчика и вызывается эта функция.
 
 Например, если мы создадим задание с именем "contact_page", nо мы должны будем объявить в нашем классе паука метод c именем "task_contact_page"::
 
     def task_contact_page(self, grab, task):
         ...
 
-Имя функции-обработчика определяется просто: берётся имя задания и добавляется префикс "task_".
+Имя функции-обработчика определяется так: берётся имя задания и добавляется префикс "task_".
 
-Рассмотрим различные способы создания Task-заданий.
+Конструктор Task объекта
+------------------------
 
+Конструктор Task-объекта принимает множество аргументов. Вы должны обязательно указать имя задания и адрес документа, вместо адреса можно использовать настроенный Grab-объект. Далее приведены примеры кода, который создаёт три одинаковых задания::
 
-initial_urls
-------------
+    # Using `url` argument
+    t = Task('wikipedia', url 'http://wikipedia.org/')
 
-В аттрибуте `initial_urls` Можно указать список адресов, с обработки которых паук должен начать свою работу::
+    # Using Grab intance
+    g = Grab()
+    g.setup(url='http://wikipedia.org/')
+    t = Task('wikipedia', grab=g)
 
-    class ExampleSpider(Spider):
-        initial_urls = ['http://google.com/', 'http://yahoo.com/']
+    # Using configured state of Grab instance
+    g = Grab()
+    g.setup(url='http://wikipedia.org/')
+    config = g.dump_config()
+    t = Task('wikipedia', grab_config=config)
 
-Для всех адресов, перечисленных в `initial_urls` будет создано задание с именем 'initial'. Это самый простой способ создания заданий, вы не можете управлять ничем кроме самих адресов документов.
+Также в конструкторе задания можно задать следующие свойства, работу которых вы сможете понять из описания других частей архитектуры Spider:
 
+:priority: приоритет задания, целое положительное число, чем меньше число, тем выше приоритет.
+:disable_cache: не использовать кэш паука для этого запроса, сетевой ответ в кэш сохранён не будет
+:refresh_cache: не использовать кэш паука, в случае удачного ответа обновить запись в кэше
+:valid_status: обрабатывать обычным способом указанные статусы. По-умолчанию, обычным образом
+    обрабатываются все 2xx статусы, а также 404 статус.
+:use_proxylist: использовать прокси лист заданные глобально для паука. По-умолчанию, опция включена.
 
-task_generator
---------------
+За исключением вышеуказанных и ещё нескольих аргументов все остальные аргументы просто сохраняются в Task-объект и доступны для дальнейшего использования. Таким образом Task-объект выступает как хранилище данных: можно запоминать данные и передавать их от запроса к запросу.
 
-Более сложный способ создания начальных заданий. Метод с именем `task_generator` должен являться python-генератором т.е. функцией выдающей множество значений с помощью yield инструкции. Spider будет обращаться к новым задания из `task_generator` каждый раз, когда его очередь будет опустошаться. Это позволяет не опасаться того, что вы создадите слишком много заданий.
+Task-объект как хранилище данных
+--------------------------------
 
-Вы, можете, например, открыть файл с миллионом записей и последовательно создавать с помощью них задания с помощью метода `task_generator`::
+В асинхронном окружении часто бывает нужным куда-то записать информацию о запросе, а затем "вспомнить" её, когда будет готов ответ на запрос. Как было сказано выше, все неспециальные аргументы конструктора Task-объекта, просто запоминаются в объекте и доступны в дальнейшем как его аттрибуты. Для удобства Task-объект имеет функцию `get`, которая возвращает None (или указанное вам значение), если запрошенного аттрибута в Task-объекте не нашлось. Рассмотрим примеры::
 
-    class ExampleSpider(Spider):
-        def task_generator(self):
-            for line in open('var/urls.txt'):
-                yield Task('download', url=line.strip())
+    t = Task('bing', url='http://bing.com/', disable_cache=True, foo='bar')
+    t.foo # == "bar"
+    t.get('foo') # == "bar"
+    t.get('asdf') # == None
+    t.get('asdf', 'qwerty') # == "qwerty"
 
+Клонирование Task-объекта
+-------------------------
 
-add_task
---------
+Иногда бывает удобным использовать существующий Task-объект для создания нового. Например, когда мы получили ответ на сетевой запрос и хотим сделать похожий запрос::
 
-Независимо от того, каким способом вы создали новое задание, в очередь заданий оно попадёт с помощью метода `add_task`. В случае использования `intial_urls` или `task_generator` метод `add_task` будет вызван неявно, но вы, конечно, можете использовать его напрямую, чтобы добавить новое задание в любом месте выполнения программы. Это можно делать даже до начала работы паука. Например::
-
-    bot = ExampleSpider()
-    bot.add_task('google', url='http://google.com')
-    bot.run()
-
-
-yield
------
-
-Инструкцию yield для создания заданий вы можете использовать в двух местах, во-первых, в методе `task_generator`, о чём уже писалось выше, во-вторых, в любой функции-обработчике результата. При вызове функции-обработчика Spider ловит все задания, которые она генерирует и складывает в очередь заданий. Создание заданий с помощью yield ничем не отличается от использования метода `add_task`, разве что запись получается более короткая::
-
-    class ExampleSpider(Spider):
-        initial_urls = ['http://google.com']
-        
-        def task_initial(self, grab, task):
-            yield Task('yahoo', url='yahoo.com')
-
-        def task_yahoo(self, grab, task):
-            pass
-
-
-Резюме
-------
-
-Для задания начальных заданий используйте аттрибут `initial_urls`, если вам нужна более сложная логика создания начальных заданий используйте метод `task_generator`. Для создания заданий внутри функций-обработчиков используйте инструкцию yield. Использовать метод `add_task` напрямую вам практически никога не понадобится.
-
-Есть также ряд методов для типичных случаев генерации новых заданий: обработка пагинации, обработка списка ссылок. Смотрите модуль `grab.spider.pattern`.
+    # TODO: придумать вменяемый пример

File docs/spider/task_building.rst

+.. _spider_task_building:
+
+Способы создания заданий
+========================
+
+Spider паук это по сути набор функций-обработчиков сетевых запросов. Каждый обработчик в свою очередь может создать новые запросы или просто сохранить куда-либо данные. Каждый запрос описывается Task-объектом. Паук добавляет каждый новый запрос в очередь и выполняет его по мере освобождения сетевых ресурсов. Каждому Task-объекту присваиваетя имя. Когда становится доступен результат сетевого запроса, то с помощью этого имени определяется имя функции-обработчика и вызывается эта функция.
+
+Например, если мы создадим задание с именем "contact_page", nо мы должны будем объявить в нашем классе паука метод c именем "task_contact_page"::
+
+        ...
+        self.add_task(Task('contact_page', url='http://domain.com/contact.html'))
+        ...
+
+    def task_contact_page(self, grab, task):
+        ...
+
+Имя функции-обработчика определяется так: берётся имя задания и добавляется префикс "task_".
+
+Рассмотрим различные способы создания Task-заданий.
+
+
+initial_urls
+------------
+
+В аттрибуте паука `initial_urls` Можно указать список адресов, с обработки которых паук должен начать свою работу::
+
+    class ExampleSpider(Spider):
+        initial_urls = ['http://google.com/', 'http://yahoo.com/']
+
+Для всех адресов, перечисленных в `initial_urls` будет создано задание с именем 'initial'. Это самый простой способ создания заданий, вы не можете управлять ничем кроме адресов запрашиваемых документов.
+
+
+.. _spider_task_generator:
+
+task_generator
+--------------
+
+Более сложный способ создания начальных заданий. Метод с именем `task_generator` должен являться python-генератором т.е. функцией выдающей множество значений с помощью yield инструкции. Spider будет обращаться к новым задания из `task_generator` каждый раз, когда его очередь будет опустошаться. Это позволяет не опасаться того, что вы создадите слишком много заданий. Выглядит это так: в начале работы паук извлекает некоторое количество заданий с помощью `task_generator` и помещает их в очередь, далее он выполняет запросы и следит за количеством заданий в очереди. Как только их становится слишком мало, паук обращется ещё раз к `task_generator` и добавляет новые задания.
+
+К примеру, вы можете открыть файл с миллионом записей и последовательно читать строки из него, создавая всё новые и новые задания::
+
+    class ExampleSpider(Spider):
+        def task_generator(self):
+            for line in open('var/urls.txt'):
+                yield Task('download', url=line.strip())
+
+
+add_task
+--------
+
+Независимо от того, каким способом вы создали новое задание, в очередь заданий оно попадёт с помощью метода `add_task`. В случае использования `intial_urls` или `task_generator` метод `add_task` будет вызван неявно, но вы, конечно, можете использовать его напрямую, чтобы добавить новое задание в любом месте выполнения программы. Это можно делать даже до начала работы паука. Например::
+
+    bot = ExampleSpider()
+    bot.add_task('google', url='http://google.com')
+    bot.run()
+
+
+yield
+-----
+
+Инструкцию yield для создания заданий вы можете использовать в двух местах, во-первых, в методе `task_generator`, о чём уже писалось выше, во-вторых, в любой функции-обработчике результата. При вызове функции-обработчика Spider ловит все задания, которые она генерирует и складывает в очередь заданий. Создание заданий с помощью yield ничем не отличается от использования метода `add_task`, разве что запись получается более короткая::
+
+    class ExampleSpider(Spider):
+        initial_urls = ['http://google.com']
+        
+        def task_initial(self, grab, task):
+            # Google page was fetched
+            # Now let's download yahoo page
+            yield Task('yahoo', url='yahoo.com')
+
+        def task_yahoo(self, grab, task):
+            pass
+
+
+Резюме
+------
+
+Для задания начальных заданий используйте аттрибут `initial_urls`, если вам нужна более сложная логика создания начальных заданий используйте метод `task_generator`. Для создания заданий внутри функций-обработчиков используйте инструкцию yield. Использовать метод `add_task` напрямую вам практически никога не понадобится.
+
+Есть также ряд методов для типичных случаев генерации новых заданий: обработка пагинации, обработка списка ссылок. Смотрите модуль `grab.spider.pattern`.

File docs/spider/task_queue.rst

+.. _spider_task_queue:
+
+===============
+Очередь заданий
+===============
+
+Приоритеты заданий
+------------------
+
+Все задания помещаются в очередь, откуда они ивзлекаются по мере освобождения сетевых ресурсов и выполняются. У каждого задания есть приоритет обозначаемый положительным целым числом. Чем меньше это число, тем выше приоритет. Если приоритет не указан явно при создании задания, он назначается автоматически. Есть два алгоритма автоматического задания приоритетов:
+
+:random: случайные приоритеты
+:const: один и тот же приоритет для всех заданий.
+
+По-умолчанию, используются случайные приоритеты. Способ выбора приоритетов задаётся аргументом `priority_mode` при создании Spider-объекта::
+    
+    bot = SomeSpider(priority_mode='const')
+
+Бэкенды хранилищ
+----------------
+
+Очередь заданий в Grab выделена в отдельный слой, это позволяет писать реализации очереди для различных систем хранения данных. Из коробки доступны две реализации: хранение заданий в памяти и хранение заданий в mongodb. По-умолчанию, исползуется хранение заданий в памяти. Если объёма вашей памяти не хватает, чтобы хранить все задания, то рекомендуется использовать mongodb очередь::
+
+    bot = SomeSpider()
+    bot.setup_queue() # очередь в памяти
+    bot.setup_queue(backend='mongo', database='database-name') # очередь в монго
+
+Генератор заданий
+-----------------
+
+Также для сокращения потребления памяти очередью заданий вы можете воспользоваться :ref:`spider_task_generator`. методом, задания из которого будут браться только по мере опустошения очереди заданий.