PAYATHON 2026

如何抓取 paywall 后的网站内容

AI 小顾

结论

可以自动访问,但需要同时满足几个条件:使用自己的付费账户,网站条款允许自动抓取,抓取范围没有超出该账户原有的访问权限。

常见实现方式有两种:

  1. 用浏览器自动化工具登录并保存会话。这种方式适合依赖 JavaScript、单点登录或复杂验证的网站。
  2. 模拟登录请求并复用 Cookie。这种方式效率更高,但要正确处理 CSRF Token、重定向及其他登录参数。

这种做法是自动使用已经获得授权的账户,不属于绕过 paywall。不要破解付费验证、规避访问控制,也不要批量传播受版权保护的正文。

推荐方案:使用 Playwright 登录

现代新闻网站的登录流程通常依赖 JavaScript,有时还会跳转到独立的身份认证服务。遇到这类网站,Playwright 一般比手工构造 HTTP 请求更可靠。

先安装依赖:

pip install playwright
playwright install chromium

下面是一个通用示例,其中的选择器和 URL 需要按照目标网站的实际页面修改:

import os
from pathlib import Path
from playwright.sync_api import sync_playwright

LOGIN_URL = "https://example.com/login"
ARTICLE_URL = "https://example.com/archive/example-article"
STATE_FILE = Path("auth-state.json")

username = os.environ["NEWS_USERNAME"]
password = os.environ["NEWS_PASSWORD"]

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)
    context = browser.new_context()

    page = context.new_page()
    page.goto(LOGIN_URL, wait_until="domcontentloaded")

    page.locator('input[name="email"]').fill(username)
    page.locator('input[name="password"]').fill(password)
    page.locator('button[type="submit"]').click()

    # 不要只等待固定时间,应等待登录成功后才会出现的页面状态。
    page.wait_for_url("**/account**", timeout=30_000)

    # 保存 Cookie 和 localStorage,后续任务可复用登录状态。
    context.storage_state(path=str(STATE_FILE))

    page.goto(ARTICLE_URL, wait_until="domcontentloaded")

    article = page.locator("article")
    article.wait_for(state="visible", timeout=30_000)

    print(article.inner_text())

    browser.close()

通过环境变量传入凭据,避免把它们直接写进源码:

export NEWS_USERNAME='your-account@example.com'
export NEWS_PASSWORD='your-password'
python scraper.py

后续运行时,可以直接加载已经保存的会话:

from pathlib import Path
from playwright.sync_api import sync_playwright

STATE_FILE = Path("auth-state.json")
ARTICLE_URL = "https://example.com/archive/example-article"

with sync_playwright() as p:
    browser = p.chromium.launch(headless=True)

    context = browser.new_context(
        storage_state=str(STATE_FILE)
    )
    page = context.new_page()
    page.goto(ARTICLE_URL, wait_until="domcontentloaded")

    # 通过登录按钮、订阅提示或正文元素判断会话是否已失效。
    if page.locator('a[href*="login"]').is_visible():
        raise RuntimeError("登录会话可能已失效,需要重新登录")

    print(page.locator("article").inner_text())
    browser.close()

auth-state.json 可能包含能够直接代表登录身份的 Cookie,因此要像保护密码文件一样保护它。不要将该文件提交到 Git 仓库,可以把它加入 .gitignore:

auth-state.json
.env

更轻量的方式:复用 HTTP Session

如果网站使用普通表单登录,可以用 requests.Session 保存 Cookie。一般流程如下:

  1. 请求登录页面;
  2. 提取 CSRF Token;
  3. 提交用户名、密码和 Token;
  4. 检查是否登录成功;
  5. 使用同一个 Session 请求文章。
import os
import requests
from bs4 import BeautifulSoup

LOGIN_URL = "https://example.com/login"
ARTICLE_URL = "https://example.com/archive/example-article"

session = requests.Session()
session.headers.update({
    "User-Agent": "AuthorizedArchiveClient/1.0"
})

login_page = session.get(LOGIN_URL, timeout=30)
login_page.raise_for_status()

