很多人学爬虫,第一周往往是最兴奋的。能拿到网页内容、能解析出标题、能存成结构化数据,那种“我能掌控网络”的感觉会让人上瘾。但我必须泼一盆冷水:真正让你在这条路上走远的,往往不是你写了多漂亮的解析代码,而是你懂不懂“网络爬虫的盗亦有道”。
这篇笔记是《Python网络爬虫与信息提取》第一周第二部分的整理,主题就是爬虫的规则问题:robots协议、User-Agent、采集边界、数据使用边界,以及我们在实战中容易踩的坑。适合刚学完requests和BeautifulSoup、正准备拿自己第一个爬虫项目练手的朋友。看完你会发现,合规这件事不复杂,但全是细节,细节不到位,轻则被服务器拉黑,重则给自己惹上麻烦。
1. 爬虫的“道”:先说清楚什么是盗亦有道
1.1 为什么robots协议只是君子协定
很多人在第一次接触robots这个概念时会问:它是法律条款吗?违反会怎样?
先说结论:robots协议不是法律,它是一份约定俗成的“君子协定”。它的正式名称是Robots Exclusion Protocol(网络爬虫排除标准),由网站管理员在服务器根目录下放一个名为robots.txt的文本文件,声明哪些路径允许爬虫访问、哪些不允许。
关键在于,robots.txt不是强制执行的技术锁。它就像店门口挂的“谢绝参观”牌子,任何懂事的访客看到就会绕道,但不排除有人硬闯。我们的爬虫完全可以无视它去抓取被禁止的内容——技术上没有任何障碍。但问题是,一旦你这么做了,就相当于主动放弃“君子协定”的保护,后续如果对方的服务器日志记录了你的请求、你的抓取行为对网站造成影响或侵犯了权益,责任就在你这边了。
我做数据采集这些年,见过太多人在这上面翻车。有的同学为了完成期末项目,写了一晚上爬虫抓某个电商平台的全部商品信息,结果第二天IP就被封了。你以为只是被封IP?如果对方的服务器在多个地区做了冗灾,你的高频请求还会波及别人的正常服务,这就从“技术问题”上升到了“运营影响”层面。
所以,我建议你把robots协议理解成一条边界感。它不一定是你绝对不能逾越的红线(有些场景下,比如公开数据的学术研究,可能游走在边界),但至少是你必须知道的一条线。你先知道线在哪里,再决定要不要跨、怎么跨,和你完全不知道线在哪里、蒙头乱撞,是两种完全不同的处境。
1.2 robots.txt长什么样
直接看一个典型的robots.txt内容:
User-agent: * Disallow: /login Disallow: /admin Allow: /public User-agent: MyCrawler Disallow:这段内容什么意思?
User-agent: *:对所有爬虫生效。后面的Disallow: /login和Disallow: /admin意味着所有爬虫不能访问/login和/admin这两个路径。Allow: /public:允许访问/public路径,这通常是对Disallow的补充细化。User-agent: MyCrawler:这一行是专门针对名为MyCrawler的爬虫做的规则。Disallow:后面是空的,意思是这个爬虫不受限制,什么都能访问。
规则优先级上,robots.txt遵循“最具体匹配优先”原则。也就是说,如果我是MyCrawler爬虫,它会优先看针对MyCrawler的规则,而不是通用规则。这是很多新手会忽略的点——你明明看到Disallow: /但你的爬虫还能访问,很可能是网站专门为你的UA(User-Agent)开了白名单。
还有几个细节值得注意:
- 空行用来分隔不同User-agent的规则块。
#开头是注释。- 通配符
*和$在某些解析器里有特定含义,表示匹配任意字符和行尾。 - 大小写敏感。
/Admin和/admin是两个不同的路径。
建议拿到一个项目,先手动在浏览器地址栏输入域名/robots.txt看一眼,这是最快的摸底方式。
2. 代码里的“守道”:解析robots与设置身份
2.1 用urllib.robotparser读取和解析robots.txt
Python标准库里有一个非常好用的模块,叫urllib.robotparser,专门用来解析robots.txt。很多人不知道它,因为它不像requests那样出名。我第一次用的时候也觉得挺惊艳——几行代码就能判断某个URL能不能抓。
基本用法:
from urllib.robotparser import RobotFileParser rp = RobotFileParser() rp.set_url("https://example.com/robots.txt") rp.read() # 判断某个User-Agent能否抓取某个URL can_fetch = rp.can_fetch("MyCrawler", "https://example.com/public/data") print(can_fetch) # True 或 False如果can_fetch返回True,说明按robots.txt规则,这个UA有权访问该URL;返回False则不建议抓取。
在实际项目里,我一般会封装一个函数:
import requests from urllib.robotparser import RobotFileParser def can_fetch_url(url, ua="MyCrawler"): from urllib.parse import urlparse parsed = urlparse(url) base = f"{parsed.scheme}://{parsed.netloc}" robots_url = f"{base}/robots.txt" rp = RobotFileParser() rp.set_url(robots_url) try: rp.read() return rp.can_fetch(ua, url) except Exception: # 如果robots.txt读取失败,保守起见返回False return False这里有个工程细节:当robots.txt读取超时或404时,不同库的默认行为不一样。有些解析器会默认“全部允许”,但我建议你自己处理异常时保守一点——默认不允许,除非确认安全。因为404通常意味着网站没有声明限制,但有些网站是动态路由,robots.txt只是没写,不代表欢迎你遍历所有内容。
2.2 配置一个合规的User-Agent
User-Agent(简称UA)是爬虫的身份证。服务器端通过UA识别你是浏览器还是爬虫,是哪个爬虫。
关于UA,有一条重要原则:不要伪装成浏览器去绕过对方限制。
我知道很多人不以为然,觉得这只是技术手段。但换个角度想,如果你的目的是“我是合法访客,我想正常获取公开信息”,那你就应该诚实地表明你是一个爬虫;如果你伪装成浏览器,对方服务器看到你的访问模式和浏览器完全不一样(比如请求间隔极快、不加载图片、不执行JS),反而更容易触发风控。
更合理的做法是:给你的爬虫起一个独一无二的名字,然后在UA里附带联系方式。比如:
MyCrawler/1.0 (+https://mywebsite.com/crawler-info)这样网站管理员看到日志,能知道你是谁、怎么联系你。真出了问题,人家可以发邮件跟你沟通,而不是直接拉黑你的IP段。
有一种情况例外:对方要求必须设置浏览器UA才返回内容(有些网站做了UA白名单,非浏览器UA直接拒绝)。这时候我的处理原则是:先尊重robots,确认路径允许访问;再考虑用浏览器UA去获取数据。这不叫“伪装绕过”,这是在对方技术限制下、规则允许范围内的正常抓取。当然,如果你发现对方所有路径都Disallow了,那不管怎么换UA,都不该再抓。
2.3 控制请求频率:不要让你的爬虫太“热情”
爬虫的“盗亦有道”,第三个“道”就是频率。
我见过最夸张的案例,是某同学写的爬虫用单线程for循环抓几千个页面,没有任何间隔,不到三分钟就把对方服务器搞得不响应了。后面他的IP被防火墙拦了,项目也黄了。
合理做法是控制请求间隔,一般建议在两次请求之间至少间隔0.5秒到1秒。如果你抓的是中小型站点,建议间隔更长,1到3秒都算正常。代码层面可以用time.sleep()实现:
import time import requests headers = {"User-Agent": "MyCrawler/1.0 (+https://mywebsite.com/crawler-info)"} urls = ["https://example.com/page/1", "https://example.com/page/2", ...] for url in urls: resp = requests.get(url, headers=headers, timeout=10) # 在这里处理你自己的解析逻辑 time.sleep(1)如果你需要抓取大量页面,推荐使用带请求间隔队列的架构,比如用Celery或简单的生产者-消费者模式来控制并发。千万不要为了一时的速度去写一个没有节制的并发循环。速度不是爬虫的核心竞争力,稳定才是。
3. 实战中的“读道”:一个完整的合规爬取流程
3.1 开工前的六个检查项
现在我拿到了一个新爬虫任务,无论是自己的学习项目还是实际业务,我开工前都会做一套固定的检查。这套检查救过我很多次,直接分享给你。
- 看robots.txt。访问目标域名下的robots.txt,确认要抓取路径是否被允许。
- 看sitemap。很多网站会提供sitemap.xml,里面有整站可公开访问的URL清单。从sitemap出发抓取,比自己猜URL合规得多,成功率也高。
- 确认数据公开性。如果数据需要登录才能看,说明它不是公开数据,抓取前要格外谨慎。那些随便填个表单就能看到的,和必须登录才能看到的,边界完全不同。
- 设计请求头。设置明确的UA、可联系的URL,不要伪装。
- 评估请求量。预估一共需要抓多少个URL、QPS(每秒请求数)多少、会不会给对方造成压力。如果QPS高于1,我通常会觉得风险较大。
- 明确用途。区分清楚数据是用于个人学习、研究,还是商业用途。商业用途对数据合规性的要求高很多,需要更严格地控制抓取范围。
这六项做下来,至少能过滤掉80%的合规风险。
3.2 完整示例:从robots到数据落地的流程
下面用一个模拟场景走一遍完整流程。比如我们要抓取某新闻网站的公开文章列表页,抓取标题和摘要。
第一步,先看robots.txt:
from urllib.robotparser import RobotFileParser rp = RobotFileParser() rp.set_url("https://news.example.com/robots.txt") rp.read() print(rp.can_fetch("MyCrawler", "https://news.example.com/latest"))如果返回False,直接放弃这条路径。如果返回True,继续。
第二步,设置请求头并抓取页面:
import requests headers = { "User-Agent": "MyCrawler/1.0 (+https://mywebsite.com/crawler-info)", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,en;q=0.6", } resp = requests.get("https://news.example.com/latest", headers=headers, timeout=10) print(resp.status_code)一个值得提的细节:Accept和Accept-Language两个头最好带上。很多服务器会依据这两个字段判断是不是真实浏览器请求,缺了某些头,返回的可能是错误页或验证码页面。这不是为了伪装,而是让请求看起来“正常且友好”。
第三步,解析页面内容:
from bs4 import BeautifulSoup soup = BeautifulSoup(resp.text, "html.parser") articles = soup.select("div.article-title a") for a in articles: title = a.get_text(strip=True) link = a.get("href") print(title, link) time.sleep(0.5)这一步看起来简单,实际项目里最耗时。页面结构经常变,class命名混乱,甚至数据和HTML混在一起。但这不是本篇的重点,就不展开讲了。
第四步,把抓到的数据规范化存储,同时做好请求频率控制。
我把这套流程叫做“先摸底、再动手、带间隔、留痕迹”。整个过程做完,你的爬虫即使给对方管理员看到日志,对方也知道你是在守规矩地爬,而不是在攻击。
3.3 数据使用边界:采集完了不等于能用
这是很多人最容易“翻车”的地方。好不容易把数据抓下来了,存到Excel里,松了口气——但我要提醒你:采集完成只是开始,数据怎么用才是关键。
如果你抓的是公开信息,比如新闻标题、公共百科的词条内容,并且在个人学习笔记里引用,问题不大。但如果你想抓某电商平台的商品价格做比价网站,或者抓某社交平台的用户发言做情绪分析,哪怕数据是公开的,使用场景也可能涉及侵权或不正当竞争。
我的个人判断标准是三条:
- 第一,数据是否涉及个人可识别信息(姓名、手机号、地址、账号等)。涉及的话,风险很大,能不用就别用。
- 第二,数据量级是否大到影响原站点的正常运营。抓全站和抓几页是两回事。全站数据抓取在某些场景下可能被认定为“实质性替代原网站服务”。
- 第三,你的使用是否会与原网站形成直接竞争关系。比如你抓了内容自己做一个App分发,这就是典型的越界。
回到学习场景:你写爬虫练手,抓什么都行,但数据只用于个人学习和笔记,不要公开发布抓取的完整数据包。我发现有些同学喜欢把抓到的几十万条数据打包发到网上“分享”,这种做法极其危险。这不是分享精神,这是给别人埋雷,同时也是给自己埋雷。
4. 常见问题与排查技巧实录
4.1 请求被拒与封禁的排查
爬虫跑着跑着,突然状态码变成403或者418,甚至直接超时,这是最常见的问题。
403表示服务器拒绝访问。排查思路按顺序来:
- 确认你是否访问了robots.txt不允许的路径。如果是,立刻停手,调整范围。
- 检查UA是否被识别为爬虫。有些服务器会直接拒绝常见爬虫UA。你可以试着换成自己的自定义UA,而不是伪装成浏览器。
- 检查请求头是否完整。缺Accept、Accept-Language等字段,可能触发反爬逻辑。
- 检查请求频率。停掉爬虫,等几分钟再试。如果恢复正常,说明是频率问题。
418状态码比较有意思,这是“我是一个茶壶”的状态码,很多网站用它来标记疑似爬虫请求。遇到418,说明对方已经认出你是爬虫了。这时候不要硬刚,停下来重新审视你的请求模式和频率。
4.2 robots.txt误判与缓存问题
有时候robots.txt明明允许,但爬虫还是返回False,怎么排查?
先检查rp.read()是否成功。RobotFileParser在读取失败时,内部状态可能不是预期的。我建议你读取后立即检查:
rp.read() print(rp.mtime()) # 如果为0,说明可能没读成功另外,robots.txt的内容可能会更新。有的网站之前允许,后来改成了Disallow。所以不要一次性把robots解析结果缓存一天甚至一周,每次启动爬虫前重新读取一次,是最稳妥的。
还有一种情况:can_fetch的URL写法不同会导致判断不同。比如https://example.com/path和https://example.com/path/(末尾多了一个斜杠)在robots解析里可能是两个不同的路径。实际调用时尽量使用和站点真实路径完全一致的URL去判断。
4.3 团队协作时的规范建议
如果你不是一个人写爬虫,而是在团队里工作,我建议在项目里加一个“采集规范文件”,内容包括:
- 目标站点列表
- 每个站点允许爬取的路径范围
- 请求频率上限
- 负责人的联系方式
- 采集数据的存储位置和权限
这套东西很简单,但能避免两个问题:一是团队成员你抓一条、我抓一条,频率叠加导致被封;二是离职或交接时,后面的同事不知道前面对站点做过什么承诺,违规破坏关系。
我自己在团队里踩过这个坑。某次项目后来换了人维护,新同事不知道robots的限制,直接改了请求参数去高频抓取,结果对方站点封了我们整个出口IP,连带影响了其他正常业务。从那以后,每接一个新站点,我都会把规范写清楚、写在文档里。
5. 新手最容易忽略的三个细节
最后再补充几个我见过无数人忽略的细节,这几条可以说是我这几年爬虫实践里沉淀出来的真东西。
一是robots.txt里没有提到的路径,不代表可以胡来。robots.txt只是管理员主动声明的一部分。如果某个路径在robots.txt里没写,服务器端又设置了访问频率限制或验证码,你还是要遵守服务器实际行为传递出来的信号。换句话说,robots是底线声明,但服务器的实时反馈才是真正的“实时规则”。
二是日志要留住,但不是用来对抗,而是用来证明你的善意。如果你的爬虫UA里写了联系邮箱,对方真的会发邮件来沟通。保持礼貌、说明用途、控制频率,大部分正常沟通都能解决。反过来,如果你留的是假UA、假联系方式,对方只会直接封禁了事。
三是学习爬虫最好的试练场,是你自己的网站。在自己服务器上搭一个临时页面,放几十条假数据,写一个robots.txt,然后用自己的爬虫去实践:先写一个禁止抓取的路径,再写一个允许抓取的路径,观察你的代码怎么判断、怎么处理。这一步走通,你对robots协议的理解会比看十篇教程都深。
爬虫技术本身是中性的,关键是使用方式。我现在写爬虫,第一反应不是“怎么把它写出来”,而是“该不该写、写了注意什么”。这份谨慎不是怯懦,而是见过太多翻车案例之后形成的一种职业习惯。希望这篇笔记能帮你建立起同样的习惯,至少在“盗亦有道”这四个字上,先做到心里有数。