如何抓取 paywall 后的网站内容
结论
可以自动访问,但需要同时满足几个条件:使用自己的付费账户,网站条款允许自动抓取,抓取范围没有超出该账户原有的访问权限。
常见实现方式有两种:
- 用浏览器自动化工具登录并保存会话。这种方式适合依赖 JavaScript、单点登录或复杂验证的网站。
- 模拟登录请求并复用 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。一般流程如下:
- 请求登录页面;
- 提取 CSRF Token;
- 提交用户名、密码和 Token;
- 检查是否登录成功;
- 使用同一个 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 选择器。
备注:内容仅供参考。
