速读#
这篇标题里有 requests 和 BeautifulSoup,但重点不是 HTML 怎么解析,而是“能抓”和“该抓”的边界。爬虫水平不体现在抓得多猛,而体现在频率、身份、timeout、robots.txt 这些分寸上;克制同时保护对方和自己。
水平在分寸,不在抓得猛#
把请求发得又多又快,技术上没有任何门槛——几行并发代码就能把频率推到对方明显吃不消的量级。真正难的是分寸感:对方的页面是对方花钱花算力供出来的资源,我的并发、频率、身份,是否在该用的范围内。
「能抓到更多」在技术上永远成立;「该不该抓到这个程度」是另一个问句。把两个问句分开,是这篇要立的判断。
我坚持的几条#
HEADERS = {"User-Agent": "s1oopx-learner/1.0"}resp = requests.get(url, headers=HEADERS, timeout=10)time.sleep(1) # 请求之间歇一下| 该做的 | 原因 |
|---|---|
| 看 robots.txt | 对方明确声明的不可抓区域要尊重 |
| 设置 timeout | 请求卡死别拖垮自己 |
| 请求间 sleep | 别把对方打爆,也降低被封风险 |
| 带 User-Agent | 表明身份,出问题对方能联系到你 |
timeout 和 sleep 既是对别人有分寸,也是对自己有保护——没 timeout 能挂死自己的程序,不 sleep 能封掉自己的 IP。克制在这件事上,和自利同向。
robots.txt 值得单独说一句:它不是墙,是声明。协议上没有任何强制力,绕过它一行代码都不用多写——正因为如此,遵不遵守才是个立场问题,不是技术问题。User-Agent 同理:写真实身份,而不是伪装成浏览器。哪天发现必须靠伪装才能继续抓,那一刻对方其实已经用技术手段说了「不欢迎」,再绕过去就是明知故犯。
失败是常态,不是意外#
写爬虫很快撞上一个事实:网络请求不是函数调用,失败是常态。超时、临时 5xx、被限流,都不该让程序裸崩。
resp = requests.get(url, headers=HEADERS, timeout=10)resp.raise_for_status() # 4xx / 5xx 显式抛出来,别静默往下解析不加这行,拿到一个 404 页面接着往下 BeautifulSoup,解析出一堆空值还不知道为什么——错误要在发生的那一层暴露,别让它伪装成数据问题漏到下一层。
失败之后怎么办也有分寸:临时错误可以重试,但要有上限,间隔要一次比一次拉开——指数退避是最省心的默认。无脑立刻重试等于把已经吃紧的对方打得更狠,恰好和「有礼貌」相反。重试用尽还拿不到,就先放弃、记下来,比死磕体面。
能抓 ≠ 该抓#
- 公开 ≠ 允许:页面公开不代表授权你高频抓
- 能复现登录态绕过 ≠ 该绕过:已越过「公开数据」边界
我没把「能抓到」当目标函数。这条线画在哪,取决于我愿不愿意把这套做法讲出来给对方看——讲不出口的,基本就不该做。
别把礼貌绝对化到不敢抓#
公开、允许抓取的数据,正常频率地抓,是合理使用。守的是分寸,不是停止。
后来用 Flask 调外部 API,基本就是这套排列组合:带 UA、带 timeout、控制频率。
有一次技术上几百 QPS 能跑,我仍选单线程加 2 秒 sleep——不急,没必要把别人服务器打爆。这个选择不是技术限制,是边界感。 后来延伸到运维:能开的端口不代表该开,能放的权不代表该放。
