iOS中如何从WebKit控件获取JavaScript执行后的完整HTML源码
在iOS的WebKit控件中获取JS执行后的完整HTML源码
嘿,我来帮你搞定这个问题~要拿到JS执行后的完整HTML,核心是得等所有动态脚本都跑完再去抓取DOM,下面给你详细的实现思路和代码示例:
核心思路
WebKit的WKWebView触发webViewDidFinishNavigation回调只是页面初始资源加载完毕,但异步JS可能还在执行。所以我们需要精准捕捉JS执行完成的时机,再注入JS去获取完整的DOM源码。
具体实现方案
方案一:可控网页(能修改网页JS)——最可靠的方式
如果是你自己开发的网页,可以在JS执行完后主动通知原生代码,这样能100%保证时机正确:
配置
WKWebView,添加消息处理器
让WKWebView能接收网页发来的通知,告诉我们JS已经执行完毕。网页JS执行完后发送通知
在网页的异步JS逻辑结束后,调用window.webkit.messageHandlers给原生发消息。原生接收通知后抓取完整HTML
收到网页的通知后,调用evaluateJavaScript执行document.documentElement.outerHTML获取完整源码。
Swift代码示例
import WebKit class WebViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler { private var webView: WKWebView! override func viewDidLoad() { super.viewDidLoad() setupWebView() loadTargetPage() } private func setupWebView() { // 配置WebView,注册消息处理器 let userContentController = WKUserContentController() // 注册名为"htmlReady"的消息处理器,网页会通过这个名字发通知 userContentController.add(self, name: "htmlReady") let config = WKWebViewConfiguration() config.userContentController = userContentController webView = WKWebView(frame: view.bounds, configuration: config) webView.navigationDelegate = self view.addSubview(webView) } private func loadTargetPage() { // 示例HTML,模拟异步创建JSON script标签 let testHtml = """ <html> <head> <title>Test Page</title> <script> // 模拟异步JS操作,1秒后创建JSON script标签 setTimeout(() => { const jsonScript = document.createElement('script'); jsonScript.type = 'application/json'; jsonScript.textContent = '{"data": "dynamic-content"}'; document.body.appendChild(jsonScript); // 通知原生:JS执行完成,可以抓HTML了 window.webkit.messageHandlers.htmlReady.postMessage("done"); }, 1000); </script> </head> <body> <span>Hello my html sample page</span> </body> </html> """ webView.loadHTMLString(testHtml, baseURL: nil) } // 接收网页发来的消息 func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard message.name == "htmlReady" else { return } // 执行JS获取完整HTML webView.evaluateJavaScript("document.documentElement.outerHTML") { result, error in if let fullHtml = result as? String { print("✅ 获取到完整HTML:\(fullHtml)") // 这里可以处理拿到的HTML,比如保存、解析等 } else if let err = error { print("❌ 获取HTML失败:\(err.localizedDescription)") } } } deinit { // 记得移除消息处理器,避免内存泄漏 webView.configuration.userContentController.removeScriptMessageHandler(forName: "htmlReady") } }
方案二:第三方网页(无法修改网页JS)——轮询检查目标元素
如果是第三方网页,没法修改JS,那我们可以通过轮询检查目标元素是否存在来判断JS是否执行完成:
- 在
webViewDidFinishNavigation回调后,开始轮询 - 每次轮询时执行JS检查目标元素(比如你例子里的JSON类型script标签)是否存在
- 一旦检测到目标元素,就抓取完整HTML
Swift代码示例(轮询部分)
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { // 开始轮询检查目标元素 checkForDynamicElement() } private func checkForDynamicElement() { // 执行JS检查JSON script标签是否存在 webView.evaluateJavaScript("document.querySelector('script[type=\"application/json\"]') !== null") { [weak self] result, error in guard let self = self else { return } if let elementExists = result as? Bool, elementExists { // 目标元素已存在,抓取完整HTML self.webView.evaluateJavaScript("document.documentElement.outerHTML") { htmlResult, htmlError in if let fullHtml = htmlResult as? String { print("✅ 获取到完整HTML:\(fullHtml)") } } } else { // 目标元素还没出现,1秒后继续轮询 DispatchQueue.main.asyncAfter(deadline: .now() + 1) { self.checkForDynamicElement() } } } }
关键注意点
- 一定要用
document.documentElement.outerHTML,而不是document.body.innerHTML,这样才能拿到整个HTML文档的完整源码,包括head部分。 - 轮询的间隔可以根据网页实际情况调整,避免太频繁影响性能。
- 如果网页的JS执行时间不确定,也可以设置一个最大轮询次数,防止无限循环。
内容的提问来源于stack exchange,提问作者Sharath
相关产品推荐
相关产品推荐

