Scrapy

2017/01/02

定义: Scrapy, An open source and collaborative framework for extracting the data you need from websites. In a fast, simple, yet extensible way. 请注意上面的定义,即“什么是Scrapy”,或者说“Scrapy是什么”,这是最最重要的。

关于如何用Scrapy写爬虫,这里不做叙述,官网有详细的中英文文档

Scrapy中Item和Pipeline的概念,将资源变为可自由定义、扩展的元素,很利于写出健壮、可维护的爬虫项目。

以下是我的一份爬取网络生效资源的Scrapy项目代码,思想比较简单,为了写出这个项目,从零开始接触Scrapy到完成内容爬取花了半个下午的时间。

在items.py中定义Item

class SeItem(scrapy.Item):
    name = scrapy.Field()
    # 以下字段用于后期的文件下载
    files = scrapy.Field()
    file_urls = scrapy.Field()

在pipelines.py中定义Pipeline

class SePipeline(FilesPipeline):

    def file_path(self, request, response=None, info=None):
        # start of deprecation warning block (can be removed in the future)
        def _warn():
            from scrapy.exceptions import ScrapyDeprecationWarning
            import warnings
            warnings.warn('FilesPipeline.file_key(url) method is deprecated, please use '
                          'file_path(request, response=None, info=None) instead',
                          category=ScrapyDeprecationWarning, stacklevel=1)

        # check if called from file_key with url as first argument
        if not isinstance(request, Request):
            _warn()
            url = request
        else:
            url = request.url

        # detect if file_key() method has been overridden
        if not hasattr(self.file_key, '_base'):
            _warn()
            return self.file_key(url)
        # end of deprecation warning block

        media_guid = hashlib.sha1(to_bytes(url)).hexdigest()
        media_ext = os.path.splitext(url)[1]

        return 'full/%s%s' % (request.meta['name'][0].encode('utf8'), media_ext)
        # return 'full/%s%s' % (media_guid, media_ext)

    def get_media_requests(self, item, info):
        for file_url in item['file_urls']:
            yield Request(file_url.strip(), None, 'GET', None, None, None,
                          {'name': item['name']})

    def item_completed(self, results, item, info):
        if isinstance(item, dict) or self.files_result_field in item.fields:
            item[self.files_result_field] = [x for ok, x in results if ok]
        return item

比较麻烦的一点在于,下载后的文件需要我根据网页内容指定。

即:程序根据网页上下文推断某链接下载完毕后将要存储的文件名

由于Pipeline的file_path方法没法接收Item参数,所以我在Request参数的meta中加入了文件存储名,可以在上面的代码中看出。其他的逻辑就很简单了。

代码在Github上,不妨Review一下。