scrapy利用FilesPipline實現了文件的下載, 因此如果想要重名文件,只需要重寫這個類
class MyfilesPipeline(FilesPipeline): def file_downloaded(self, response, request, info): """ 文件下載完成以后會調用FilesPipeline 中的file_downloaded方法,因此只需要重寫 file_downloaded方法即可 """ path = self.file_path(request, response=response, info=info) # 文件的名字, 在Response響應內容中,一般都包含file文件的名字,可以根據reponse中的請求頭參數 # 進行提取,比如content-disposition可能就含有文件名或者文件類型, # content-disposition: attachment; filename="科創板上市法律意見書模板.docx";filename*=utf-8' '%E7%A7%91%E5%88%9B%E6%9D%BF%E4%B8%8A%E5%B8%82%E6%B3%95%E5%BE%8B%E6%84%8F%E8%A7%81%E4%B9%A6%E6%A8%A1%E6%9D%BF.docx # 或者也可以自定義文件名字 file_name = self.clean_file_name(response) if file_name: path = file_name buf = BytesIO(response.body) checksum = md5sum(buf) buf.seek(0) self.store.persist_file(path, buf, info) return checksum
如上述代碼重寫 FilesPipeline 中的 file_downloaded 方法, 因為當文件下載完成以后會執行這個方法, 而其中的path則是文件的名字, 其中path 調用了 FilesPipeline 中的 file_path 方法,下面來看這個方法的源碼
# 生成儲存文件名的方法 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() # change to request.url after deprecation media_ext = os.path.splitext(url)[1] # change to request.url after deprecation return 'full/%s%s' % (media_guid, media_ext) # 上述為file_path的源碼,最終返回的是full/hash后的URL , 那么會在指定文件中生成full文件夾,在full文件夾下儲存下載好的文件,文件名為hash候的URL
文件儲存則是調用了 self.store.persist_file(path, buf, info) 方法, 其實是調用了文件的儲存類 FSFilesStore ,下面看一下該類的源碼
class FSFilesStore(object): def __init__(self, basedir): if '://' in basedir: basedir = basedir.split('://', 1)[1] self.basedir = basedir self._mkdir(self.basedir) self.created_directories = defaultdict(set)
# 文件儲存調用了該方法 def persist_file(self, path, buf, info, meta=None, headers=None): absolute_path = self._get_filesystem_path(path) self._mkdir(os.path.dirname(absolute_path), info) with open(absolute_path, 'wb') as f: f.write(buf.getvalue())
如上面源碼所示, scrapy 將傳入的path和setting中的文件儲存路徑進行拼接,最后在生成的路徑中寫入文件, 所以我們只需要修改path就能對文件重新進行命名, 至於是從reponse請求頭中獲取還是其他自定義方式,由情況而定
在完成path的修改以后,還需要添加一下內容
settings.py
# setting 中配置 ITEM_PIPELINES = { # 'scrapy.pipelines.files.FilesPipeline':1, # 文件下載管道 'zhishixinqiu.pipelines.MyfilesPipeline':1 # 自定義的文件下載管道 } DOWNLOAD_TIMEOUT=1800 # 長連接斷開時間 FILES_STORE = '/Users/lin/Desktop/test' # 文件儲存路徑
spider.py文件
# 需要將download_url 對象返回, # 如果是item,則必須在item中含有file_urls字段 item = ZhishixinqiuItem() item["files"] = file item["file_urls"] = [download_url] # file_urls字段value必須是列表對象 yield item # 也可以直接返回字典,但是字典中必須含有鍵"file_urls", 且值必須是URL列表 item = {} item["file_urls"] = [download_url] yield item