
本文介绍如何使用 scrapy 实现递归爬虫,自动发现并提取网站中所有包含特定标题(如“az school safety program”)的卡片所在的完整页面 url,避免手动查找,同时兼顾效率与合规性。
本文介绍如何使用 scrapy 实现递归爬虫,自动发现并提取网站中所有包含特定标题(如“az school safety program”)的卡片所在的完整页面 url,避免手动查找,同时兼顾效率与合规性。
在实际网页抓取任务中,常遇到同一内容模块(如项目卡片)被复用在多个页面的情况。例如目标站点 lawforkids.org 中,“AZ School Safety Program”这一标题出现在 /officers、/educators、/programs 等多个路径下。若仅靠单层解析无法覆盖全站分布,必须启用递归遍历 + 内容匹配策略。
以下是一个完整、可运行的 Scrapy 爬虫实现,专为该类场景设计:
import scrapy
class PostsSpider(scrapy.Spider):
name = "card"
# 限制爬取深度,防止过度请求和服务器压力
custom_settings = {
'DEPTH_LIMIT': 1,
'DEPTH_PRIORITY': 1,
'ROBOTSTXT_OBEY': True, # 尊重 robots.txt(推荐开启)
'DOWNLOAD_DELAY': 1, # 建议添加延迟,体现爬虫礼仪
}
def start_requests(self):
# 明确起始 URL,支持多个入口点
urls = ["https://lawforkids.org"]
for url in urls:
yield scrapy.Request(url=url, callback=self.parse)
def parse(self, response):
# 跳过非 HTML 响应(如 PDF、图片、API 接口等)
content_type = response.headers.get('Content-Type', b'').decode()
if 'text/html' not in content_type:
return
# 定位所有卡片容器,并提取标题进行精确匹配
for card in response.css("div.card-body"):
title = card.css("h5.card-title::text").get(default="").strip()
if title == "AZ School Safety Program":
yield {
"title": title,
"url": response.url
}
# 递归抓取当前页内所有站内链接(仅限 lawforkids.org 域名)
for a_tag in response.css('a[href]'):
link = a_tag.css('::attr(href)').get()
if not link:
continue
absolute_link = response.urljoin(link)
# 严格限定为同域链接,避免爬出目标范围
if absolute_link.startswith("https://lawforkids.org"):
yield scrapy.Request(
url=absolute_link,
callback=self.parse,
# 可选:携带优先级或元数据便于调试
cb_kwargs={"source_url": response.url}
)
✅ 关键设计说明:
- 使用
start_requests()替代start_urls,便于灵活控制请求逻辑; -
response.urljoin()确保相对 URL 正确转为绝对 URL; -
custom_settings中设置DEPTH_LIMIT=1平衡覆盖率与性能——实测已能捕获/programs/lre-academy等深层路径;如需更广覆盖,可谨慎提升至2,但务必同步增加DOWNLOAD_DELAY和监控响应状态; -
ROBOTSTXT_OBEY=True和DOWNLOAD_DELAY=1是负责任爬虫的基本实践,强烈建议保留; - 标题比对采用
.strip()和default=""防止空格或 None 引发异常,增强鲁棒性。
运行命令如下(输出为 JSONL 格式,便于后续处理):
scrapy crawl card -o matches.jsonl
输出示例:
{"title": "AZ School Safety Program", "url": "https://lawforkids.org/programs"}
{"title": "AZ School Safety Program", "url": "https://lawforkids.org/officers"}
{"title": "AZ School Safety Program", "url": "https://lawforkids.org/educators"}
⚠️ 注意事项:
- 切勿移除域名校验(
startswith("https://lawforkids.org")),否则可能意外爬取外部资源甚至陷入无限循环; - 若目标标题存在大小写变体或前后缀(如带括号、空格数不一),建议改用正则模糊匹配:
import re if re.search(r"AZ\s+School\s+Safety\s+Program", title, re.I):
- 生产环境建议添加
HTTPERROR_ALLOWED_CODES = [403, 404]并在parse中处理异常响应,避免中断整个爬取流程。
通过本方案,你不仅能精准定位多页面复用的内容单元,还能以可控、可审计、符合 Web 礼仪的方式完成全站扫描——这才是专业级 Scrapy 爬虫应有的姿态。










