在Scrapy中为每个start_urls列表中的URL单独输出文件

8 投票
3 回答
3757 浏览
提问于 2025-04-18 07:35

我想为我在爬虫的开始网址(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

在这里,我试图找到每个抓取项目的来源网址(referer url),这个网址是在开始网址列表中。如果找到了来源网址,就可以用这个网址来创建文件名。

但问题是,我不知道如何在spider_opened()方法中访问响应对象。如果我能在那儿访问到它,就可以根据这个网址来创建文件了。

有没有人能帮我找到解决办法?提前谢谢!

3 个回答

0

这是我在项目中实现的方式,没设置项目的类别:

你可以在命令行中这样传递参数:

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

只要你不把起始网址存储在项目本身里,你就无法真正知道这个起始网址。下面的解决方案应该对你有帮助:

  • 重新定义一下 make_request_from_url 方法,让它在每次发送 Request 时都带上起始网址。你可以把这个网址存储在 Requestmeta 属性里。这样在后续的每个 Request 中就可以跳过这个起始网址。

  • 一旦你决定把这个元素传递给管道,就从 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

6

我会用一种更明确的方法来实现这个功能(虽然没测试过):

  • settings.py 文件中配置可能的分类列表:

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

    start_urls = ['http://www.dmoz.org/%s' % category for category in settings.CATEGORIES]
    
  • Item 类中添加 category 字段:

  • 在爬虫的解析方法中,根据当前的 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 中。根据项目的分类值选择导出到哪个文件。

希望这对你有帮助。

撰写回答