如何修改Scrapy源码实现按URL路径导出数据至对应文件
嘿,我来帮你搞定这个需求!要实现按URL路径分文件保存Scrapy爬取的数据,其实有两种可行思路:一种是直接修改Scrapy的源码,另一种是用自定义Pipeline(更推荐,不用改动框架源码,灵活性更高)。我给你详细拆解每种方法的操作步骤:
方法一:修改Scrapy源码实现分文件导出
如果你确实需要修改Scrapy源码,核心是调整它的Feed导出逻辑,让每个Item根据对应的URL匹配到不同的输出文件。以下是基于Scrapy 2.x版本的修改步骤:
1. 定位核心文件
找到Scrapy安装目录下的scrapy/extensions/feedexport.py,这个文件负责管理Feed导出的核心逻辑。
2. 修改FeedExporterExtension类
初始化exporter缓存字典
在类的__init__方法里添加一个字典,用来缓存不同文件路径对应的导出器实例,避免重复创建:
def __init__(self, crawler): self.crawler = crawler self.settings = crawler.settings self.exporters = {} # 新增:缓存每个文件对应的exporter和文件句柄 # 保留原有代码...
重写process_item方法,实现动态文件路径
原来的逻辑是所有Item导出到同一个文件,现在我们要根据Item携带的URL生成目标路径,并匹配对应的导出器:
def process_item(self, item, spider): # 从Item中获取页面URL(需要你的Spider把URL存入Item) item_url = item.get('url') if not item_url: # 如果没有URL,可选择跳过或导出到默认文件,这里按需求调整 return item # 解析URL,提取路径片段 from urllib.parse import urlparse parsed_url = urlparse(item_url) path_parts = parsed_url.path.strip('/').split('/') # 生成目标路径:比如http://example/big/ppp/a → d:/ppp/a.csv dir_name = path_parts[-2] if len(path_parts) >= 2 else 'default' file_name = f"{path_parts[-1]}.csv" output_path = os.path.join('d:/', dir_name, file_name) # 确保目标目录存在 os.makedirs(os.path.dirname(output_path), exist_ok=True) # 获取或创建对应文件的导出器 if output_path not in self.exporters: from scrapy.exporters import CsvItemExporter file = open(output_path, 'ab') exporter = CsvItemExporter(file) exporter.start_exporting() self.exporters[output_path] = (exporter, file) else: exporter, file = self.exporters[output_path] # 导出当前Item exporter.export_item(item) return item
重写spider_closed方法,关闭所有资源
原来的方法只关闭一个导出器,现在要遍历所有缓存的导出器和文件句柄:
def spider_closed(self, spider, reason): for exporter, file in self.exporters.values(): exporter.finish_exporting() file.close() self.exporters.clear()
方法二:自定义Pipeline(更推荐,无需修改源码)
其实完全没必要动Scrapy源码,自定义Pipeline更灵活,还不会影响其他Scrapy项目的使用:
1. 在项目中创建自定义Pipeline
在你的Scrapy项目的pipelines.py文件中添加以下代码:
import os from urllib.parse import urlparse, quote from scrapy.exporters import CsvItemExporter class PerUrlCsvPipeline: def __init__(self): # 缓存每个文件对应的导出器和文件句柄 self.exporters = {} def open_spider(self, spider): # 确保根目录存在 os.makedirs('d:/', exist_ok=True) def process_item(self, item, spider): # 从Item中获取页面URL item_url = item.get('url') if not item_url: return item # 解析URL路径 parsed_url = urlparse(item_url) path_parts = parsed_url.path.strip('/').split('/') # 处理特殊字符(比如中文、空格),避免文件名非法 dir_name = quote(path_parts[-2]) if len(path_parts) >=2 else 'default' file_name = f"{quote(path_parts[-1])}.csv" output_path = os.path.join('d:/', dir_name, file_name) # 创建目录 os.makedirs(os.path.dirname(output_path), exist_ok=True) # 获取或创建导出器 if output_path not in self.exporters: file = open(output_path, 'ab') exporter = CsvItemExporter(file) exporter.start_exporting() self.exporters[output_path] = (exporter, file) exporter, file = self.exporters[output_path] # 导出Item exporter.export_item(item) return item def close_spider(self, spider): # 关闭所有导出器和文件 for exporter, file in self.exporters.values(): exporter.finish_exporting() file.close()
2. 启用自定义Pipeline
在项目的settings.py中修改ITEM_PIPELINES配置,启用刚才创建的Pipeline:
ITEM_PIPELINES = { # 替换成你的项目名称 'your_project_name.pipelines.PerUrlCsvPipeline': 300, }
关键前置操作
不管用哪种方法,都需要你的Spider在生成Item时,把当前页面的URL存入Item中,比如在parse方法里:
def parse(self, response): item = YourCustomItem() # 填充Item的其他字段... item['url'] = response.url # 必须添加这一行,把页面URL传入Item yield item
内容的提问来源于stack exchange,提问作者chenshitao
相关产品推荐
相关产品推荐

