使用Scrapy Files Pipeline按年份范围下载IRS税表PDF的实现与故障排查
核心问题根因定位
一、导致“仅访问起始页就被终止”的常见代码错误
- 未正确禁用robots.txt遵循规则
Scrapy默认开启ROBOTSTXT_OBEY = True,IRS站点的robots.txt对列表页爬取的限制较严格,若未在settings.py中显式写入ROBOTSTXT_OBEY = False(哪怕命令行临时修改也容易失效),爬虫会在抓取首页解析链接时,被robots规则判定为无权访问后续列表、详情页,直接终止所有请求队列,完全匹配你描述的运行现象。 - 解析逻辑空转
部分代码写在解析起始页的方法中未正确提取列表条目、翻页链接,返回空的Request集合,表面日志看起来是被robots拦截终止,实际是没有可继续执行的任务队列。 - Files Pipeline配置失效
未在ITEM_PIPELINES中正确启用文件下载管道,哪怕抓到了PDF链接,也不会触发实际下载流程,日志表现为爬完初始页就无后续动作。
二、导致无法实现需求的逻辑缺陷
- 未重写FilesPipeline的路径生成规则:Scrapy默认会将下载的文件按MD5哈希值直接存在
FILES_STORE根目录下,完全无法满足“按表单名建子目录、自定义PDF文件名”的要求。 - 未做年份范围过滤:解析时无针对性判断,会把所有年份的税表都爬下来,不符合指定范围下载的需求。
可落地的完整实现方案
1. settings.py 必改配置
直接在配置文件中写入以下参数,避免依赖默认值导致配置失效:
# 禁用robots遵循规则 ROBOTSTXT_OBEY = False # 配置真实浏览器UA,避免被站点WAF拦截 USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" # 配置下载延迟,降低服务器压力 DOWNLOAD_DELAY = 1 # 配置文件存储根目录 FILES_STORE = "./irs_tax_forms" # 替换为自定义管道类,见下文代码 ITEM_PIPELINES = { "pdf.CustomIRSFilesPipeline": 1, } # 对应Item的字段映射,匹配FilesPipeline的参数要求 FILES_URLS_FIELD = "file_urls" FILES_RESULT_FIELD = "files"
2. 自定义FilesPipeline 实现目录与文件名规则
注意:不要直接调用Scrapy自带的默认FilesPipeline,默认路径规则是将所有下载文件按内容哈希值存在根目录,完全无法匹配你要求的目录结构与文件命名格式。
在pdf.py中重写FilesPipeline的路径生成方法,完全匹配存储要求:
from scrapy.pipelines.files import FilesPipeline from scrapy import Item, Field # 定义爬取用的Item结构 class TaxFormItem(Item): form_name = Field() # 存储表单名称,比如Form W-2 form_year = Field() # 存储对应年份,比如2020 file_urls = Field() # 存储PDF下载链接 class CustomIRSFilesPipeline(FilesPipeline): def file_path(self, request, response=None, info=None, *, item=None): # 过滤表单名中的Windows/macOS非法文件名字符 safe_form_name = item["form_name"].replace("/", "_").replace("\\", "_").strip() # 生成目标路径:表单名目录/表单名 - 年份.pdf return f"{safe_form_name}/{item['form_name']} - {item['form_year']}.pdf"
3. Spider 核心爬取逻辑实现
在pdf.py中编写Spider类,实现列表解析、年份过滤、翻页抓取逻辑:
import scrapy class IRSTaxFormSpider(scrapy.Spider): name = "irs_tax_form" start_urls = ["https://apps.irs.gov/app/picklist/list/priorFormPublication.html"] # 配置需要爬取的年份范围,可按需修改 TARGET_YEARS = set(range(2018, 2021)) def parse(self, response): # 遍历当前页所有税表单条目 for row in response.xpath("//table[@class='picklist-dataTable']/tr")[1:]: # 提取条目对应的表单名 form_name = row.xpath("./td[1]/a/text()").get() # 提取条目对应的年份 form_year = int(row.xpath("./td[2]/span/text()").get().strip()) # 提取PDF下载链接 pdf_url = row.xpath("./td[1]/a/@href").get() # 只保留目标年份范围内的条目 if form_year in self.TARGET_YEARS: yield TaxFormItem( form_name=form_name, form_year=form_year, file_urls=[response.urljoin(pdf_url)] ) # 提取翻页链接,递归爬取后续列表页 next_page = response.xpath("//a[contains(text(), 'Next')]/@href").get() if next_page: yield response.follow(next_page, callback=self.parse)
关键注意事项
- 解析XPath选择器可根据实际页面结构微调,避免出现条目错位(比如把年份和PDF链接对应错)
- 运行时直接执行
scrapy runspider pdf.py即可,无需额外启动配置 - 可自行增加URL去重逻辑,避免同一个PDF被重复提交下载
内容的提问来源于stack exchange,提问作者alivingpixel
相关产品推荐
相关产品推荐

