
本文介绍一种轻量、无需浏览器渲染的方案,通过解析 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.__preloadedDatas*=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": "nn".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) 或使用队列限流;
- 此方法不适用于付费墙后内容(如订阅专属文章),仅适用于公开可访问的免费文章。
该方案已在实际调度任务中稳定运行数月,单次请求平均耗时