URLSession请求动态网页未加载内容:如何等待脚本执行?
你遇到的问题很典型:URLSession只会获取服务器返回的初始静态HTML,而页面里的实际内容是通过客户端JavaScript动态加载渲染的,所以你拿到的只是页面加载前的loading占位元素,而非最终渲染后的内容。下面给你两种可行的解决方案:
方法一:使用WKWebView渲染页面后获取内容
WKWebView是iOS/macOS上的webkit内核组件,它会像浏览器一样执行页面的JavaScript脚本,等待页面完全渲染后再提取HTML。示例代码如下:
import WebKit class ViewController: UIViewController, WKNavigationDelegate { var webView: WKWebView! override func viewDidLoad() { super.viewDidLoad() // 配置WKWebView let webConfiguration = WKWebViewConfiguration() webView = WKWebView(frame: .zero, configuration: webConfiguration) webView.navigationDelegate = self view.addSubview(webView) // 设置约束(如果需要) webView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ webView.topAnchor.constraint(equalTo: view.topAnchor), webView.leadingAnchor.constraint(equalTo: view.leadingAnchor), webView.trailingAnchor.constraint(equalTo: view.trailingAnchor), webView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) // 加载目标URL guard let url = URL(string: "https://www.ultimatetennisstatistics.com/playerProfile?playerId=3333") else { return } webView.load(URLRequest(url: url)) } // 页面加载完成后的回调 func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { // 执行JS获取渲染后的完整HTML webView.evaluateJavaScript("document.documentElement.outerHTML") { (result, error) in if let htmlString = result as? String { // 这里可以处理渲染后的HTML,查找class为"row"的div print(htmlString) } else if let error = error { print("获取HTML失败:\(error.localizedDescription)") } } } // 处理加载失败的情况 func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { print("页面加载失败:\(error.localizedDescription)") } }
这种方法的优势是完全模拟浏览器行为,能处理所有JS渲染的内容;缺点是需要依赖WebView组件,性能比直接请求API稍差,且在后台线程/非UI环境下使用会有局限。
方法二:直接调用网站的API接口
很多动态渲染的网站会通过AJAX/Fetch请求后端API获取数据,再在前端渲染。你可以通过Chrome开发者工具的Network面板(切换到XHR/Fetch标签),查找页面加载过程中请求的API接口,直接请求这些接口获取结构化的JSON数据,这比解析HTML更高效。
比如,你打开目标页面后,查看Network请求,可能会找到类似/api/player/3333或者/playerStats?playerId=3333的接口,直接用URLSession请求这个接口,就能拿到你需要的统计数据,无需处理HTML解析。
示例代码(假设找到的API接口是https://www.ultimatetennisstatistics.com/api/playerProfile?playerId=3333):
guard let apiUrl = URL(string: "https://www.ultimatetennisstatistics.com/api/playerProfile?playerId=3333") else { return } let task = URLSession.shared.dataTask(with: apiUrl) { (data, response, error) in if let error = error { print("请求失败:\(error.localizedDescription)") return } guard let data = data else { print("无返回数据") return } do { // 解析JSON数据,根据API返回的结构定义对应的Model let playerData = try JSONSerialization.jsonObject(with: data, options: []) print(playerData) // 这里可以直接提取需要的统计字段,无需处理HTML } catch { print("JSON解析失败:\(error.localizedDescription)") } } task.resume()
这种方法的优势是性能更高,数据结构更清晰;缺点是需要分析网站的API接口,部分网站可能会有签名或反爬机制,需要额外处理。
内容的提问来源于stack exchange,提问作者Hergen Lehmann

