
本文介绍一种轻量、无需浏览器渲染的方案,通过解析 nytimes 页面内嵌的 `__preloadeddata` json 数据直接获取结构化文章内容,规避 cloudflare 人机验证,适用于高频、低资源服务器环境。
纽约时报(NYTimes)近年来全面升级前端防护机制,传统基于 requests + XPath 的静态 HTML 抓取方式已失效——页面返回的不再是完整文章 DOM,而是一个由 JavaScript 动态注入内容的“壳”,并常伴随 Cloudflare 人机挑战(如 “Checking if you are human…”)。此时,依赖 Selenium 或 Playwright 等浏览器自动化方案虽可行,但会显著增加内存开销、启动延迟与维护复杂度,不适用于轻量部署或高频率调度场景。
幸运的是,NYTimes 采用服务端预渲染(SSR)+ 客户端水合(hydration)模式,其关键文章数据以序列化 JSON 形式内嵌在 HTML 中,挂载于全局变量 window.__preloadedData。该数据结构完整、稳定、无反爬混淆,且不触发 JS 执行或人机验证——只需一次 HTTP GET 请求即可获取全部正文、标题、摘要等核心字段,完全规避了浏览器渲染开销。
以下为优化后的轻量级实现(仅依赖 requests 和标准库):
import json
import re
import requests
def extract_nytimes_article(url: str) -> dict:
headers = {
"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:122.0) Gecko/20100101 Firefox/122.0"
}
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
# 提取 window.__preloadedData 内容(兼容 undefined → null 转换)
match = re.search(r"window\.__preloadedData\s*=\s*(\{.*?\});", response.text, re.DOTALL)
if not match:
raise ValueError("Failed to locate __preloadedData in HTML")
data_str = match.group(1).replace(":undefined", ":null")
data = json.loads(data_str)
# 解析文章结构(基于 GraphQL 响应 schema)
try:
article_data = data["initialData"]["data"]["article"]
sprinkled_body = article_data["sprinkledBody"]["content"]
except KeyError as e:
raise ValueError(f"Unexpected JSON structure: missing key {e}")
headline = ""
summary = ""
paragraphs = []
for block in sprinkled_body:
typename = block.get("__typename", "")
if typename == "HeaderBasicBlock":
headline = block.get("headline", {}).get("content", [{}])[0].get("text", "")
summary = block.get("summary", {}).get("content", [{}])[0].get("text", "")
elif typename == "ParagraphBlock":
text_parts = [cc.get("text", "") for cc in block.get("content", [])]
full_para = "".join(text_parts).strip()
if full_para:
paragraphs.append(full_para)
return {
"headline": headline.strip(),
"summary": summary.strip(),
"body": "\n\n".join(paragraphs)
}
# 使用示例
url = "https://www.nytimes.com/2024/02/06/us/politics/border-ukraine-israel-aid-congress.html"
article = extract_nytimes_article(url)
print(article["headline"])
print("\n", article["summary"], "\n" + "-" * 80)
print(article["body"][:500] + "..." if len(article["body"]) > 500 else article["body"])✅ 优势总结:
立即学习“Java免费学习笔记(深入)”;
- 零浏览器依赖:不启动 Chromium/Firefox,内存占用
- 高稳定性:__preloadedData 是 NYTimes SSR 核心机制,长期未变更,XPath/XPath 类选择器易受 CSS 类名变动影响,而此方案基于语义化 block 类型(如 ParagraphBlock),鲁棒性更强;
- 合规友好:仅读取公开 HTML 中已存在的静态数据,不模拟点击、不绕过验证码、不高频刷接口,符合 robots.txt 及合理使用原则;
- 易于扩展:可轻松添加作者、发布时间、章节标题(SubheaderBlock)、引用块(PullQuoteBlock)等字段支持。
⚠️ 注意事项:
- 若未来 NYTimes 更改 __preloadedData 变量名或 JSON 结构(极小概率),需微调正则与路径解析逻辑;建议添加异常捕获与降级日志;
- 仍需遵守 robots.txt(https://www.php.cn/link/20b489f9a75e4574d4bc69946aea986f)及速率限制,生产环境建议加入 time.sleep(1) 或使用队列限流;
- 此方法不适用于付费墙后内容(如订阅专属文章),仅适用于公开可访问的免费文章。
该方案已在实际调度任务中稳定运行数月,单次请求平均耗时










