因为requests无法执行javascript,而next.js等框架常将微数据动态注入客户端,导致初始html中script标签为空或仅含占位符。

为什么直接用 requests 拿不到微数据?
因为很多现代网站(尤其是 Next.js、Vue SSR、React Hydration 场景)把 script type="application/ld+json" 微数据块写在客户端 JS 里,或者靠 JS 动态注入。用 requests + BeautifulSoup 只能拿到初始 HTML 的空壳,script 标签压根没内容,或者只有占位符。
这不是解析能力问题,是根本没执行 JS —— 所以第一步必须明确:你需要一个能执行 JS 的环境。
用 Playwright 启动无头浏览器并等待微数据加载
比 Selenium 更轻、更稳,启动快,对动态内容等待逻辑更自然。关键不是“等页面 load”,而是“等特定 script 标签出现且有内容”。
- 用
page.wait_for_function()监控document.querySelector('script[type="application/ld+json"]')是否存在且.textContent非空 - 避免硬写
time.sleep(2)—— 网络抖动或 CDN 延迟会导致漏抓 - 记得加
timeout=10000防止卡死 - 如果页面用多个微数据块(如
WebPage+Article),用page.query_selector_all()全部取出来再逐个json.loads()
from playwright.sync_api import sync_playwright
import json
<p>with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto("<a href="https://www.php.cn/link/0a70b4e9e53d81c726eb245123e2b384">https://www.php.cn/link/0a70b4e9e53d81c726eb245123e2b384</a>")
page.wait_for_function(
"document.querySelector('script[type=\"application/ld+json\"]')?.textContent?.trim()",
timeout=10000
)
scripts = page.query_selector_all('script[type="application/ld+json"]')
for s in scripts:
text = s.text_content().strip()
if text:
try:
data = json.loads(text)
print(data.get("@type"), data.get("headline"))
except json.JSONDecodeError:
pass # 跳过格式损坏的块
</p>
遇到 JSONDecodeError: Expecting property name enclosed in double quotes 怎么办?
常见于开发者手写微数据时用了单引号、尾逗号、注释或模板变量(如 {{ title }}),导致 JSON 不合法。别急着报错退出,先做轻量清洗:
- 用正则替换掉单引号包围的字符串:
re.sub(r"'([^']*)'", r'"\1"', text)(仅当确认无嵌套单引号时) - 删掉尾逗号:
re.sub(r",\s*([}\]])", r"\1", text) - 删注释(JS 风格):
re.sub(r"//.*$|/\*.*?\*/", "", text, flags=re.MULTILINE | re.DOTALL) - 更稳妥的做法:用
json5.loads()替代json.loads()(需pip install json5),它原生支持单引号、注释、尾逗号
要不要用 scrapy-playwright?
如果你已在用 Scrapy 做大规模爬取,集成 scrapy-playwright 能复用中间件、去重、管道,但要注意两个实际约束:
-
playwright的每个Browser实例内存占用约 80–120MB,Scrapy 并发设太高(如CONCURRENT_REQUESTS = 16)容易 OOM —— 建议控制在 3–5,并配PLAYWRIGHT_BROWSER_TYPE = "chromium"(比 firefox 轻) - 微数据可能依赖用户行为触发(比如滚动后才加载评论区的
Review块),这时得在page.evaluate()里主动滚动或点击,不能只靠wait_for_function
真正难的不是取到那几行 JSON,而是判断“这个页面到底该等哪块脚本”“这块 JSON 是不是被服务端预渲染出来的假数据”——这些得结合 response.headers、page.url 和 DOM 时间戳交叉验证。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