soup = BeautifulSoup(login_page.text, "html.parser")
csrf_input = soup.select_one('input[name="csrf_token"]')

if csrf_input is None:
    raise RuntimeError("未找到 CSRF Token,请检查实际登录表单")

payload = {
    "email": os.environ["NEWS_USERNAME"],
    "password": os.environ["NEWS_PASSWORD"],
    "csrf_token": csrf_input["value"],
}

response = session.post(
    LOGIN_URL,
    data=payload,
    timeout=30,
    allow_redirects=True,
)
response.raise_for_status()

# HTTP 200 不一定代表登录成功,错误页面也可能返回 200。
if "login" in response.url.lower():
    raise RuntimeError("登录可能失败,请检查凭据、表单字段或验证流程")

article_response = session.get(ARTICLE_URL, timeout=30)
article_response.raise_for_status()

article_soup = BeautifulSoup(article_response.text, "html.parser")
article = article_soup.select_one("article")

if article is None:
    raise RuntimeError("未找到文章正文,可能是会话失效或页面结构已变化")

print(article.get_text("\n", strip=True))

示例里的 csrf_token、email 和 password 只是常见字段名,并非通用标准。需要通过浏览器开发者工具的 Network 面板确认目标网站实际提交的地址、字段和重定向流程。

如何选择合适的方式

以下情况通常适合优先使用 Playwright:

  • 登录页面依赖 JavaScript;
  • 登录过程会进入第三方身份认证域名;
  • Token 保存在 localStorage 中,或由脚本动态生成;
  • 页面需要滚动后才加载正文;
  • 登录后还要执行额外的页面操作。

以下情况更适合使用 requests.Session:

  • 网站通过普通 HTML 表单登录;
  • 文章正文直接包含在服务器返回的 HTML 中;
  • 页面不需要执行 JavaScript;
  • 需要用较少的资源处理较多页面。

也可以先用 Playwright 完成一次可见的人工登录,再保存会话。如果站点允许,还可以把浏览器取得的 Cookie 迁移到 HTTP Session。不过,跨域 Cookie、有效期或安全属性处理不正确时,这种迁移很容易失败。

双重验证和验证码

如果账户启用了 MFA,首次登录时可以使用可见浏览器,由人工完成验证码或二次验证,然后保存登录状态:

browser = p.chromium.launch(headless=False)

不要编写代码规避 CAPTCHA、设备验证或其他反自动化安全机制。会话过期后,应重新完成正常认证,也可以询问报社是否提供档案 API、研究接口或批量授权方案。

抓取时的注意事项

检查授权边界

拥有订阅账户不一定代表可以批量下载。开始前需要检查:

  • 服务条款是否允许自动访问;
  • robots.txt 和网站公布的抓取政策;
  • 订阅是否限制并发访问、下载量或内容再利用;
  • 当地适用的版权、数据库权利和计算机访问相关法律。

如果准备保存完整档案、公开数据集或用于商业用途,最好先取得报社的书面许可。

控制请求频率

不要向网站发送大量并发请求。可以采用串行请求,设置合理间隔,并在失败后退避:

import random
import time

for url in article_urls:
    scrape_article(url)
    time.sleep(random.uniform(2.0, 5.0))

遇到 429 Too Many Requests 时,应遵守 Retry-After 的要求并降低请求频率,不要通过轮换 IP 等方式规避限制。

验证内容是否真的解锁

不能只根据状态码判断访问是否成功,因为付费提示页也可能返回 200 OK。还应检查:

  • 页面上是否有登录后的账户标识;
  • 正文容器是否存在;
  • 页面是否出现 “subscribe” 或 “sign in” 提示;
  • 正文长度是否异常;
  • 最终 URL 是否被重定向到登录页。

优先寻找官方接口

部分报社会提供搜索 API、档案导出、图书馆数据库访问或研究用途授权。与解析网页相比,官方接口通常更稳定,访问频率和内容使用范围也更明确。如果有合法 API,应优先使用,避免依赖容易变化的 CSS 选择器。

备注:内容仅供参考。