你真正想要处理的大量数据都藏在登录之后:你自己的分析仪表板、内部报表工具、导出按钮只能拉到上一季度的某个 SaaS 账户、你所管理的会员区。对这些页面发出一个普通 HTTP 请求,得到的是一个重定向到登录表单的响应,因为服务器并不知道你是谁。要触及内容,你必须做浏览器所做的事:登录、保持会话,并在之后的每一次请求中带上那个会话。

本指南将向你展示如何用 Python 抓取登录页面后面的数据。你会构建一个小巧、可运行的抓取脚本,它检查一个登录表单,通过 requests.Session 提交凭据,把会话 cookie(以及一个 CSRF token)带入已认证的请求,然后读取受保护的内容。全程我们都使用公开的练习站点 quotes.toscrape.com/login 作为一个安全的、形如登录的目标。文末附近的合法性章节并非样板话:它确立了让上述一切站得住脚的那条唯一的硬规则,所以在把这段代码指向一个真实账户之前请先读它。

你将构建什么

一个 Python 脚本,它对着一个登录表单进行认证,然后获取一个只对已登录用户才会渲染的页面。以这个练习目标为贯穿示例,脚本会处理一个真实认证流程的每个部分:

  • 表单检查 从登录表单的 HTML 里读取它的字段名和 action URL。
  • CSRF token 从表单里取出隐藏的 token,并在提交时把它回放。
  • 会话登录 通过一个持久的 requests.Session 提交凭据。
  • Cookie 延续 复用会话,使其 cookie 在之后的每一次请求中都随行。
  • 已认证获取 请求一个受保护的页面,并确认你已登录。

为什么普通请求在登录后面会失败

对一个需要登录的页面发出一个裸的 requests.get(),你会得到两种非答案之一:一个重定向到登录表单,或者带着 200 状态的登录 HTML 本身。无论哪种,受保护的内容都不在那里。服务器把页面门禁在一个它看不到的会话上,因为你的脚本从未认证,也没有发送那个证明它已认证的 cookie。

认证是第一道墙。第二道是各站点为了把自动化流量挡在外面、即便你持有一个有效会话也照样使出的种种手段:每次请求都变化的隐藏 CSRF token、速率限制、IP 信誉检查,以及那些在加载后由 JavaScript 渲染、而不是随初始 HTML 一起送来的页面内容。一个静态客户端跑不了那些 JavaScript,所以即便是一次已登录的获取,回来时也可能看起来是空的。当你的目标把登录墙与客户端渲染或机器人拦截结合在一起时,繁重的活儿就该交给一项为此而生的服务,而这正是 Crawling API 稍后登场之处。

范围

本演示刻意使用一个公开的练习登录。其机制与真实账户完全相同,但只有当账户和数据是你自己的,或者你拥有书面授权时,合法性才成立。把这个练习目标当作你自己仪表板的替身,绝不要拿它替代别人的。

前置条件

在写任何代码之前先准备好几样东西。它们都不会花太久。

基本的 Python。 你应当能自如地编写并运行一个脚本,并用 pip 安装包。如果解析 HTML 对你来说是新东西,我们的在 Python 中使用 BeautifulSoup 的指南涵盖了本教程所假定的内容。

Python 3.8 或更高版本。python --version 确认。如果没有,请从 python.org 或像 Anaconda 这样的发行版安装它。

你被允许使用的凭据。 对于练习站点,任意用户名和密码都行。对于真实工作,只使用你拥有的或被明确授权访问的账户。绝不要复用偷来的、共享的或猜测的凭据。

一个 Crawlbase 账户和 JS token(用于最后一步)。 当你真实的目标用 JavaScript 渲染内容或拦截普通客户端时,你会把已认证的请求通过 Crawling API 路由。注册、打开你的仪表板,并复制你的 JavaScript(JS)token。把它当作密码对待,别把它放进版本控制里。

搭建项目

创建一个虚拟环境以便让依赖保持隔离,然后安装抓取器所需的两个库。

bash
python --version

python -m venv login_env
source login_env/bin/activate

pip install requests beautifulsoup4

在 Windows 上,用 login_env\Scripts\activate 而不是 source 那一行来激活环境。两个依赖完成全部工作:requests 驱动 HTTP 会话,而 beautifulsoup4 解析登录表单,让你能读取它的字段名并取出 CSRF token。

第 1 步:检查登录表单

在你能提交凭据之前,你需要确切知道表单期望什么:它提交到的 URL、它各个输入字段的名字,以及它携带的任何隐藏值。在你的浏览器里打开登录页,右键点击表单,选择“检查”。在练习目标上,表单提交到 /login,并包含一个 username 字段、一个 password 字段和一个隐藏的 csrf_token 字段。真实站点各有不同,所以请始终对照实时 HTML 来确认这些名字,而不要想当然。

