由于 WKWebView 的安全沙箱机制,我们无法直接从 Objective/Swift 层访问网页的 DOM 树,所以必须通过 JavaScript 作为桥梁。
下面我将分步详细讲解,并提供完整的 Swift 代码示例。
核心原理
- 注入 JavaScript:使用
WKUserScript将一段获取 HTML 的 JavaScript 代码注入到 WKWebView 中,这段代码会在网页加载完成后执行。 - 执行 JavaScript:通过
evaluateJavaScript:completionHandler:方法,在当前网页的上下文中执行这段 JavaScript。 - 获取结果:JavaScript 执行完成后,会将页面的 HTML 字符串作为返回值,通过
completionHandler回调传递给 iOS 代码。 - 处理字符串:在 iOS 代码中,你就可以对这个 HTML 字符串进行解析、显示或保存了。
步骤详解 (以 Swift 为例)
第 1 步:创建 WKWebView 和配置控制器
创建一个 WKWebView 和它的 WKWebViewConfiguration,为了安全起见,我们最好只允许在特定的时机(比如用户点击一个按钮时)执行获取源码的脚本,而不是一加载页面就执行。
import UIKit
import WebKit
class ViewController: UIViewController {
var webView: WKWebView!
var sourceCodeTextView: UITextView! // 用于显示源码的文本视图
override func viewDidLoad() {
super.viewDidLoad()
// 1. 配置 WKWebView
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.navigationDelegate = self
view.addSubview(webView)
// 2. 设置约束
webView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
webView.heightAnchor.constraint(equalToConstant: 300) // 给 webView 一个固定高度
])
// 3. 添加一个按钮来触发获取源码
let getSourceButton = UIButton(type: .system)
getSourceButton.setTitle("查看网页源代码", for: .normal)
getSourceButton.addTarget(self, action: #selector(getSourceCode), for: .touchUpInside)
view.addSubview(getSourceButton)
getSourceButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
getSourceButton.topAnchor.constraint(equalTo: webView.bottomAnchor, constant: 20),
getSourceButton.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
// 4. 添加一个 UITextView 来显示源码
sourceCodeTextView = UITextView()
sourceCodeTextView.isEditable = false
sourceCodeTextView.font = UIFont.systemFont(ofSize: 10) // 源码通常用小字体显示
view.addSubview(sourceCodeTextView)
sourceCodeTextView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
sourceCodeTextView.topAnchor.constraint(equalTo: getSourceButton.bottomAnchor, constant: 20),
sourceCodeTextView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
sourceCodeTextView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
sourceCodeTextView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
// 5. 加载一个网页
if let url = URL(string: "https://www.apple.com") {
let request = URLRequest(url: url)
webView.load(request)
}
}
@objc func getSourceCode() {
// 这个方法将在按钮点击时调用
}
}
第 2 步:编写获取 HTML 的 JavaScript 函数
这个 JavaScript 函数非常简单,它直接返回 document.documentElement.outerHTML。document.documentElement 代表 HTML 文档的根元素 <html>,而 outerHTML 属性会返回该元素及其所有后代的 HTML 代码。
function getPageSource() {
return document.documentElement.outerHTML;
}
第 3 步:在 Swift 中执行 JavaScript 并处理结果
我们将 getSourceCode 方法实现完整,它将调用 evaluateJavaScript 来执行我们上面定义的 JavaScript 函数。
extension ViewController: WKNavigationDelegate {
// MARK: - WKNavigationDelegate
// 当网页开始加载时,可以清空源码显示区域
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
sourceCodeTextView.text = "正在加载网页..."
}
// 当网页加载完成时,可以恢复按钮状态等
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
sourceCodeTextView.text = "网页加载完成,点击按钮查看源码。"
}
}
// 在 ViewController 类中
@objc func getSourceCode() {
print("正在获取网页源码...")
// 执行 JavaScript 代码
webView.evaluateJavaScript("document.documentElement.outerHTML") { (result, error) in
// 在主线程更新 UI
DispatchQueue.main.async {
if let error = error {
// 如果执行出错,显示错误信息
self.sourceCodeTextView.text = "获取源码失败: \(error.localizedDescription)"
print("获取源码失败: \(error.localizedDescription)")
} else if let htmlString = result as? String {
// 成功获取到 HTML 字符串
print("成功获取到源码,长度: \(htmlString.count)")
// 将源码显示在 UITextView 中
self.sourceCodeTextView.text = htmlString
} else {
// 其他未知情况
self.sourceCodeTextView.text = "获取源码失败,未知错误。"
print("获取源码失败,返回结果为空或类型不正确。")
}
}
}
}
完整代码 (Swift)
将以上所有部分组合起来,你就可以得到一个完整可用的示例。
import UIKit
import WebKit
class ViewController: UIViewController {
var webView: WKWebView!
var sourceCodeTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
loadWebPage()
}
private func setupViews() {
// 1. 配置 WKWebView
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.navigationDelegate = self
view.addSubview(webView)
// 2. 设置约束
webView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
webView.heightAnchor.constraint(equalToConstant: 300)
])
// 3. 添加一个按钮来触发获取源码
let getSourceButton = UIButton(type: .system)
getSourceButton.setTitle("查看网页源代码", for: .normal)
getSourceButton.setTitleColor(.systemBlue, for: .normal)
getSourceButton.addTarget(self, action: #selector(getSourceCode), for: .touchUpInside)
view.addSubview(getSourceButton)
getSourceButton.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
getSourceButton.topAnchor.constraint(equalTo: webView.bottomAnchor, constant: 20),
getSourceButton.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
// 4. 添加一个 UITextView 来显示源码
sourceCodeTextView = UITextView()
sourceCodeTextView.isEditable = false
sourceCodeTextView.font = UIFont(name: "Courier", size: 8) // 使用等宽字体更好看
sourceCodeTextView.layer.borderColor = UIColor.gray.cgColor
sourceCodeTextView.layer.borderWidth = 1.0
view.addSubview(sourceCodeTextView)
sourceCodeTextView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
sourceCodeTextView.topAnchor.constraint(equalTo: getSourceButton.bottomAnchor, constant: 20),
sourceCodeTextView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
sourceCodeTextView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
sourceCodeTextView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -10)
])
}
private func loadWebPage() {
if let url = URL(string: "https://www.apple.com") {
let request = URLRequest(url: url)
webView.load(request)
}
}
@objc func getSourceCode() {
print("正在获取网页源码...")
// 执行 JavaScript 代码获取整个页面的 HTML
webView.evaluateJavaScript("document.documentElement.outerHTML") { (result, error) in
// 必须在主线程更新 UI
DispatchQueue.main.async {
if let error = error {
self.sourceCodeTextView.text = "获取源码失败: \(error.localizedDescription)"
print("获取源码失败: \(error.localizedDescription)")
} else if let htmlString = result as? String {
print("成功获取到源码,长度: \(htmlString.count)")
self.sourceCodeTextView.text = htmlString
} else {
self.sourceCodeTextView.text = "获取源码失败,返回结果为空或类型不正确。"
print("获取源码失败,返回结果为空或类型不正确。")
}
}
}
}
}
// MARK: - WKNavigationDelegate
extension ViewController: WKNavigationDelegate {
func webView(_ webView 