You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中如何通过WKWebView获取动态懒加载网页的HTML内容

问题原因与优化方案

原实现错误原因

你写的while循环是同步执行在主线程的,而evaluateJavaScript是异步方法,它的回调需要等主线程空闲时才会执行。while循环会一直占住主线程,导致回调永远没有执行机会,并不是同一次页面加载不能多次调用evaluateJavaScript。

优化方案

方案1:异步轮询(改造成本最低)

把同步while循环改成异步递归调用,增加超时机制避免无限等待,代码示例:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    // 最多等待10秒超时
    let timeoutTime = Date().timeIntervalSince1970 + 10
    func checkLoadStatus() {
        myWebView.evaluateJavaScript("document.querySelectorAll('header').length") { [weak self] result, error in
            guard let self = self, error == nil, let count = result as? Int else {
                print("评估错误")
                return
            }
            if count > 0 {
                // 元素已加载,拉取HTML解析
                myWebView.evaluateJavaScript("document.body.innerHTML") { result, error in
                    guard let htmlContent = result, error == nil else {
                        print("HTML获取错误")
                        return
                    }
                    do {
                        let doc = try SwiftSoup.parse(htmlContent as! String)
                        self.allProducts = try doc.select("header").array()
                        // 后续处理逻辑
                    } catch {
                        print("解析错误")
                    }
                }
            } else if Date().timeIntervalSince1970 < timeoutTime {
                // 未加载完成且未超时,500ms后再检查
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                    checkLoadStatus()
                }
            } else {
                print("加载超时")
            }
        }
    }
    checkLoadStatus()
}

方案2:DOM变化监听(最高效,无冗余轮询)

直接在页面注入JS的MutationObserver监听目标元素生成,元素加载完成后主动通知原生端,无需反复轮询:

  1. 先在WebView初始化时配置消息回调:
private let myWebView: WKWebView = {
    let prefs = WKPreferences()
    let config = WKWebViewConfiguration()
    config.preferences = prefs
    // 注册消息处理器
    config.userContentController.add(LeakAvoider(delegate: self), name: "DOMLoaded")
    let webView = WKWebView(frame: .zero, configuration: config)
    return webView
}()

// 防止WKUserContentController强引用导致内存泄漏的工具类
class LeakAvoider: NSObject, WKScriptMessageHandler {
    weak var delegate: WKScriptMessageHandler?
    init(delegate: WKScriptMessageHandler) {
        self.delegate = delegate
        super.init()
    }
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        delegate?.userContentController(userContentController, didReceive: message)
    }
}
  1. 实现消息接收协议,页面加载完成后注入监听逻辑:
extension YourViewController: WKScriptMessageHandler {
    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        // 注入MutationObserver监听逻辑
        let observerJS = """
        const observer = new MutationObserver((mutations) => {
            if (document.querySelectorAll('header').length > 0) {
                observer.disconnect();
                window.webkit.messageHandlers.DOMLoaded.postMessage(document.body.innerHTML);
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
        // 超时10秒主动断开监听
        setTimeout(() => {observer.disconnect()}, 10000);
        """
        webView.evaluateJavaScript(observerJS)
    }

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        guard message.name == "DOMLoaded", let html = message.body as? String else { return }
        do {
            let doc = try SwiftSoup.parse(html)
            allProducts = try doc.select("header").array()
            // 后续处理逻辑
        } catch {
            print("解析错误")
        }
    }
}

方案3:直接调用数据接口

你抓取的商品列表页数据是通过后端接口返回的,可直接通过网络请求调用对应接口获取结构化JSON数据,比解析HTML性能高、稳定性强,无需依赖WebView。你可以通过浏览器开发者工具的网络面板找到对应的商品接口,直接构造请求调用即可。

内容的提问来源于stack exchange,提问作者aadi sach

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.03 01:24:03