Отдельный выходной файл для каждого URL-адреса, указанного в списке start_urls паука в scrapy.

Я хочу создать отдельный выходной файл для каждого URL-адреса, который я установил в start_urls паука, или каким-то образом хочу разделить выходные файлы на начальный URL-адрес.

Ниже приведены start_urls моего паука.

start_urls = ['http://www.dmoz.org/Arts/', 'http://www.dmoz.org/Business/', 'http://www.dmoz.org/Computers/']

Я хочу создать отдельный выходной файл, например

Arts.xml
Business.xml
Computers.xml

Я точно не знаю, как это сделать. Я думаю добиться этого, внедрив что-то вроде следующего в методе spider_opened класса конвейера элементов,

import re
from scrapy import signals
from scrapy.contrib.exporter import XmlItemExporter

class CleanDataPipeline(object):
    def __init__(self):
        self.cnt = 0
        self.filename = ''

    @classmethod
    def from_crawler(cls, crawler):
        pipeline = cls()
        crawler.signals.connect(pipeline.spider_opened, signals.spider_opened)
        crawler.signals.connect(pipeline.spider_closed, signals.spider_closed)
        return pipeline

    def spider_opened(self, spider):
        referer_url = response.request.headers.get('referer', None)
        if referer_url in spider.start_urls:
            catname = re.search(r'/(.*)$', referer_url, re.I)
            self.filename = catname.group(1)

        file = open('output/' + str(self.cnt) + '_' + self.filename + '.xml', 'w+b')
        self.exporter = XmlItemExporter(file)
        self.exporter.start_exporting()

    def spider_closed(self, spider):
        self.exporter.finish_exporting()
        #file.close()

    def process_item(self, item, spider):
        self.cnt = self.cnt + 1
        self.spider_closed(spider)
        self.spider_opened(spider)
        self.exporter.export_item(item)
        return item

Где я пытаюсь найти URL-адрес реферера каждого очищенного элемента в списке start_urls. Если URL-адрес реферера найден в start_urls, то имя файла будет создано с использованием этого URL-адреса реферера. Но проблема в том, как получить доступ к объекту ответа внутри метода spider_opened(). Если я могу получить к нему доступ, я могу создать файл на его основе.

Любая помощь, чтобы найти способ выполнить это? Заранее спасибо!

[ИЗМЕНИТЬ]

Решил мою проблему, изменив код конвейера следующим образом.

import re
from scrapy import signals
from scrapy.contrib.exporter import XmlItemExporter

class CleanDataPipeline(object):
    def __init__(self):
        self.filename = ''
        self.exporters = {}

    @classmethod
    def from_crawler(cls, crawler):
        pipeline = cls()
        crawler.signals.connect(pipeline.spider_opened, signals.spider_opened)
        crawler.signals.connect(pipeline.spider_closed, signals.spider_closed)
        return pipeline

    def spider_opened(self, spider, fileName = 'default.xml'):
        self.filename = fileName
        file = open('output/' + self.filename, 'w+b')
        exporter = XmlItemExporter(file)
        exporter.start_exporting()
        self.exporters[fileName] = exporter

    def spider_closed(self, spider):
        for exporter in self.exporters.itervalues(): 
            exporter.finish_exporting()

    def process_item(self, item, spider):
        fname = 'default'
        catname = re.search(r'http://www.dmoz.org/(.*?)/', str(item['start_url']), re.I)
        if catname:
            fname = catname.group(1)
        self.curFileName = fname + '.xml'

        if self.filename == 'default.xml':
            if os.path.isfile('output/' + self.filename):
                os.rename('output/' + self.filename, 'output/' + self.curFileName)
            exporter = self.exporters['default.xml']
            del self.exporters['default.xml']
            self.exporters[self.curFileName] = exporter
            self.filename = self.curFileName

        if self.filename != self.curFileName and not self.exporters.get(self.curFileName):
            self.spider_opened(spider, self.curFileName)

        self.exporters[self.curFileName].export_item(item)
        return item

Также реализовано make_requests_from_url в пауке, чтобы установить start_url для каждого элемента.

def make_requests_from_url(self, url):
    request = Request(url, dont_filter=True)
    request.meta['start_url'] = url
    return request

person FMoridhara    schedule 26.05.2014    source источник
comment
Я столкнулся с той же ситуацией, но когда я попытался применить ваше решение, я получил ошибку exceptions.KeyError: 'start_url' из метода process_item(). Не могли бы вы подсказать, что мне делать? Я здесь новичок. :'(   -  person Quizer    schedule 07.02.2015
comment
@NatthawuteSae-Lim Вы пытались добавить в паук метод, который может установить начальный URL-адрес в request.meta? Проверьте последний раздел моего ответа.   -  person FMoridhara    schedule 05.03.2015


Ответы (3)


Я бы реализовал более явный подход (не проверенный):

  • настроить список возможных категорий в settings.py:

    CATEGORIES = ['Arts', 'Business', 'Computers']
    
  • определите свой start_urls на основе настройки

    start_urls = ['http://www.dmoz.org/%s' % category for category in settings.CATEGORIES]
    
  • добавить category Field в класс Item

  • в методе разбора паука установите поле category в соответствии с текущим response.url, например:

    def parse(self, response):
         ...
         item['category'] = next(category for category in settings.CATEGORIES if category in response.url)
         ...
    
  • в конвейере откройте экспортеры для всех категорий и выберите, какой экспортер использовать на основе item['category']:

    def spider_opened(self, spider):
        ...
        self.exporters = {}
        for category in settings.CATEGORIES:
            file = open('output/%s.xml' % category, 'w+b')
            exporter = XmlItemExporter(file)
            exporter.start_exporting()
            self.exporters[category] = exporter
    
    def spider_closed(self, spider):
        for exporter in self.exporters.itervalues(): 
            exporter.finish_exporting()
    
    def process_item(self, item, spider):
        self.exporters[item['category']].export_item(item)
        return item
    

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