你也可以用编程方式读取同样的结构。获取登录页,把它加载进 BeautifulSoup,并打印出表单的字段,这样你就知道该发送什么。

python
import requests
from bs4 import BeautifulSoup

LOGIN_URL = "https://quotes.toscrape.com/login"

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

for field in soup.select("form input"):
    print(field.get("name"), "->", field.get("type"))

运行它,你会看到三个字段名被打印出来,包括隐藏的 csrf_token。那个隐藏值正是大多数初次写登录抓取器的人会漏掉的部分:服务器在登录页上签发它,并拒绝任何不把它回传的 POST,而这恰恰是跨站请求伪造(Cross-Site Request Forgery)防护要做的事。

第 2 步:用会话和 CSRF token 登录

现在提交凭据。关键在于使用一个 requests.Session 对象,而不是一次性的 requests.post。会话会在多次请求之间保持 cookie,所以一旦服务器在一次成功登录时设置了一个会话 cookie,之后经由同一个会话发出的每一次请求都会自动带上这个 cookie,服务器也就继续把你当作已登录。

流程是:GET 登录页以接收一个新鲜的 CSRF token(以及初始 cookie),从隐藏输入里抓取出 token,然后通过会话把用户名、密码以及同一个 token POST 回表单的 action URL。

python
import requests
from bs4 import BeautifulSoup

LOGIN_URL = "https://quotes.toscrape.com/login"
USERNAME = "your-username"
PASSWORD = "your-password"

session = requests.Session()

# GET the form first to receive a fresh CSRF token and cookies.
login_page = session.get(LOGIN_URL)
soup = BeautifulSoup(login_page.text, "html.parser")
token = soup.find("input", {"name": "csrf_token"})["value"]

payload = {
    "csrf_token": token,
    "username": USERNAME,
    "password": PASSWORD,
}

response = session.post(LOGIN_URL, data=payload)
response.raise_for_status()

# The site shows a "Logout" link only when authenticated.
if "Logout" in response.text:
    print("Login succeeded; session cookies:", session.cookies.get_dict())
else:
    print("Login failed; still on the sign-in page.")

运行脚本,在一次成功登录时,你会看到 Login succeeded,后面跟着服务器设置的会话 cookie。那个 cookie 就是你在接下来一切操作中的身份凭证。检查 Logout 链接是一个简单、可靠的成功测试:那段文本只对已认证用户出现,所以它的存在就确认了会话已生效,而不必只依赖状态码。

Crawlbase Crawling API

上面的登录之所以行得通,是因为练习目标是纯 HTML。一旦你真实的仪表板用 JavaScript 渲染它的数据,或对自动化客户端发起挑战,单凭一个 requests.Session 就不够了。Crawling API 会在一个真实浏览器里渲染页面,并在服务端通过受信任的住宅 IP 轮换请求,而且它接受你的会话 cookie,所以你可以把一个已认证的请求交给它,拿回成品内容,而无需自己运行一支无头浏览器舰队和一个代理池。

第 3 步:获取一个受保护页面并解析它

会话已认证之后,经由同一个 session 对象发出的每一次请求都会自动带上登录 cookie。所以获取一个受保护页面只不过是又一次 session.get(),无需额外的请求头。这里我们复用第 2 步的会话来请求一个页面并从中解析内容,方式与你解析自己导出的数据完全一样。

python
PROTECTED_URL = "https://quotes.toscrape.com/"

# The same session sends the login cookie automatically.
page = session.get(PROTECTED_URL)
page.raise_for_status()

soup = BeautifulSoup(page.text, "html.parser")
records = []

for card in soup.select(".quote"):
    records.append({
        "text": card.select_one(".text").text.strip(),
        "author": card.select_one(".author").text.strip(),
    })

print(len(records), "records read while authenticated")

因为会话持有那个 cookie,服务器返回的是页面的已登录版本,而不是把你弹回表单。如果你换上自己被授权的仪表板 URL 及其真实选择器,这就是整个模式:登录一次,然后经由同一个会话读取你需要的任意多个受保护页面。

第 4 步:把会话带入 Crawling API

当受保护页面由 JavaScript 渲染,或站点在你的 cookie 甚至还没被检查之前就对自动化客户端发起挑战时,纯会话的做法就不再奏效。这种情况下,你保留上面构建好的同一个登录,把已认证的请求交给 Crawling API,并传入服务器给你的 cookie。API 会在受信任的 IP 背后渲染页面,并返回成品内容。

python
import requests

