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监听目标元素生成,元素加载完成后主动通知原生端,无需反复轮询:
- 先在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) } }
- 实现消息接收协议,页面加载完成后注入监听逻辑:
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
相关产品推荐
相关产品推荐