Надеюсь, это поможет.

person alecxe    schedule 26.05.2014
comment
Спасибо @alecxe за полезный пример. Это определенно сработает. Но то, что я опубликовал, - это просто демонстрационный пример, который я хочу протестировать. Если это удастся, мне нужно реализовать ту же логику на большом уровне. Где количество начальных URL-адресов будет в тысячах. В то время будет невозможно перечислить их в настройках. Также start_url будет отличаться от паука к пауку, поэтому я просто не могу определить их в настройках. - person FMoridhara; 30.05.2014
comment
Спасибо @alecxe, попробовав несколько других вариантов, наконец решил проблему, как вы предложили! - person FMoridhara; 11.06.2014
comment
Отличный пример, благодаря вам я смог создать собственный конвейер для вывода JSON для каждого отдельного URL-адреса в url_list. Спасибо @alecxe! - person soldy; 15.09.2018

Пока вы не храните его в самом элементе, вы не можете узнать исходный URL-адрес. Следующее решение должно работать для вас:

  • переопределите make_request_from_url, чтобы отправлять начальный URL-адрес с каждым Request, который вы делаете. Вы можете сохранить его в атрибуте meta вашего файла Request. Обход этого начального URL с каждым последующим Request.

  • как только вы решите передать элемент в конвейер, введите начальный URL-адрес элемента из response.meta['start_url']

Надеюсь, поможет. Следующие ссылки могут быть полезны:

http://doc.scrapy.org/en/latest/topics/spiders.html#scrapy.spider.Spider.make_requests_from_url

http://doc.scrapy.org/en/latest/topics/request-response.html?highlight=meta#passing-additional-data-to-callback-functions

person user2016508    schedule 26.05.2014
comment
Спасибо @ user2016508, мне удалось установить start_url в элементе, но теперь я не могу получить доступ к этому полю start_url списка элементов внутри метода spider_opened()? - person FMoridhara; 28.05.2014
comment
Я считаю, что решение, которое вы указали выше в своем вопросе, достаточно хорошее. Проверив начальный URL-адрес элемента, вы можете точно определить правильный выходной файл в методе process_item. - person user2016508; 29.05.2014
comment
Привет @user2016508, как мне получить доступ к элементу ['start_url'] внутри spider_opened? Мне это понадобится в spider_opened() для создания нового файла на основе элемента ['start_url'], который мне удалось установить в пауке. Я не понимаю, как передать его от паука к конвейеру? - person FMoridhara; 30.05.2014
comment
Привет @FMoridhara, намного выше, чем вы используете start_urls для создания соответствующих файлов csv. Это решение недостаточно хорошо для вашей цели?! - person user2016508; 30.05.2014
comment
Мне нужно установить имя файла перед следующей строкой, где XmlItemExporter требует путь к файлу. self.exporter = XmlItemExporter(file) Начальный URL для определенного элемента, который очищается, может быть доступен только в том случае, если я могу получить доступ к объекту элемента внутри spider_opened(), но я не думаю, что это будет возможно, поскольку паук только что открыт, и никакие элементы не будут там в то время. В этом сценарии, как я могу достичь своей цели? - person FMoridhara; 06.06.2014
comment
Привет, @ user2016508 start_urls для моего класса паука доступен через объект паука внутри spider_opened(), но мне понадобится start_url для конкретного очищаемого элемента. Может быть, я усложняю это, но не получаю способ обойти это. - person FMoridhara; 06.06.2014
comment
Спасибо @user2016508, ваше предложение помогло решить проблему! - person FMoridhara; 11.06.2014

Вот как я сделал это для своего проекта, не устанавливая категорию в элементе:

Передайте аргумент из командной строки следующим образом:

scrapy crawl reviews_spider -a brand_name=apple

Получите аргумент и установите аргументы паука в my_spider.py

def __init__(self, brand_name, *args, **kwargs):
    self.brand_name = brand_name
    super(ReviewsSpider, self).__init__(*args, **kwargs)

    # i am reading start_urls from an external file depending on the passed argument
    with open('make_urls.json') as f:
        self.start_urls = json.loads(f.read())[self.brand_name]

In pipelines.py:

class ReviewSummaryItemPipeline(object):
    @classmethod
    def from_crawler(cls, crawler):
        pipeline = cls()
        crawler.signals.connect(pipeline.spider_opened, signals.spider_opened)
        crawler.signals.connect(pipeline.spider_closed, signals.spider_closed)
        return pipeline

    def spider_opened(self, spider):
        # change the output file name based on argument
        self.file = open(f'reviews_summary_{spider.brand_name}.csv', 'w+b')
        self.exporter = CsvItemExporter(self.file)
        self.exporter.start_exporting()

    def spider_closed(self, spider):
        self.exporter.finish_exporting()
        self.file.close()

    def process_item(self, item, spider):
        self.exporter.export_item(item)
        return item
person penduDev    schedule 10.02.2020