Python Scrapy - фильтр на основе mimetype, чтобы избежать загрузки нетекстовых файлов

У меня есть работающий проект scrapy, но он требует большой пропускной способности, поскольку пытается загрузить много двоичных файлов (zip, tar, mp3, ... и т. Д.).

Я думаю, что лучшим решением является фильтрация запросов на основе HTTP-заголовка mimetype (Content-Type :). Я посмотрел код scrapy и нашел такую ​​настройку:

DOWNLOADER_HTTPCLIENTFACTORY = 'scrapy.core.downloader.webclient.ScrapyHTTPClientFactory'

Я изменил его на: DOWNLOADER_HTTPCLIENTFACTORY = 'myproject.webclients.ScrapyHTTPClientFactory'

И немного поигрался с ScrapyHTTPPageGetter, вот выделенные правки:

class ScrapyHTTPPageGetter(HTTPClient):
    # this is my edit
    def handleEndHeaders(self):
        if 'Content-Type' in self.headers.keys():
            mimetype = str(self.headers['Content-Type'])
            # Actually I need only the html, but just in 
            # case I've preserved all the text
            if mimetype.find('text/') > -1: 
                # Good, this page is needed
                self.factory.gotHeaders(self.headers)
            else:
                self.factory.noPage(Exception('Incorrect Content-Type'))

Я считаю, что это неправильно, мне нужен более удобный способ отмены / удаления запроса сразу после определения, что это нежелательный mimetype. Вместо того, чтобы ждать загрузки всех данных.

Изменить:
Я прошу конкретно об этой части self.factory.noPage(Exception('Incorrect Content-Type')), это правильный способ отменить запрос.

Обновление 1:
Мои текущие настройки привели к сбою сервера Scrapy, поэтому не пытайтесь использовать тот же код, что и выше, для решения проблемы.

Обновление 2:
Я настроил для тестирования веб-сайт на базе Apache со следующей структурой:

/var/www/scrapper-test/Zend -> /var/www/scrapper-test/Zend.zip (symlink)
/var/www/scrapper-test/Zend.zip

Я заметил, что Scrapy отбрасывает файлы с расширением .zip, но удаляет файл без .zip, хотя это просто символическая ссылка на него.


person Omar Al-Ithawi    schedule 15.11.2012    source источник
comment
это на самом деле не работает? handleEndHeaders должен вызываться до загрузки тела   -  person fmoo    schedule 17.11.2012
comment
@fmoo Пожалуйста, посмотрите правку, я был более конкретным.   -  person Omar Al-Ithawi    schedule 18.11.2012
comment
Моя текущая установка привела к сбою сервера Scrapy, поэтому, пожалуйста, не пытайтесь использовать тот же код, что и выше, для решения проблемы.   -  person Omar Al-Ithawi    schedule 05.01.2013


Ответы (3)


Я создал это промежуточное ПО, чтобы исключить любой тип ответа, который не входит в белый список регулярных выражений:

from scrapy.http.response.html import HtmlResponse
from scrapy.exceptions import IgnoreRequest
from scrapy import log
import re

class FilterResponses(object):
    """Limit the HTTP response types that Scrapy dowloads."""

    @staticmethod
    def is_valid_response(type_whitelist, content_type_header):
        for type_regex in type_whitelist:
            if re.search(type_regex, content_type_header):
                return True
        return False

    def process_response(self, request, response, spider):
        """
        Only allow HTTP response types that that match the given list of 
        filtering regexs
        """
        # each spider must define the variable response_type_whitelist as an
        # iterable of regular expressions. ex. (r'text', )
        type_whitelist = getattr(spider, "response_type_whitelist", None)
        content_type_header = response.headers.get('content-type', None)
        if not type_whitelist:
            return response
        elif not content_type_header:
            log.msg("no content type header: {}".format(response.url), level=log.DEBUG, spider=spider)
            raise IgnoreRequest()
        elif self.is_valid_response(type_whitelist, content_type_header):
            log.msg("valid response {}".format(response.url), level=log.DEBUG, spider=spider)
            return response
        else:
            msg = "Ignoring request {}, content-type was not in whitelist".format(response.url)
            log.msg(msg, level=log.DEBUG, spider=spider)
            raise IgnoreRequest()

Чтобы использовать его, добавьте его в settings.py:

