Scrapy爬取下载文件报Missing scheme及IsADirectoryError问题
问题描述
使用Scrapy框架从目标网站下载数据文件时,先后触发两个报错:
- 第一个报错信息如下:
raise ValueError(f'Missing scheme in request url: {self._url}')
ValueError: Missing scheme in request url: h
已确认提取的链接可正常传递到回调函数,无法定位错误触发点,初始实现代码如下:
爬虫主文件代码
import scrapy from nhs.items import DownfilesItem class NhsScapeSpider(scrapy.Spider): name = 'nhs_scape' #allowed_domains = ['nh'] start_urls = ['https://www.england.nhs.uk/statistics/statistical-work-areas/ae-waiting-times-and-activity/ae-attendances-and-emergency-admissions-2021-22/'] custom_settings = { 'USER_AGENT':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36' } def start_requests(self): for url in self.start_urls: yield scrapy.Request( url = url, callback = self.parse ) def parse(self, response): side_panel = response.xpath("//aside[@class='subnav group minimal_nav desktop-only']//ul[@class='children']//li") for years in side_panel: year_links = years.xpath('.//a/@href').get() yield response.follow(year_links, callback = self.download_files) def download_files(self, response): test_files = response.xpath("//article[@class='rich-text']//p") month_files = response.xpath("//article[@class='rich-text']//h3") for files, mn in zip(test_files, month_files): all_files = files.xpath('.//a//@href').getall() all_file_names = files.xpath('.//a//text()').getall() month_year = mn.xpath('.//text()').get() for ind_files,ind_text in zip(all_files, all_file_names): item = DownfilesItem() if '.xls' in ind_files and 'Monthly' in ind_text: item['file_urls'] = ind_files item['original_file_name'] = ind_text yield item elif '.xls' in ind_files and 'Week' in ind_text: item['file_urls'] = ind_files item['original_file_name'] = ind_text yield item
Items.py代码
import scrapy class DownfilesItem(scrapy.Item): # define the fields for your item here like: file_urls = scrapy.Field() original_file_name = scrapy.Field()
Pipelines.py代码
from scrapy.pipelines.files import FilesPipeline class DownfilesPipeline(FilesPipeline): def file_path(self, request, response=None, info=None): file_name: str = request.url.split("/")[1] return file_name
Settings.py配置
ITEM_PIPELINES = {'nhs.pipelines.DownfilesPipeline': 150} FILES_STORE = "Files"
- 调整代码后触发第二个报错:
IsADirectoryError: [Errno 21] Is a directory: 'Files/'
初步判断报错与FILES_STORE = "Files"配置相关,但移除该配置后程序不再抛错,却无法下载任何文件。
错误原因
两个报错分别由两处代码逻辑错误导致,和FILES_STORE配置本身无关:
- Missing scheme报错原因:Scrapy内置的
FilesPipeline要求Item中file_urls字段必须传入列表类型的URL集合,代码中直接将单个字符串URL赋值给file_urls,Scrapy会将字符串作为可迭代对象逐字符遍历,第一个遍历到的字符是h(来自https://开头),将其作为独立URL发起请求时,自然会触发缺少协议头的报错。 - IsADirectoryError报错原因:自定义的
file_path方法逻辑错误,对URL按/分割后取索引为1的元素,对于标准HTTP URLhttps://example.com/path/file.xls,分割后结果为['https:', '', 'example.com', 'path', 'file.xls'],索引1位置是空字符串,相当于返回空路径作为保存文件名,Scrapy会直接尝试向Files/目录本身写入文件,触发目录不可写为文件的错误。
修复方案
按以下步骤修改代码即可解决问题:
- 修正file_urls字段赋值:给
file_urls传入列表格式的URL,同时建议用response.urljoin处理相对路径,确保URL完整带协议头。修改download_files方法中赋值部分:
# 原代码 item['file_urls'] = ind_files 替换为 item['file_urls'] = [response.urljoin(ind_files)]
- 修正Pipelines中文件路径提取逻辑:不要通过固定索引取文件名,正确提取URL最后一段作为文件名,避免返回空路径:
from scrapy.pipelines.files import FilesPipeline from urllib.parse import urlparse import os class DownfilesPipeline(FilesPipeline): def file_path(self, request, response=None, info=None): # 解析URL获取路径部分,提取最后一段作为文件名 parsed_url = urlparse(request.url) file_name = os.path.basename(parsed_url.path) return file_name
- 保留FILES_STORE配置:该配置是FilesPipeline必填项,指定文件保存的根目录,配置为相对路径时会在项目根目录下自动创建对应文件夹,确保运行爬虫的用户对项目目录有读写权限即可。
- (可选)放开
allowed_domains配置,填入正确域名www.england.nhs.uk,避免链接被OffsiteMiddleware过滤。
内容的提问来源于stack exchange,提问作者Emil11
相关产品推荐
相关产品推荐

