Python 爬虫(一):先别急着写代码,搞懂 HTTP 再动手

Posted on Tue 13 October 2026 in 技术 • Sucre Liu

第一课不该是 requests.get

几乎所有爬虫教程都从这两行开始:

import requests
from bs4 import BeautifulSoup

html = requests.get(url).text
soup = BeautifulSoup(html, "html.parser")

然后初学者很快卡在"为什么拿到的内容和浏览器不一样"、"为什么一会儿就 403"。九成问题不在解析,而在 HTTP 层。 解析只是把已经拿到的 HTML 拆开;拿不到正确的 HTML,解析写得再漂亮也没用。

所以这个系列的第一篇不写解析器,先把 HTTP 讲清楚。本文基于 requests 2.33 与 httpx 0.28,Python 3.12。

HTTP 里你必须先看懂的几件事

状态码:它才是第一个信号

把响应当"文本"是最大的误区。先看状态码:

码 含义 爬虫里的常见原因
200 成功 ——
301 / 302 重定向 少了结尾斜杠、跳登录、跳地区
304 未修改 你带了 If-Modified-Since 等条件头
403 禁止 UA/Referer 缺失、被 WAF 拦、需要 Cookie
404 不存在 URL 拼错或资源已删
429 请求过多 被限速了,看 Retry-After 头
5xx 服务端错误 目标站故障或你触发了防护

一个负责任的习惯:先 raise_for_status(),再谈解析。 把 403 当成"HTML 不对"去调解析器,是在错误的地方花时间。

请求头不是装饰

服务器判断"你是不是正常用户",很大程度靠请求头。最少要带:

headers = {
    "User-Agent": "Mozilla/5.0 (compatible; MySpider/1.0; +https://example.com/bot)",
    "Accept-Language": "zh-CN,zh;q=0.9",
}

两点经验:

  • 别伪装成某个具体浏览器又到处乱爬。更诚实的做法是在 UA 里留下你的项目名和联系方式——很多站点对"可联系"的 bot 更宽容。
  • 需要登录态的接口,Referer、Cookie、有时还有 X-Requested-With 缺一不可。用浏览器 DevTools 的 "Copy as cURL" 把真实请求抄下来,比猜头高效得多。

编码:text 不一定对

requests.get(...).text 会按响应头里的 charset 解码,响应头没写 charset 时它可能猜错,中文尤其容易变乱码。稳妥做法:

r = requests.get(url, timeout=10)
r.encoding = r.apparent_encoding      # 或显式指定
html = r.text
# 或者干脆拿字节自己解:
html = r.content.decode("utf-8", errors="replace")

另外,gzip/deflate 默认会自动解压;br(Brotli)需要额外装 brotli,否则可能拿到二进制垃圾。

超时:不设就是等死

requests 默认没有超时——目标站不响应时,你的脚本会一直挂着。任何生产代码都必须显式设:

r = requests.get(url, timeout=(3.05, 10))   # (连接超时, 读取超时)

httpx 默认有 5 秒超时,但显式写出来更好。

同步还是异步:requests 与 httpx

先给结论:单机几百个页面以内,同步 + 连接复用就够;要并发几千个请求,再上异步。

requests 和 httpx 的基本用法几乎一样:

# requests
with requests.Session() as s:
    r = s.get(url, headers=headers, timeout=10)

# httpx(同步接口同构)
import httpx
with httpx.Client(headers=headers, timeout=10, http2=True) as c:
    r = c.get(url)

差异值得知道的:

  • Session / Client 复用 TCP 连接,比每次 requests.get 新建连接快得多,也少触发风控。
  • httpx 原生支持异步(httpx.AsyncClient)和 HTTP/2(http2=True,需装 h2)。HTTP/2 对多路复用和某些 CDN 更友好。
  • requests 的生态和第三方兼容更好;httpx 的 API 更现代、更统一。两者都值得会。

并发不是越多越好。对目标站来说,你就是负载。 后文会专门讲并发与退避。

合规与边界:先想清楚再动手

这段不写就是耍流氓。技术能做,不等于可以随便做。

robots.txt:它是站点的"访问礼仪",不是法律条文。用标准库读一下,尊重 Disallow 是基本素养:

from urllib.robotparser import RobotFileParser

rp = RobotFileParser()
rp.set_url("https://books.toscrape.com/robots.txt")
rp.read()
print(rp.can_fetch("MySpider", "https://books.toscrape.com/catalogue/page-1.html"))

比 robots 更重要的是这几条判断:

  1. 服务条款(ToS):很多站点在条款里明确禁止自动化抓取,违反可能构成违约。
  2. 版权与数据库权利:抓到的是别人的内容,转载/再发布是另一回事。
  3. 个人数据:涉及个人信息时,受《个人信息保护法》《GDPR》等约束——不是"公开可见"就等于"可以批量收集"。
  4. 频率:即使没写 robots,也不要对单站做高频请求。加 time.sleep(),别把对方打挂。
  5. 登录态与验证码:绕过验证码、破解登录,可能触及法律红线。这类场景,走官方 API 或直接放弃。

一句话:能拿到 ≠ 能合法使用。 系列里所有示例都使用公开的练习站点(books.toscrape.com、quotes.toscrape.com、httpbin.org),它们就是给人练爬虫的。

一个"负责任"的最小抓取器

把上面的点合起来,一个能直接用的骨架:

import time
import requests
from urllib.robotparser import RobotFileParser

BASE = "https://books.toscrape.com/"
UA = "MySpider/1.0 (+https://example.com/bot)"

def allowed(url: str) -> bool:
    rp = RobotFileParser()
    rp.set_url(BASE + "robots.txt")
    try:
        rp.read()
    except Exception:
        return True
    return rp.can_fetch(UA, url)

def fetch(session: requests.Session, url: str) -> str | None:
    try:
        r = session.get(url, timeout=(3.05, 10))
        r.raise_for_status()
        r.encoding = r.apparent_encoding
        return r.text
    except requests.RequestException as e:
        print(f"[warn] {url} -> {e}")
        return None

def main():
    with requests.Session() as s:
        s.headers.update({"User-Agent": UA})
        for page in range(1, 4):
            url = f"{BASE}catalogue/page-{page}.html"
            if not allowed(url):
                print(f"[skip] robots 不允许: {url}")
                continue
            html = fetch(s, url)
            if html:
                print(url, "->", len(html), "chars")
            time.sleep(1)      # 限速:对目标站最基本的尊重

if __name__ == "__main__":
    main()

它做了四件事:尊重 robots、显式超时、状态码检查、限速。这四点做到,你后面所有代码都建立在正确的 HTTP 认知上。

小结

  • 拿不到数据,先看状态码和响应头,别急着调解析器。
  • 请求头(UA/Referer/Cookie)、编码(apparent_encoding)、超时(必须显式设)是最容易踩的三个坑。
  • Session/Client 复用连接;httpx 额外给你异步与 HTTP/2。
  • 合规先于技术:robots、ToS、版权、个人信息、频率,缺一不可;绕过验证码/登录的事不做。
  • 用练习站起步,用"最小负责任抓取器"打底。

下一篇讲解析:CSS 选择器与 XPath 该怎么选,BeautifulSoup / lxml / parsel 各自适合什么场景,以及为什么"能解析"和"解析得稳"是两回事。