使用Playwright加载本地HTML时无法获取JSON数据的问题
问题描述
尝试通过JavaScript读取本地JSON文件渲染HTML表单,再用Playwright将填充后的表单导出为PDF。但通过file://协议打开本地index.html时,JS无法加载JSON数据,导出的PDF只有纯HTML内容。用本地服务器(如liveserver、node)运行则一切正常,希望不用服务器解决这个问题。
复现代码
gen.py
import os from playwright.sync_api import sync_playwright def generate_pdf(): with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() current_directory = os.getcwd() index_html_path = os.path.join(current_directory, 'index.html') index_html_url = f'file://{index_html_path}' page.goto(index_html_url) page.pdf(path='output.pdf') browser.close() generate_pdf()
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Minimal Reproducible Example</title> <script src="script.js"></script> </head> <body> <div id="content"></div> </body> </html>
script.js
document.addEventListener("DOMContentLoaded", function () { const contentDiv = document.getElementById("content"); fetch("data.json") .then(response => response.json()) .then(data => { const message = data.message; contentDiv.innerHTML = `<p>${message}</p>`; }) .catch(error => console.error("Error loading JSON:", error)); });
data.json
{ "message": "This is dynamic content loaded from a JSON file." }
问题原因
file://协议安全限制:浏览器出于安全考量,对file://协议下的跨资源请求有严格限制,fetch请求本地JSON文件会被拦截,导致JS无法获取数据。- 未等待JS执行完成:
page.goto()默认仅等待DOM加载完成,不会等待异步的fetch请求和动态DOM渲染,生成PDF时内容还未加载完毕。
解决方案
方案1:等待动态内容加载完成
修改Playwright脚本,等待目标元素出现或网络空闲后再生成PDF:
import os from playwright.sync_api import sync_playwright def generate_pdf(): with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() current_directory = os.getcwd() index_html_path = os.path.join(current_directory, 'index.html') index_html_url = f'file://{index_html_path}' page.goto(index_html_url) # 等待动态生成的p元素出现 page.wait_for_selector('#content p') # 或等待网络空闲(无请求持续500ms) # page.wait_for_load_state('networkidle') page.pdf(path='output.pdf') browser.close() generate_pdf()
方案2:解除本地文件访问限制
启动Chromium时添加--allow-file-access-from-files参数,放开file://协议下的跨资源请求限制:
import os from playwright.sync_api import sync_playwright def generate_pdf(): with sync_playwright() as p: browser = p.chromium.launch( headless=True, args=["--allow-file-access-from-files"] ) page = browser.new_page() current_directory = os.getcwd() index_html_path = os.path.join(current_directory, 'index.html') index_html_url = f'file://{index_html_path}' page.goto(index_html_url) page.wait_for_selector('#content p') page.pdf(path='output.pdf') browser.close() generate_pdf()
方案3:内嵌JSON数据到HTML
直接将JSON数据写入HTML的<script>标签,避免fetch请求:
修改index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Minimal Reproducible Example</title> <script> // 内嵌JSON数据 const formData = { "message": "This is dynamic content loaded from a JSON file." }; </script> <script src="script.js"></script> </head> <body> <div id="content"></div> </body> </html>
修改script.js:
document.addEventListener("DOMContentLoaded", function () { const contentDiv = document.getElementById("content"); // 直接使用内嵌数据 const message = formData.message; contentDiv.innerHTML = `<p>${message}</p>`; });
方案4:通过Playwright注入JSON数据
用Playwright读取本地JSON文件,再注入到页面上下文,替代前端fetch:
修改gen.py:
import os import json from playwright.sync_api import sync_playwright def generate_pdf(): with sync_playwright() as p: browser = p.chromium.launch(headless=True) page = browser.new_page() current_directory = os.getcwd() # 读取本地JSON数据 json_path = os.path.join(current_directory, 'data.json') with open(json_path, 'r') as f: form_data = json.load(f) index_html_path = os.path.join(current_directory, 'index.html') index_html_url = f'file://{index_html_path}' page.goto(index_html_url) # 将数据注入页面全局变量 page.add_init_script(f"window.formData = {json.dumps(form_data)};") page.wait_for_selector('#content p') page.pdf(path='output.pdf') browser.close() generate_pdf()
对应修改script.js:
document.addEventListener("DOMContentLoaded", function () { const contentDiv = document.getElementById("content"); const message = window.formData.message; contentDiv.innerHTML = `<p>${message}</p>`; });
内容的提问来源于stack exchange,提问作者Akhil Babu
相关产品推荐
相关产品推荐

