Мы внедрили сканер Scrapy Weibo раньше. Хотя сканер является асинхронным и многопоточным, мы можем запустить его только на одном хосте, поэтому эффективность сканирования по-прежнему ограничена. Распределенный сканер представляет собой комбинацию нескольких хостов. задача сканирования, которая значительно повысит эффективность сканирования.
1. Распределенная архитектура сканера
Прежде чем понять архитектуру распределенного поискового робота, сначала просмотрите архитектуру Scrapy, как показано на следующем рисунке.
В автономном сканере Scrapy есть локальная очередь сканирования Queue, которая реализована с помощью модуля deque. Если сгенерирован новый запрос, он будет помещен в очередь, а затем запланирован планировщиком запроса. После этого Запрос передается Загрузчику для сканирования.Простая архитектура планирования показана на рисунке ниже.
Если два Планировщика одновременно принимают Запросы из очереди, и у каждого Планировщика есть соответствующий Загрузчик, что произойдет с эффективностью сканирования, когда пропускной способности достаточно, сканирование идет нормально, а давление доступа к очереди не учитывается? Да, эффективность сканирования удвоится.
Таким образом, планировщик может быть расширен несколько раз, и загрузчик также может быть расширен несколько раз. Очередь сканирования Очередь всегда должна быть 1, т. е. так называемая общая очередь сканирования. Таким образом, можно гарантировать, что после того, как планировщик запланирует запрос из очереди, другие планировщики не будут планировать запрос повторно, чтобы несколько планировщиков могли выполнять сканирование синхронно. Это базовый прототип распределенного сканера.Простая архитектура планирования показана на рисунке ниже.
Все, что нам нужно сделать, это запустить задачи сканера на нескольких хостах одновременно для совместного сканирования, а предпосылкой совместного сканирования является совместное использование очереди сканирования. Таким образом, каждому хосту не нужно поддерживать свою собственную очередь сканирования, а нужно обращаться к запросам из общей очереди сканирования. Тем не менее, каждый хост по-прежнему имеет свои собственные планировщик и загрузчик, поэтому функции планирования и загрузки выполняются отдельно. Если потребление производительности при доступе к очереди не учитывается, эффективность сканирования все равно удвоится.
2. Поддерживайте очередь сканирования
Итак, для чего используется эта очередь? Первое, что нужно учитывать, это производительность. Естественно, мы думаем о Redis на основе хранилища в памяти, которое поддерживает различные структуры данных, такие как List, Set, Sorted Set и т. д. Операция доступа также очень проста.
Эти хранилища структур данных, поддерживаемые Redis, имеют свои преимущества.
В списке есть
lpush()
,lpop()
,rpush()
,rpop()
метод, мы можем использовать его для реализации очереди обхода стека по принципу «первым пришел — первым обслужен» или очереди обхода стека «первым пришел — последним вышел».Элементы коллекции неупорядочены и не повторяются, поэтому мы можем легко реализовать случайную сортировку и неповторяющуюся очередь сканирования.
Упорядоченные наборы представлены баллами, а запрос Scrapy также имеет управление приоритетом, которое мы можем использовать для реализации очередей с приоритетным планированием.
Нам нужно гибко выбирать разные очереди в соответствии с потребностями конкретных краулеров.
3. Как убрать вес
Scrapy имеет автоматическую дедупликацию, а ее дедупликация использует коллекции в Python. Эта коллекция записывает отпечаток каждого запроса в Scrapy, который на самом деле является хеш-значением запроса. Мы можем взглянуть на исходный код Scrapy следующим образом:
import hashlib
def request_fingerprint(request, include_headers=None):
if include_headers:
include_headers = tuple(to_bytes(h.lower())
for h in sorted(include_headers))
cache = _fingerprint_cache.setdefault(request, {})
if include_headers not in cache:
fp = hashlib.sha1()
fp.update(to_bytes(request.method))
fp.update(to_bytes(canonicalize_url(request.url)))
fp.update(request.body or b'')
if include_headers:
for hdr in include_headers:
if hdr in request.headers:
fp.update(hdr)
for v in request.headers.getlist(hdr):
fp.update(v)
cache[include_headers] = fp.hexdigest()
return cache[include_headers]
request_fingerprint()
Это метод вычисления отпечатка запроса, который использует внутреннюю хеш-библиотеку.sha1()
метод. Вычисляемые поля включают метод запроса, URL-адрес, тело и заголовки. Пока здесь есть разница, вычисленные результаты будут другими. Результатом вычисления является зашифрованная строка, то есть отпечаток пальца. Каждый запрос имеет уникальный отпечаток, и отпечаток представляет собой строку. Гораздо проще определить, повторяется ли строка, чем определить, повторяется ли объект запроса, поэтому отпечаток можно использовать в качестве основы для определения того, является ли запрос повторил.
Так как же определить повторение? Scrapy реализует это следующим образом:
def __init__(self):
self.fingerprints = set()
def request_seen(self, request):
fp = self.request_fingerprint(request)
if fp in self.fingerprints:
return True
self.fingerprints.add(fp)
в классе дедупликацииRFPDupeFilter
, существует одинrequest_seen()
метод, этот метод имеет один параметрrequest
, его роль состоит в том, чтобы определить, повторяется ли объект запроса. этот вызов методаrequest_fingerprint()
Получите отпечаток Запроса и проверьте, существует ли отпечаток вfingerprints
переменная иfingerprints
Является набором, элементы которого не повторяются. Если отпечаток существует, вернитеTrue
, указывающий, что Запрос повторяется, в противном случае в набор добавляется отпечаток пальца. Если тот же Запрос будет передан в следующий раз, и отпечаток тот же, то отпечаток уже существует в коллекции, и объект Запроса будет напрямую определен как дубликат. Таким образом достигается цель дедупликации.
Процесс дедупликации Scrapy заключается в использовании неповторяющихся характеристик элементов коллекции для реализации дедупликации запроса.
Для распределенных поисковых роботов мы, конечно же, больше не можем использовать собственную коллекцию каждого поискового робота для дедупликации. Это связано с тем, что каждый хост поддерживает свою собственную коллекцию отдельно и не может использоваться совместно. Если несколько хостов генерируют один и тот же запрос, они могут дедуплицировать только друг друга, и они не могут быть дедуплицированы между хостами.
Затем, чтобы добиться дедупликации, этот набор отпечатков также должен быть общим.Redis просто имеет заданную структуру данных хранилища.Мы можем использовать набор Redis в качестве набора отпечатков, поэтому Redis также использует набор для дедупликации. После того, как каждый хост генерирует новый Запрос, отпечаток Запроса сравнивается с набором.Если отпечаток уже существует, то это означает, что Запрос является дубликатом, в противном случае отпечаток Запроса может быть добавлен в набор. Используя тот же принцип и разные структуры хранения, мы также реализуем дедупликацию распределенного запроса.
В-четвертых, предотвратить прерывание
В Scrapy очередь запросов при работе сканера помещается в память. После того, как искатель прерывается, пространство очереди освобождается, и очередь уничтожается. Таким образом, как только поисковый робот был прерван, его повторный запуск эквивалентен новому процессу сканирования.
Чтобы продолжить сканирование после прерывания, мы можем сохранить запрос в очереди и напрямую прочитать сохраненные данные для следующего сканирования, чтобы получить очередь, которая была просканирована в прошлый раз. Мы можем указать путь хранения для очереди сканирования в Scrapy, Этот путь используетJOB_DIR
Переменные для идентификации, мы можем использовать следующую команду для достижения:
scrapy crawl spider -s JOB_DIR=crawls/spider
Более подробные способы использования см. в официальной документации по ссылке: https://doc.scrapy.org/en/latest/topics/jobs.html.
В Scrapy мы фактически сохраняем очередь сканирования локально, а второе сканирование может напрямую читать и восстанавливать очередь. Так что нам все еще нужно беспокоиться об этой проблеме в распределенной архитектуре? ненужный. Поскольку сама очередь сканирования хранится в базе данных, если обходчик будет прерван, Запрос в базе данных все еще существует, и при следующем запуске сканирование продолжится с того места, где оно было прервано в прошлый раз.
Поэтому, когда очередь Redis пуста, сканер снова начнет сканирование; когда очередь Redis не пуста, сканер продолжит сканирование с того места, где он остановился в прошлый раз.
5. Реализация архитектуры
Затем нам нужно реализовать эту архитектуру в программе. Сначала реализуйте общую очередь сканирования, а также реализуйте функцию дедупликации. Кроме того, перепишите реализацию планировщика для доступа к запросам из общей очереди сканирования.
К счастью, кто-то реализовал эту логику и архитектуру и выпустил ее в виде пакета Python под названием Scrapy-Redis. Далее давайте подробно рассмотрим реализацию исходного кода Scrapy-Redis и то, как она работает.
Этот ресурс был впервые опубликован в личном блоге Цуй Цинцай Цзин Ми:Практическое руководство по разработке веб-краулера на Python3 | Цзин Ми
Если вы хотите узнать больше информации о поисковых роботах, обратите внимание на мой личный публичный аккаунт WeChat: Coder of Attack.
WeChat.QQ.com/Day/5 Это радость VE Z…(автоматическое распознавание QR-кода)