【问题标题】:Separate output file for every url given in start_urls list of spider in scrapy在scrapy中蜘蛛的start_urls列表中给出的每个url的单独输出文件
【发布时间】:2014-07-15 03:33:26
【问题描述】:

我想为我在蜘蛛的 start_urls 中设置的每个 url 创建单独的输出文件,或者想以某种方式拆分输出文件 start url。

下面是我的蜘蛛的 start_urls

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

我想创建单独的输出文件,如

Arts.xml
业务.xml
计算机.xml

我不知道该怎么做。我正在考虑通过在 item 管道类的 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

我试图在 start_urls 列表中找到每个抓取项目的引用 URL。如果在 start_urls 中找到引用 URL,则将使用该引用 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

还在spider中实现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

【问题讨论】:

  • 我面临同样的情况,但是当我尝试应用您的解决方案时,我从 process_item() 方法中得到了“exceptions.KeyError: 'start_url'”错误。你能建议我应该怎么做吗?我是这里的新手。 :'(
  • @NatthawuteSae-Lim 您是否尝试过在蜘蛛中添加一个可以在 request.meta 中设置启动 url 的方法?检查我答案的最后一部分。

标签: python web-scraping scrapy scrapy-spider


【解决方案1】:

在项目中没有设置类别的情况下,我是这样为我的项目做的:

像这样从命令行传递参数:

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]

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

【讨论】:

    【解决方案2】:

    我会实现一个更明确的方法(未经测试):

    • settings.py中配置可能的类别列表:

      CATEGORIES = ['Arts', 'Business', 'Computers']
      
    • 根据设置定义您的start_urls

      start_urls = ['http://www.dmoz.org/%s' % category for category in settings.CATEGORIES]
      
    • category Field 添加到Item

    • 在spider的parse方法中,根据当前的response.url设置category字段,例如:

      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 中。根据项目类别值选择要导出到的文件。

    希望对您有所帮助。

    【讨论】:

    • 感谢@alecxe 提供有用的示例。这肯定会奏效。但我发布的只是我想测试的演示示例。如果这会成功,我需要在大层次上实现相同的逻辑。起始网址的数量将是数千。那时在设置中列出它们是不可行的。 start_url 也会因蜘蛛而异,所以我无法在设置中定义它们。
    • 感谢@alecxe,在尝试了其他一些选项后,终于按照您的建议解决了问题!
    • 很好的例子,多亏了你,我能够制作一个自定义管道来为 url_list 中的每个单独的 url 输出 JSON。谢谢@alecxe!
    【解决方案3】:

    只要不将其存储在项目本身中,您就无法真正知道凝视 url。以下解决方案应该适合您:

    • 重新定义 make_request_from_url 以发送带有您创建的每个 Request 的起始 URL。您可以将其存储在Requestmeta 属性中。使用以下每个 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

    【讨论】:

    • 感谢@user2016508,我设法在项目中设置了start_url,但现在我无法获得如何访问spider_opened() 方法中项目列表的start_url 字段?
    • 我相信您在上面的问题中所说的解决方案已经足够好了。通过检查项目的起始 url,您可以在 process_item 方法中完美地识别正确的输出文件。
    • 嗨@user2016508,我如何访问spider_opened中的item['start_url']?我将需要在 spider_opened() 中创建基于 item['start_url'] 的新文件,我已设法在 spider 中设置该文件。我不知道如何将它从蜘蛛传递到管道?
    • 嗨@FMoridhara,您正在使用start_urls 创建相应的csv 文件。该解决方案对您的目标还不够好吗?!
    • 我需要在下一行之前设置文件名,其中 XmlItemExporter 需要文件路径。 self.exporter = XmlItemExporter(file) 只有当我可以访问 spider_opened() 中的 item 对象时才能访问 被抓取的特定项目的起始 url,但我认为这不可能,因为蜘蛛刚刚打开并且没有项目那时会在那里。在这种情况下,我怎样才能实现我的目标?
    猜你喜欢
    • 1970-01-01
    • 2013-05-28
    • 1970-01-01
    • 1970-01-01
    • 2018-05-01
    • 2019-06-25
    • 1970-01-01
    • 1970-01-01
    • 2015-06-12
    相关资源
    最近更新 更多