DOWNLOADER_MIDDLEWARES = {
    '[project_name].middlewares.FilterResponses': 999,
}
person saxman01    schedule 12.05.2014
comment
Спасибо. Это отменяет файлы до или после загрузки? То есть, что, если размер файла составляет 30 ГБ, будет ли он отменен сразу после загрузки заголовков, или ему придется ждать полного ответа. - person Omar Al-Ithawi; 13.05.2014
comment
Сначала отметьте ошибки, которые я только что исправил. (Мне пришлось отредактировать пару раз, потому что я ошибся с уценкой). Да, на основе моих экспериментов по подбрасыванию сообщений журнала в это промежуточное ПО во время выполнения Scrapy, это остановит ответ до загрузки файла. - person saxman01; 13.05.2014
comment
Большой. Вы не возражаете, чтобы ответ был принят как принятый? (вместо вашего ответа)? - person Omar Al-Ithawi; 13.05.2014
comment
это все просто фальшивые точки доступа в Интернет. Просто выберите тот, который, по вашему мнению, является лучшим ответом на ваш вопрос, ради будущих гуглеров. - person saxman01; 13.05.2014
comment
process_response? Это должно произойти после загрузки файла или, в лучшем случае, в самом начале загрузки. Просто используйте process_request, см. stackoverflow.com/questions/12140460/ - person Christian; 04.03.2021

Может быть, уже слишком поздно. Вы можете использовать заголовок Accept для фильтрации данных, которые вы ищете.

person Badarau Petru    schedule 04.01.2013
comment
Нет, я все еще ищу ответ. Что делать, если сервер понимает или игнорирует заголовок Accept :? - person Omar Al-Ithawi; 05.01.2013
comment
Может быть, это неправильная идея. Вы можете разделить процесс загрузки на два этапа: 1. Запрос с методом HEADER и 2. Если тип содержимого - Ok, запрос с методом Get. С уважением. - person Badarau Petru; 09.01.2013
comment
В настоящее время я пытаюсь сделать это правильно, дружественным к Scrapy способом, используя промежуточное программное обеспечение загрузчика. Но разделение запроса на две фазы усложнит настройку Scrapy. Я бы предпочел поставить веб-прокси-сервер и выполнить логику фильтрации, а не возиться с python-twisted. - person Omar Al-Ithawi; 09.01.2013
comment
У меня есть другая идея. Вы можете определить свой класс промежуточного программного обеспечения с помощью метода process_response, который будет проверять ваш ответ в классе Downloader и генерирует ли случай новый запрос. Вы должны добавить этот класс промежуточного программного обеспечения в словарь DOWNLOADER_MIDDLEWARES_BASE файла default_settings.py со значением больше 1000. - person Badarau Petru; 09.01.2013

Решение состоит в том, чтобы настроить Node.js прокси и настроить Scrapy для его использования через переменную среды http_proxy.

Что должен делать прокси:

  • Принимает HTTP-запросы от Scrapy и отправляет их на просматриваемый сервер. Затем он возвращает ответ от Scrapy, то есть перехватывает весь HTTP-трафик.
  • Для двоичных файлов (на основе реализованной вами эвристики) он отправляет 403 Forbidden ошибку в Scrapy и немедленно закрывает запрос / ответ. Это помогает сэкономить время, трафик и Scrapy не выйдет из строя.

Образец прокси-кода

Это действительно работает!

http.createServer(function(clientReq, clientRes) {
    var options = {
        host: clientReq.headers['host'],
        port: 80,
        path: clientReq.url,
        method: clientReq.method,
        headers: clientReq.headers
    };


    var fullUrl = clientReq.headers['host'] + clientReq.url;
    
    var proxyReq = http.request(options, function(proxyRes) {
        var contentType = proxyRes.headers['content-type'] || '';
        if (!contentType.startsWith('text/')) {
            proxyRes.destroy();            
            var httpForbidden = 403;
            clientRes.writeHead(httpForbidden);
            clientRes.write('Binary download is disabled.');
            clientRes.end();
        }
        
        clientRes.writeHead(proxyRes.statusCode, proxyRes.headers);
        proxyRes.pipe(clientRes);
    });

    proxyReq.on('error', function(e) {
        console.log('problem with clientReq: ' + e.message);
    });

    proxyReq.end();
  
}).listen(8080);
person Omar Al-Ithawi    schedule 12.12.2013
comment
есть ли возможность реализовать этот фильтр в расширении python twisted или downloader? - person Frederic Bazin; 15.04.2014