许多值得采集的数据都坐在一个登录之后,而抓取它是一门不同于抓取公开页面的功夫。就在你认证的那一刻,你不再是一个匿名访客,而开始运营一个账户——而账户会以匿名请求从不会遭遇的方式被限速、被挑战、被封。整个游戏从”自由轮换、好让任何单一身份都不显眼”,转向”把一个身份稳稳地held住、好让账户看起来像一个真实、一致的用户”。
在讲怎么做之前先说一句:只抓取你有权访问的数据——你自己的账户、一个你有许可去拉取的合作方的数据、一个你有权使用的 API。登录之后的数据,是一个不同于公开数据的法律与伦理范畴,而抓取是否合法在很大程度上取决于这条线。本指南假定你站在它正确的那一侧。
公开抓取靠轮换。认证抓取靠一致。
来自公开抓取的直觉,是把一切都轮换——每个请求一个新 IP,好让任何单一身份都不显眼。而在一个登录之后,这个直觉恰恰相反。你现在有了一个持久的身份——账户——而一致性正是让它看起来合法的东西。一个在一小时里从五十个国家登录、或在会话中途跳 IP 的账户,看起来不像一个高级用户。它看起来像是被盗了——而那正是触发锁定的东西。
所以认证抓取归结为把两件事做好:管理好会话、好让你高效地保持登录;以及把每个账户钉在一个稳定、干净的身份上、好让它永远不像是瞬移过。
管理会话
当你登录时,服务器会给回会话状态——通常是 Cookie,有时是一个 token。最大的错误、没有之一,是每个请求都重新登录。重新认证很慢,而一阵登录的洪水本身就是一面红旗,而登录端点对此限速很凶。登录一次,把会话捕获下来,并复用它。
用一个朴素的 HTTP 客户端,那意味着一个持久的会话对象——它保住 Cookie 罐子、并骑在代理之上:
import requests
proxies = {"https": "http://customer-USER-country-us-sid-acct42-ttl-600:PASS@p.shifter.io:443"}s = requests.Session()s.proxies.update(proxies)
# 登录一次;Set-Cookie 响应会填充这个罐子。s.post("https://example.com/login", data={"user": USER, "password": PW})
# 为之后的每一个请求复用同一个会话(以及同一个粘性 IP)。r = s.get("https://example.com/account/data")在那之外还有两个细节要紧。许多站点需要一个按会话的 CSRF 或防伪 token——你从一个表单或前一个页面里把它抓下来、并随写入动作一起发送——所以从会话里读它、而不是硬编码它。而许多站点在登录之后,会暴露一个站点自己的前端所调用的干净 JSON API;观察网络标签常常能揭示它,而拿你捕获的 Cookie 或 token 直接去打那个已认证的 API,比重新渲染页面要快得多、轻得多。
把每个账户钉在一个干净的粘性 IP 上
这就是代理层挣得它一席之地的地方。一个账户应当呈现一个一致的位置,所以每个账户都拿到它自己的粘性会话:在那个会话的生命周期里,它永远从同一个住宅 IP 出口——在这里被编码为用户名里的 sid。在一个已登录账户上轮换 IP,是一个经典的封号触发器,因为那个账户看起来在会话中途于各地之间跳。
有三件事让身份撑得住:
- 地理匹配。 IP 应当与账户通常运营的地方相匹配。一个突然出现在德国 IP 上的美国账户,看起来像一次盗号,而许多站点会以一个重新验证提示或一次锁定来回应。
- 干净的信誉。 登录端点审视 IP 信誉比公开页面更狠,因为盗号正是在那里发生的。一个被标记的地址会遭到额外的摩擦——2FA 提示、CAPTCHA、“确认是你本人”的屏幕——甚至在它够到数据之前。
- 一个账户,一个身份。 如果你运营好几个账户,每个都需要它自己的粘性 IP,而不是共享一个。全都从单一地址登录的那些账户,会被关联、并被一起标记。这是反检测浏览器在设备层所处理的那种两层身份的、账户规模上的版本:不同的账户、不同的 IP,而如果你用浏览器,还有不同的指纹。
扩展到许多账户
这个模式靠”把每个账户映射到它自己的稳定身份、并held住那个映射”来扩展。想象一个登记表:账户到粘性 sid、账户到 Cookie 罐子,而如果你驱动一个浏览器,账户到浏览器配置文件。
# 每个账户一个持久身份:同一个 sid -> 同一个出口 IP,自己的罐子。def session_for(account): s = requests.Session() sid = f"acct-{account['id']}" s.proxies.update({ "https": f"http://{BASE_USER}-country-{account['geo']}-sid-{sid}-ttl-600:{PW}@p.shifter.io:443" }) load_cookies(s, account) # 恢复持久化的罐子,若不存在则登录 return s然后把负载分摊到各账户上、而不是把一个账户往死里推,并按账户、按目标给并发封顶,因为每个账户都有它自己的限速。负载均衡的原则适用,但你分摊所依据的单位是账户——每个都在它固定的身份上——而不是原始的 IP。
处理各种失败模式
有三件事会出错,而每一件都有一个特定的应对。
会话过期。 会话会超时。检测它——一个到登录页的重定向、或一个 401——并在同一个身份上重新认证、然后继续。关键的规则是:重新认证时别换 IP;一次来自新位置的重新登录,比过期本身可疑得多。
无声登出。 有时你拿到一个 200,它其实是页面登出后的版本——全是公开的外壳、没有一点账户数据。这是一次软封锁的认证版表亲:靠检查一个只有已登录用户才看得到的元素、来校验你仍然登录着,而不是信任状态码。如果那个只属于账户的标记不见了,就在你记下空行之前重新认证。
被迫的重新验证。 一个 2FA 提示、或一个”确认是你本人”的挑战,通常意味着这个身份看起来有风险——往往是因为 IP 被标记了、或位置变了。一个干净、稳定、地理匹配的 IP,正是让这些保持稀有的东西。当确实触发了一个,就把那个身份当作受怀疑的、并退避,而不是硬闯过去。
登录本身用浏览器还是 HTTP
把工具匹配到登录、而不是匹配到整个抓取。一次返回 Cookie 的简单表单登录,用一个朴素的 HTTP 客户端就跑得很好——把 Cookie 捕获下来、复用它们、保持轻量。但登录页往往是一个站点防守最重的部分——带着 JS 挑战、SSO 重定向、动态 token 和激进的指纹——正因为欺诈就发生在那里。当登录顶住了一个朴素客户端,就用一个真实浏览器去登录(Playwright、Puppeteer 或 Selenium),然后要么继续驱动它、要么把 Cookie 导出到一个更轻的客户端去做大批量的活儿。这里也正是 TLS 与 HTTP/2 指纹咬得最狠的地方,所以一个真实浏览器的网络指纹,常常在登录这一步上起决定作用——哪怕抓取的其余部分在一个简单客户端上好好的。
在信任一次运行之前先验证
认证之后,确认两件事:你确实登录了(拉取一个只属于账户的端点、并检查一个已登录标记),以及你确实从你所期望的那个粘性 IP 出口(一次回显 IP 的检查,在会话的生命周期里应当返回同一个地址)。如果登录”成功了”、但账户标记缺失,或出口 IP 在请求之间漂移,就在采集任何东西之前把它修好——两者都在超时与检测指南里有讲。
底线
登录之后的抓取,讲的是身份的一致性、而不是轮换。登录一次并复用会话、而不是不停地重新认证;把每个账户钉在一个干净、地理匹配的粘性住宅 IP 上;保持一个账户对一个身份;当一个会话过期时在同一个身份上重新认证;并去校验你仍然登录着、而不是信任一个 200。靠增加账户来扩展——每个都带着它自己的稳定身份——而不是靠把单一账户在各地址之间轮换。
把这些做对,认证采集就是持久的、而不是一串锁定。每个账户一个干净的、粘性的住宅 IP,是这整件事所依托的地基,而按 GB 计价让你能跑许多稳定的账户身份、并只为每一个实际拉到的数据付费。