做爬虫这几年,有一个话题每次跟同行聊起来都会出现分歧——robots.txt到底要不要遵守。有人觉得它就是网站挂在那儿的一个摆设,有人直接不闻不问,等到IP被拉黑或者收到对方发来的警告邮件才后悔。我自己也经历过从“无视协议”到“认真对待”的过程。今天这篇就想跟你好好聊聊robots.txt,也就是大家常说的爬虫君子协议,把它的语法、原理、应用场景,以及实战中怎么用Python来处理,一次性讲清楚。
先给新手朋友一个定位:robots.txt是网站管理者放在服务器根目录下的一个文本文件,用来告诉网络爬虫“哪些路径你可以抓,哪些路径你不能碰”。它不是法律,不依赖技术强制手段,全靠爬虫方自觉遵守,所以叫“君子协议”。这篇文章适合刚入门爬虫、想系统理解协议规则的开发者,也适合已经写了很久爬虫、但一直没认真看过robots.txt的“老油条”。读完之后,你会知道如何在代码里正确解析robots.txt、哪些细节最容易踩坑、哪些场景下“不遵守”会给自己惹麻烦,以及如何从搜索引擎和网站运维两个视角反向理解这个文件。
1. robots.txt到底在管什么?先看它是怎么来的
1.1 一件小事引发的“网络公约”
1994年,搜索引擎技术还处在蛮荒时代。当时很多搜索引擎蜘蛛在抓取网页时不讲章法,经常把服务器压垮,甚至抓走一些不该被抓的隐私目录。为了解决这个矛盾,荷兰程序员Martijn Koster提出了一个简单的约定:网站管理员可以在服务器根目录放一个文件,蜘蛛来抓之前先读这个文件,按里面的规则决定抓不抓。
这个看似朴素的方案,后来成了互联网事实上的行业标准。1996年,它被正式纳入RFC 1945(HTTP/1.0规范)的附录中。虽然它从头到尾都只是“建议性”的,但你去看Google、Bing、百度这些搜索引擎的官方文档,都会要求开发者尊重robots.txt的规则。换句话说,如果你写的爬虫不遵守robots.txt,搜索引擎的爬虫可能会遵守,但你自己写的数据采集脚本没人管——这就是君子协议的真正含义:它约束不了代码,只能约束写代码的人。
1.2 为什么叫“君子协议”而不是“强制协议”
技术上,robots.txt没有任何强制机制。服务器只是把它当普通静态文件返回,爬虫如果完全不读它,网站也没有办法直接用这个文件拦截请求。所以,它的“效力”完全取决于爬虫方的自觉。
但也正因为如此,它在某种程度上成了衡量一个爬虫开发者职业素养的试金石。你可以想象一个场景:某天你写了一个爬虫,把某个小型电商网站的商品详情页全部抓下来了,服务器压力暴涨,对方运维一查日志,发现User-Agent是你自定义的Python脚本。这时候对方不会直接报警,而是先发一封邮件给你,附上robots.txt的内容,提醒你注意抓取频率和路径限制。大多数时候,事情到这一步就平息了。但如果你无视提醒,继续高强度抓取,那就可能面临IP封禁、法律函件等后果。
我在实际项目中见过不止一次这样的纠纷。2019年,某公司因为爬取另一家平台的数据,对方直接拿出robots.txt作为证据起诉。虽然robots.txt本身不是法律文件,但在司法实践中,它常被视为“网站经营者对爬虫行为的明确授权边界”。这个信号值得每一个爬虫开发者放在心上。
1.3 两种爬虫视角:搜索引擎蜘蛛 vs 数据采集脚本
robots.txt在设计时主要面向搜索引擎蜘蛛,比如Googlebot、Baiduspider。这些蜘蛛是为了收录网页而存在的,它们需要遵守网站管理员的意愿,否则网站管理员可以屏蔽搜索引擎的抓取,导致网站排名下降。
但今天写爬虫的开发者,大部分不是在做搜索引擎,而是在做数据采集——比如抓商品信息、新闻内容、行业数据、社交平台公开信息等。对于这类脚本,robots.txt同样值得关注。理由不只是合规,更实际的原因在于:如果一个网站特意在robots.txt里屏蔽了某个目录,通常意味着那里的内容不适合被公开抓取,强行抓取要么会遇到更强的反爬策略,要么会带来法律风险。
所以我的建议是:无论你写的是搜索引擎爬虫还是数据分析脚本,都应该在项目启动时先把目标网站的robots.txt拉出来看一遍。这是一种习惯,也是一种保护自己的方式。
2. robots.txt语法精讲:规则不多,但细节很要命
2.1 四条常用指令
robots.txt的语法非常简洁,核心指令只有几个。先看一个最常见的例子:
User-agent: * Disallow: /wp-admin/ Allow: /wp-admin/admin-ajax.php Sitemap: https://example.com/sitemap.xmlUser-agent:指定这条规则适用于哪个爬虫。*代表所有爬虫。Disallow:禁止访问的路径。可以写完整路径,也可以写目录前缀。Allow:在Disallow限制内,允许放行的路径。注意,Allow在早期协议里没有,是后来补充的,用来实现更精细的控制。Sitemap:声明网站的sitemap地址,方便爬虫更快发现内容。这不是用来限制访问的,而是主动引导。
文件里可以有多组User-agent块,每个块内可以有多条Disallow和Allow。匹配规则是按顺序从上到下,谁的规则更具体谁优先。
2.2 通配符与路径匹配规则
robots.txt还支持两个通配符:*匹配任意字符序列,$匹配行尾。不过,通配符的支持程度在不同爬虫间有差异,Google是明确支持的,百度和Bing有些版本支持不完整。所以,尽量只用标准功能,不要依赖通配符。
路径匹配的规则有几个关键点:
- 大小写敏感。
/User和/user是两个完全不同的路径。在写规则时要注意和实际URL保持一致。 - 根路径是“/”。
Disallow: /表示禁止爬取整个站。 - 空路径表示允许所有。
Disallow:(后面没有内容)等于没有限制。 - 路径匹配是前缀匹配。
Disallow: /private会同时阻止/private、/private.html、/private/pic.jpg等一切以/private开头的URL。
这里有一个初学者容易犯的错误:Disallow: /private/和Disallow: /private看似差别不大,实际影响范围完全不同。前者只屏蔽/private/目录下的路径,后者覆盖所有以/private开头的URL,包括/private_fund这种页面。我刚入行时就在这上面吃过亏,站长把/admin/写成了/admin,结果整个/admin_area都被搜索引擎拒收了。
2.3 一个容易被忽略的Crawl-delay指令
Crawl-delay是用来告诉爬虫每次请求之间需要间隔多少秒的指令。它不在最初的协议版本里,是后来Yandex、Bing等搜索引擎带起来的。
User-agent: bingbot Crawl-delay: 10这个指令对搜索引擎爬虫比较有意义,对于普通数据采集脚本参考价值很大——你可以从网站的Crawl-delay看出对方对抓取频率的容忍度。如果某个网站在robots.txt里写了Crawl-delay: 10,而你依然用每秒钟5个请求的速度去抓,即便没有触发反爬,服务器日志里的异常流量也足够让对方运维注意到你。
2.4 实战案例:从真实网站看robots.txt的设计思路
我以自己做过的一个项目为例。那时要采集某新闻门户的科技频道文章,对方的robots.txt长这样(简化版):
User-agent: * Disallow: /cpro/ Disallow: /baidu/ Disallow: /static/ Disallow: /special/ User-agent: Baiduspider Allow: / User-agent: Googlebot Allow: /这个文件清楚地告诉爬虫:普通爬虫不允许抓取广告相关路径(/cpro/)、百度相关路径(/baidu/)、静态资源目录(/static/)和专题聚合页(/special/),但百度和Google的蜘蛛则可以抓全站。这就是典型的“搜索引擎友好型”配置:网站希望被搜索引擎收录,但不希望数据采集脚本乱抓。
这种情况下,采集新闻正文时只抓列表页和详情页,跳过那些特殊目录,既能完成任务,也相对安全。反过来,如果无视robots.txt去硬抓/static/下的内容,大概率得到的不是你想找的新闻数据,而是一堆CSS和JS文件,白白消耗带宽。
3. Python爬虫实操:在代码里正确解析robots.txt
3.1 用urllib.robotparser一行搞定解析
Python标准库自带robots.txt解析模块urllib.robotparser,使用起来非常简单。来看一段完整的示例代码:
from urllib.robotparser import RobotFileParser from urllib.request import urlopen rp = RobotFileParser() rp.set_url("https://example.com/robots.txt") rp.read() user_agent = "MyDataSpider/1.0" url = "https://example.com/news/today" if rp.can_fetch(user_agent, url): print("允许抓取:", url) else: print("禁止抓取:", url)核心方法就两个:
rp.read():从网络加载并解析robots.txt。rp.can_fetch(user_agent, url):传入你的爬虫名称和完整URL,返回True或False。
如果你的爬虫名称比较特殊,比如自定义了一个很长的UA,can_fetch会按照robots.txt里的规则帮你匹配最合适的User-agent块。这个模块在底层已经处理好了匹配优先级和通配符逻辑,比你自己手写字符串匹配可靠得多。
3.2 注意:robots.txt可能不存在
用RobotFileParser时有一个常见的坑:如果目标网站根本没有robots.txt,rp.read()不会报错,但rp.can_fetch()会返回什么?答案是True——也就是允许抓取所有内容。
这个逻辑其实是合理的:没有声明限制,就视为没有限制。但实战里,很多网站的robots.txt不存在,并不意味着网站欢迎你随便抓。它只代表网站管理员没有配置这个文件,具体能不能抓、能抓多快,你得结合网站规模、内容敏感度和反爬策略来判断。
所以,我的习惯是先请求robots.txt,如果返回404,就标记为“无robots.txt”,再去观察网站的反爬策略。绝不因为can_fetch()返回True就毫无顾忌地高并发抓取。
3.3 加一层缓存与离线解析
RobotFileParser每次调用read()都会发起一次HTTP请求。如果你在爬虫里每个URL都调用一次can_fetch,等于每个URL之前都额外多一次请求,耗时翻倍。
更好的做法是:在程序启动时读取一次robots.txt,把解析结果缓存到内存里。示例:
import requests from urllib.robotparser import RobotFileParser from urllib.parse import urlparse class RobotsCache: def __init__(self): self._cache = {} def get_parser(self, base_url): if base_url not in self._cache: rp = RobotFileParser() rp.set_url(base_url.rstrip("/") + "/robots.txt") try: rp.read() except Exception: rp = None self._cache[base_url] = rp return self._cache[base_url] def can_fetch(self, url, user_agent): parsed = urlparse(url) base_url = f"{parsed.scheme}://{parsed.netloc}" rp = self.get_parser(base_url) if rp is None: return True return rp.can_fetch(user_agent, url)这样设计后,整个爬虫生命周期内,每个网站最多只请求一次robots.txt。如果网站的robots.txt内容不频繁变动,这种做法能显著减少无效请求。当然,如果你爬的是大规模网站集群,还可以把解析结果存入Redis,让多个爬虫节点共享缓存。
3.4 分布式爬虫中的robots.txt处理
分布式爬虫和多线程爬虫里,robots.txt的处理需要统一规划。比如你用Scrapy + Redis搭建了一个分布式采集系统,几十台机器同时抓同一个网站,所有节点都各自请求一次robots.txt,不仅浪费流量,还可能因为高并发触发反爬。
我的做法是:把robots.txt的解析结果放到Redis里,用域名作为key,解析后的规则列表作为value。每个节点启动时先检查Redis里有不有,没有再请求并写入,设置半天或一天过期。这样所有节点共享同一份规则,还能动态更新。
另外,如果项目里有多个不同的爬虫任务(比如新闻爬虫和商品爬虫),可以考虑给每个任务设置不同的User-Agent,再根据robots.txt里对应的规则分别判断。很多网站的robots.txt对*限制严格,但对指定搜索引擎的UA比较宽松。虽然我们不伪装成搜索引擎,但自定义一个有辨识度的UA本身没问题。
3.5 一个容易忽略的细节:User-Agent要一致
RobotFileParser的匹配逻辑是纯字符串级别的,它只看你传入的user_agent参数,不会去检查真实请求的User-Agent请求头。也就是说,下面这段代码可以正常运行:
rp.can_fetch("Googlebot", url) # 返回True requests.get(url, headers={"User-Agent": "Mozilla/5.0"}) # 实际用浏览器UA但问题来了:如果你用Googlebot去匹配robots.txt,然后实际请求时又用别的UA,这在语义上是自欺欺人。更重要的是,很多网站的反爬系统会比对请求的UA和robots.txt的规则。你明明用Googlebot的身份“骗”过了robots.txt检查,实际抓取时又暴露了非搜索引擎的UA,这种行为很容易被识别为异常流量。
正确做法是:定义一个统一的UA,比如MyDataSpider/1.0 (+https://myproject.example.com),在can_fetch检查和实际请求中都使用这个UA。保持身份一致,是做数据采集最基本的礼仪。
4. 哪些场景可以忽略robots.txt?哪些必须遵守?边界要拎清
4.1 搜索引擎与数字化存档的特殊性
有一些场景,业界普遍认为robots.txt的约束力需要重新审视。最典型的是Internet Archive(互联网档案馆)这类非营利性存档项目,它抓取网页是为了历史记录和学术研究。很多网站为了存档目的会主动放行这类爬虫,或者在robots.txt里给它们开绿灯。
但如果你做的不是存档,而是商业项目的数据采集,就不建议拿“非营利”当挡箭牌。商业用途的数据采集,务必先确认robots.txt的限制,再评估法律风险。
4.2 公开API接口与公开数据页面
如果你的爬虫目标不是HTML页面,而是网站自己提供的公开API接口,robots.txt的限制通常不适用于API路径。因为API接口是程序化访问入口,网站管理者如果不想让第三方调用,通常会通过API鉴权、频率限制等机制来控制,而不是靠robots.txt。
例如,GitHub的robots.txt里限制了/search/路径的抓取,但它的公共REST API是允许授权后访问的。这种情况下,抓取API数据时看robots.txt意义不大,更重要的是遵守API的Terms of Service和频率限制。
对于公开数据页面,我的原则是:robots.txt限制了不抓,没限制但明显是“页面数据”的,先低频率试探,观察服务器响应和网站态度,再决定是否加量。这里说的低频率,是指每秒不超过1个请求,甚至每2秒1个请求。
4.3 强反爬网站:认真读robots.txt反而是情报收集
有些网站的反爬措施非常强,比如通过JS渲染验证、滑块验证、指纹识别等机制来拦截爬虫。这类网站往往在robots.txt里写得很模糊,甚至完全不写。但越是这样,越值得你把robots.txt当作“情报”来分析。
举个例子,某招聘网站之前把/jobs/目录设置为Disallow,但很多爬虫脚本依然去抓取职位列表。网站的反爬系统把这些流量识别出来,做了IP段封禁。后来我仔细看它的robots.txt,发现里面还特意标注了Disallow: /account/——这是在暗示账号相关页面是敏感区域。这类线索比你去试错找出敏感路径要高效得多。
所以,不管目标网站反爬强不强,启动爬虫前花一分钟看robots.txt,永远不亏。
4.4 法律风险与伦理判断
严格来说,robots.txt并不是法律,但在司法实践中确实有参考价值。爬虫相关的法律纠纷里,法院经常关注一个核心问题:爬虫方是否超出了网站明示的访问许可范围。robots.txt就是最典型的“明示许可范围”。
在中国,涉及数据爬取的法律条款主要聚焦在《反不正当竞争法》和《个人信息保护法》上。如果爬虫抓取了非公开数据、用户个人信息、或者绕过技术保护措施,即便robots.txt没有明确禁止,也可能构成违法。反过来,robots.txt允许抓取的公开数据,也未必就是绝对安全的——如果抓取频率过高导致服务器瘫痪,可能涉及破坏计算机信息系统的问题。
我的建议很简单:robots.txt只代表网站管理者的基础意愿,你可以把它当作参考下限,而不是安全上限。更稳妥的边界是“不抓个人隐私、不抓需登录才能看的内容、不绕过技术保护措施、不超高频请求”。这四条守住了,大部分问题都能避免。
5. 常见问题与排查技巧实录
5.1 问题速查表
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
can_fetch返回True,但网站依然封IP | 反爬系统按并发和频率判断,不看robots.txt | 降低请求频率,增加随机延时,模拟真实用户行为 |
can_fetch返回False,但页面实际能访问 | robots.txt规则匹配到了前缀,批量屏蔽了目录 | 换更具体的URL路径,或检查是否触及了/整站屏蔽 |
rp.read()报超时 | 网站屏蔽了某些UA或IP段的robots.txt访问 | 用浏览器UA重试,或从缓存中读取历史数据 |
| 抓取结果里混入大量重复页面 | 未正确处理分页参数,导致URL无限膨胀 | 在代码中加入URL去重,限制最大爬取深度 |
| 刚启动爬虫就被封 | User-Agent过于脚本化,或请求头缺少常见字段 | 完善请求头,降低并发,设置预热阶段,先跑少量URL再逐步加量 |
5.2 排查实录:一个真实案例
去年我帮一个朋友排查他的爬虫为什么频繁被封。他在爬某电商平台的公开商品列表页,代码逻辑看起来没问题,requests伪装了UA,也设置了延时,但跑了不到5分钟IP就被封了。
我们第一件事就是看robots.txt。结果发现,这个平台的robots.txt里对*并没有禁止商品列表页,但是有两个细节:第一,User-agent: *块下面有一条Crawl-delay: 2,说明网站期望爬虫至少间隔2秒;第二,/search/路径被明确Disallow了,而朋友恰好是从站内搜索框对应的URL开始抓的——这个路径在robots.txt里是“禁区”。
他改用商品分类导航URL、并将间隔调到3秒之后,再也没被封过。这事给我一个很深的印象:很多被封禁的背后,不是网站“不让爬”,而是你没看明白网站的规矩。
5.3 独家经验:robots.txt的“变与不变”
robots.txt不是一成不变的。很多大站会随着业务调整、反爬策略升级而频繁修改robots.txt。如果长期运行爬虫,建议定期重新拉取robots.txt,对比变化。我习惯在每个爬虫任务里加一个日志,记录前一天抓到的robots.txt内容和当天内容的差异,一旦发现规则变化就告警提醒。
另外,robots.txt的获取本身也可能被反爬系统盯上。有些网站会对高频访问robots.txt的IP做标记,因为正常的搜索引擎蜘蛛一天最多访问一两次,而爬虫开发者容易在调试时反复请求。所以,调试时可以手动下载robots.txt到本地,改用RobotFileParser的本地文件解析方式:
rp = RobotFileParser() rp.set_url("file:///path/to/robots.txt") rp.read()这样既能快速调试,又不会给目标服务器增加压力。
后记:尊重规则,才能把爬虫这条路走长
做了这么久的爬虫,我最大的体会是:真正决定一个爬虫项目能做多长久的,不是技术难度,而是你有没有建立正确的规则意识。robots.txt只是这个意识里最基础的一环。它不像算法那么炫酷,也不像并发设计那么有挑战性,但它是你进入别人系统前的一张“门牌”——看一眼不花多少时间,却能避开很多不必要的冲突。
如果你刚开始接触爬虫,我的建议是每次接到一个新网站,先手动访问一下https://目标域名/robots.txt,养成习惯。如果你已经写过不少爬虫,这几天找个时间把自己经常抓的几个网站的robots.txt重新看一遍,说不定会有新的发现。毕竟,规则是死的,人是活的;尊重规则,往往能让你的爬虫项目走得更远、更稳。