Scrapy: асинхронный запрос к базе данных в промежуточном программном обеспечении Spider? - PullRequest
3 голосов
/ 17 марта 2012

ПРЕАМБУЛА:

У меня есть паук, который сохраняет свои результаты в базе данных. Затем, чтобы сэкономить время и ресурсы удаленного веб-сервера, я решил не запрашивать элементы, которые уже есть в базе данных. Читая документы, я решил, что пользовательское промежуточное ПО для пауков - лучший выбор, который у меня есть.

class SkipDupeMiddleware(object):
    process_spider_output(response, result, spider):
        for r in result:
            if isinstance(r, Request) and item_in_database(r.url):
                log.msg('Skip %s' % r.url)
            else:
                yield r

, где item_in_database запрашивает БД.

Он также отлично работает и экономит много времени.

Теперь реальная проблема:

Позже я прочитал, что использование блокирующих запросов к базе данных в неблокирующих приложениях не очень хорошая идея. Я всегда до «делаю все правильно» и решил обернуть все мои запросы к БД в adbapi

Я нашел рецепты для использования adbapi в конвейерах , но возможно ли это сделать на уровне промежуточного программного обеспечения? Ожидается, что промежуточное программное обеспечение вернет BaseItem, Request или None, но adbapi вернет twisted Deferred, что позже вернет Request или None.

А теперь я застрял.

Ответы [ 4 ]

2 голосов
/ 01 апреля 2012

Вы можете (и должны) возвратить метод Deferred из конвейера process_item().

1 голос
/ 17 апреля 2012

Scrapy по умолчанию не выполняет повторный запрос при сканировании с использованием фильтра дубликатов (по умолчанию используется).Параметр настройки DUPEFILTER_CLAS решает, какой фильтр использовать. Его значение по умолчанию: «scrapy.dupefilter.RFPDupeFilter».На самом деле, это только выбор сейчас.Если вы хотите постоянную функцию, вы должны установить JOBDIR.Затем Scrapy сохранит доступный URL и загрузит их (в файл фильтра) при следующем запуске.

Здесь вы можете получить несколько советов: http://groups.google.com/group/scrapy-users/browse_thread/thread/56546e9fab7030f3

1 голос
/ 17 марта 2012

AFAIK,

Scrapy в основном синхронный. Только загрузка страниц выполняется асинхронно, поэтому запросы имеют обратные вызовы.

Трубопроводы и промежуточное ПО синхронны.

0 голосов
/ 01 мая 2017

Аналогичная проблема, я хочу сделать несколько проверок (включая вызовы БД) в пауке для первых нескольких элементов и прекратить сканирование, если проверки пройдены (я думаю, что если есть только несколько запросов, мы можем сделать их, используя блокировка соединения дб).

Одна мысль пришла мне в голову: если паук scrapy хорош в выполнении http-запросов к сайтам, которые мы чистим, давайте делать асинхронные db-запросы, используя тот же механизм. Это должно быть легко реализовать для баз данных, имеющих REST API, таких какasticsearch.

Вот что я сделал для AWS S3:

from types import MethodType

import botocore.session
import treq
from scrapy import Request


class BotocoreRequest(Exception):

    def __init__(self, request, *args, **kwargs):
        super(BotocoreRequest, self).__init__(*args, **kwargs)
        self.method = request.method
        # https://github.com/twisted/treq/issues/185
        self.url = request.url.replace('https://', 'http://')
        self.headers = dict(request.headers)
        self.body = request.body and request.body.read()


def _send_request(self, request_dict, operation_model):
    request = self.create_request(request_dict, operation_model)
    raise BotocoreRequest(request=request)


class ScrapyAWSClient(object):
    def __init__(self, service, access_key, secret_key, region, timeout=30):
        session = botocore.session.get_session()
        session.set_credentials(
            access_key=access_key,
            secret_key=secret_key
        )
        self.client = session.create_client(service, region_name=region)
        endpoint = self.client._endpoint
        endpoint._send_request = MethodType(_send_request, endpoint)
        self.timeout = timeout

    def request(self, method, callback, meta, **kwargs):
        try:
            getattr(self.client, method)(**kwargs)
        except BotocoreRequest as e:
            return Request(
                method=e.method,
                url=e.url,
                body=e.body,
                headers=e.headers,
                meta=meta,
                callback=callback,
                dont_filter=True
            )

Паук:

class MySpider(Spider):

    def __init__(self, *args, **kwargs):
        super(MySpider, self).__init__(*args, **kwargs)
        self.client = ScrapyAWSClient(
            service='s3',
            access_key='',
            secret_key='',
            region='your-region'
        )

    def parse(self, response):
        ...
        yield self.client.request(
            method='get_object',
            Bucket='my-s3-bucket',
            Key='my-key',
            callback=self.my_parser,
            meta={
                'handle_httpstatus_list': [200, 403]
            }
        )
...