JS_TOKEN = "YOUR_CRAWLBASE_JS_TOKEN"
TARGET_URL = "https://quotes.toscrape.com/"

# Reuse the cookies from the logged-in session in Step 2.
cookie_pairs = [f"{k}={v}" for k, v in session.cookies.get_dict().items()]
cookie_header = "; ".join(cookie_pairs)

params = {
    "token": JS_TOKEN,
    "url": TARGET_URL,
    "cookies": cookie_header,
    "country": "US",
}

api = requests.get("https://api.crawlbase.com/", params=params)
api.raise_for_status()
print(api.text[:500])

cookies 参数采用与浏览器发送的相同的 key1=value1; key2=value2 格式,这就是我们把会话的 cookie 字典拼接成一个请求头字符串的原因。Crawlbase 会随它渲染的请求转发这些 cookie,所以站点把这次调用当作已登录,然后返回渲染好的 HTML,供你用第 3 步里同样的 BeautifulSoup 代码解析。如果你连续发起多次已认证的调用,并希望会话在它们之间保持,请看下面关于 cookies-session 参数的常见问题。

输出长什么样

第 3 步里的纯会话运行产出了你可以序列化为 JSON 的结构化记录。在练习目标上,其形状小巧而可预测:

json
[
  {
    "text": "The world as we have created it is a process of our thinking.",
    "author": "Albert Einstein"
  },
  {
    "text": "It is our choices that show what we truly are.",
    "author": "J.K. Rowling"
  }
]

换上你被授权的仪表板,字段会变,但原则不变:你登录了,会话承载了你的身份,而你解析了一个匿名请求永远触及不到的内容。

处理“记住我”和过期的会话

一旦你越过单次运行,就会冒出两个实际的小麻烦。第一个是“记住我”复选框。当一个表单提供它时,它只不过是又一个表单字段,通常是一个名为类似 remember 的复选框。检查表单,如果这个框映射到某个值,就把它加进你的 payload(例如 "remember": "on")。尊重它的站点会返回一个存活更久的 cookie,所以你的会话能跨脚本运行存活,而不是在你停止时就过期。只在表单确实有它时才设置它;凭空发明服务器不期望的字段会导致登录失败。

第二个小麻烦是过期。登录 cookie 不是永久的。它们会因计时器、在别处登出,或站点轮换会话而失效。其征兆是你的抓取器突然拉到登录页而不是内容。处理它的办法是检测失败(Logout 链接不见了,或你被重定向到了 /login),并在重试之前从第 2 步重跑登录流程以铸造一个新鲜的会话。从一开始就把这个检查建进去,能省得你在一个小时里悄无声息地抓取登录页。

让会话保持温热

如果你通过 Crawling API 发起许多已认证的请求,并希望同一个登录在它们之间保持,请把 cookies_session 参数赋一个不超过 32 个字符的任意值。API 会把一次请求的会话 cookie 链接到下一次,这样你就不必每次都重新发送完整的 cookie 字符串。

保持不被封锁

即便持有一个有效会话,站点也会监视看起来不像人类的流量。几个习惯能让一次被授权的运行保持健康。

  • 给你的请求设定节奏。 在一个紧凑的循环里猛攻受保护页面,是让会话被标记的最快方式。把请求分散开来,并在它们之间加一个短暂的休眠。
  • 发送表单给你的同一个 CSRF token。 复用一个过期的 token,或者跳过它,是登录 POST 被拒绝的常见原因。始终先 GET 表单,并回放它当前的 token。
  • 留意状态码。 一次运行开始返回重定向或挑战,就是在告诉你会话已失效,或 IP 等级已不再够用。后退并重新认证,而不要盲目重试。
  • 对难啃的目标依靠轮换。 当单个 IP 不断触发检查时,Crawling API 会替你轮换住宅地址;如果你搭建自己的技术栈,Smart AI Proxy 以一个即插即用的端点为你提供同样的轮换。

关于更宽泛的策略手册,参阅如何在不被封锁的情况下抓取网站,以及在受保护页面是客户端渲染时,用 Python 抓取 JavaScript 页面

抓取登录后面的数据合法吗?

这个问题决定了上面的一切是否适合运行,所以在你写下一行生产代码之前,对它要诚实。简短的回答是:只访问你拥有的或被明确授权访问的账户和数据。你一登录某个站点,就接受了它的服务条款,而那些条款几乎总是限制自动化访问。所以登录并不授予你抓取的权利;如果说有什么的话,它还给你套上了一份你现在受其约束的合同。如果数据不是你的,在你对它进行自动化操作之前先取得书面许可。

