crawlspider需手动兜底漏页与死循环,rules中allow/deny为正则全串匹配而非子串判断,应锚定边界或优先deny;linkextractor用restrict_xpaths定位dom区域更可靠;须设allowed_domains、深度控制和自定义去重。

CrawlSpider 不是“全自动全站爬虫”,它只按你定义的规则走,漏页或死循环都得自己兜底。
rules 里 allow 和 deny 的正则匹配逻辑容易误判
很多人以为 allow 是“只要 URL 包含某字符串就抓”,其实它是对整个 URL 字符串做 re.search,不是 in 判断。比如写 allow=r'news',会匹配 https://example.com/newsletter,也可能意外抓到带 news 的无关资源(如 /static/js/news-banner.js)。
实操建议:
- 用
^和$锚定边界,例如allow=r'^/news/\d+\.html$' - 优先用
deny排除已知干扰路径:deny=(r'/login', r'/api/', r'\.pdf$') - 调试时打开
LOG_LEVEL='DEBUG',观察CrawlSpider输出的 “Filtered offsite request” 或 “Ignoring link” 日志,确认哪些链接被规则放过或拦下
LinkExtractor 的 restrict_xpaths 比 allow 更可靠
仅靠 URL 正则容易过宽或过窄;真正可控的方式是先定位页面中“应该被跟进的链接”所在的 DOM 区域,再提取。比如新闻列表页,所有文章链接都在 <div class="list"> 下,那就用 <code>restrict_xpaths='//div[@class="list"]//a',比写一堆 URL 正则更稳。
常见误区:
-
restrict_xpaths写成//a—— 这会提取页脚、导航栏、广告里的所有链接,极易爬偏 - 忽略
tags和attrs参数,默认只提<a href></a>,但有些站点用<area href>或<link data-href>,需显式指定 - 没设
unique=True(默认为 True),但若页面 JS 动态插入重复链接,仍可能重复请求
避免无限爬取的关键:域名限制 + 深度控制 + 去重机制
CrawlSpider 默认不限制爬取深度,也不校验是否跨站,一不留神就会从目标站跳到友链站、甚至爬完整个互联网。
必须加的防护项:
- 在 spider 类中显式声明
allowed_domains = ['example.com'],注意不带http://,且子域名要单独列('blog.example.com'不会被'example.com'覆盖) - 用
Rule的process_request回调注入请求 meta,记录当前深度:meta={'depth': response.meta.get('depth', 0) + 1},然后在parse_start_url或回调中检查if response.meta.get('depth', 0) > 5: return - Scrapy 自带
RFPDupeFilter基于 request fingerprint 去重,但若页面有分页参数(如?page=1&sort=date)而内容实际相同,就得自定义request_fingerprint函数,忽略无意义 query 参数
真实场景下,CrawlSpider 往往只是起点,不是终点
它适合结构清晰、链接规律强的旧式 CMS 站点(如 WordPress、Drupal 默认主题)。但遇到以下情况,硬套 CrawlSpider 反而增加维护成本:
- 前端路由由 Vue/React 管理,关键链接不在 HTML 源码里(得换
Splash或Playwright) - 列表页和详情页共用一个 API 接口,返回 JSON 数据(这时直接调 API 比解析 HTML 链接高效得多)
- 反爬严格,需要登录态、Referer、动态 token ——
CrawlSpider的规则层不处理这些,得退回到Spider手动发请求 + 维护 session
真正难的从来不是“怎么写出 rules”,而是判断哪些链接值得跟、哪些响应该丢弃、哪些字段必须补全 —— 这些逻辑藏在 parse 回调里,而不是 CrawlSpider 的骨架中。