坚决出界的,是本指南不教的那部分。绝不要使用偷来的、共享的或暴力破解的凭据,绝不要登录一个不属于你的账户。绝不要采集其他用户的个人数据、私信、个人资料,或任何一个真实的人会认为是属于他们的东西。绕过认证、抓取一个你未被邀请进入的登录墙,或采集个人信息,这不是灰色地带;无论你的代码有多干净,它都可能违反计算机滥用法和数据保护法。这里的技术只为一个目的而存在:触及你自己被授权的数据,比如从一个你所管理的仪表板里导出数字,当站点没有提供更轻松的途径时。

那条更轻松的途径通常才是正确的第一站。在你为登录写脚本之前,先看看这项服务是否有官方 API、数据导出或下载功能,或 OAuth 集成。那些是提供方正是为此而建的、经认可的路径,并能让你站在你所同意的条款的正确一侧。只在不存在任何官方机制、且数据确实是你自己的时候,才动用会话抓取,然后把范围限定在那份数据上,别的都不碰。如果一个项目需要属于其他人或其他组织的信息,正确的路径是一份正式的数据协议,而不是一个更聪明的登录脚本。

回顾

核心要点

  • 授权先行。 只对你拥有的或被明确许可访问的账户和数据进行登录后抓取,并在存在官方 API 或导出时优先选用它。
  • 在提交之前检查表单。 从登录 HTML 里读取字段名、action URL 以及任何隐藏的 CSRF token,而不要靠猜。
  • 使用会话,而非一次性请求。 一个 requests.Session 会保持 cookie,所以单次登录就能让之后的每一次请求都保持已认证。
  • 回放 CSRF token。 GET 表单以接收一个新鲜的 token,然后在 POST 时把它发回去,否则服务器会拒绝登录。
  • 把 JS 渲染和拦截交给 Crawling API。 当单凭会话不够时,把你的 cookie 传给 Crawling API,让它在受信任的 IP 背后渲染并返回成品内容。

常见问题

为什么普通请求返回登录页而不是我的数据?

因为服务器把页面门禁在一个你的脚本从未建立的会话上。一个裸的 requests.get() 不发送任何登录 cookie,所以服务器把你当作匿名,并返回一个重定向到登录表单的响应,或带着 200 状态的表单 HTML。要触及内容,你必须先认证,然后在每次请求中发送会话 cookie,而这正是 requests.Session 自动完成的事。

我该如何处理登录表单里的 CSRF token?

先向登录 URL 发出一个 GET 请求,解析返回的 HTML,并从表单里读取隐藏的 CSRF 输入(通常名为 csrf_token)。把那个确切的值包含进你 POST 回登录 URL 的 payload 里。有些站点每次请求都轮换 token 或使用不止一个,所以请始终重新 GET 表单并仔细检查它,而不要把 token 硬编码。

“记住我”会改变请求里的什么?

它是一个额外的表单字段,通常是一个复选框。当你把它包含进你的 POST payload 里时(例如 "remember": "on"),尊重它的站点会签发一个存活更久的 cookie,所以会话能跨脚本运行存活,而不是在你停止时就过期。只在表单确实有这个字段时才加它;发送服务器不期望的字段会让登录失败。

我的抓取器运行到一半开始返回登录页了。发生了什么?

你的会话 cookie 几乎可以肯定过期了或被作废了,原因可能是计时器、在别处登出,或站点轮换会话。检测它(Logout 链接不见了,或你被重定向到了 /login),并在重试之前重跑登录流程以铸造一个新鲜的会话。从一开始就把这个检查建进去,能避免你悄无声息地抓取登录页。

我可以用这种方式抓取另一个人的账户吗?

不行。本指南的范围限定在你拥有的或被明确授权访问的数据。使用偷来的、共享的或猜测的凭据,登录一个不属于你的账户,或采集其他用户的个人数据,都属于出界,并可能违反计算机滥用法和数据保护法。如果你需要属于他人的数据,请取得书面许可或使用一份官方数据协议。

我什么时候该用 Crawling API 而不是普通的 requests?

当受保护页面是静态 HTML 时(就像这里的练习目标),使用普通的 requests。当你被授权的目标用 JavaScript 渲染内容或对自动化客户端发起挑战时,动用 Crawling API。你保留构建好的同一个登录,然后通过它的 cookies 参数把会话 cookie 传给 API,让它在受信任的 IP 背后渲染并返回成品内容。

开始构建

大规模爬取任何站点,无需与基础设施对抗。

Crawlbase 负责处理代理、指纹和 CAPTCHA,让你的团队专注于交付数据流水线,而非维护爬取管道。1,000 次请求免费,无需信用卡。

自助开通 · 无需销售通话 · 提供企业级爬取量