我正在寻找从页面获取完整 html 的最直接的方法。页面加载后和 JS 完成工作后。
速度并不重要。优化并不重要。主线程的锁定并不重要。
只有结果才是重要的:获取页面的完整 HTML 代码。同步进行。
我尝试使用 WKWebView 获取 html:
class InternetLoader {
private let webView = WKWebView(frame: .zero)
private let script = """
window.onload = function() {
document.body.innerHTML;
};
"""
private var resultHtml: String? = nil
func getHTML(from url: URL) -> String {
webView.load(URLRequest(url: url))
let semaphore = DispatchSemaphore(value: 0)
webView.evaluateJavaScript(script) { (result, error) in
if let error = error {
print("Error executing JavaScript: \(error.localizedDescription)")
} else if let htmlContent = result as? String {
self.resultHtml = htmlContent
}
semaphore.signal()
}
semaphore.wait()
return resultHtml!
}
}
用法:
let il = InternetLoader()
let html = il.getHTML(from: URL(string: "https://stackoverflow.com/questions"))
但它却无休无止地卡住了。
我做错了什么?
14
2 个回答
2
感谢。修改此以获取html
@MainActor
class InternetLoader: NSObject {
lazy var webView: WKWebView = {
let webView: WKWebView = WKWebView()
webView.navigationDelegate = self
return webView
}()
private var resultHtml: String? = nil
var continuation: UnsafeContinuation<String?, Error>?
func getHTML(from url: URL) async throws -> String? {
return try await withUnsafeThrowingContinuation { continuation in
self.continuation = continuation
webView.load(URLRequest(url: url))
}
}
}
extension InternetLoader: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("document.body.innerHTML") { [weak self] (result, error) in
if let error = error {
print("Error executing JavaScript: \(error.localizedDescription)")
} else if let htmlContent = result as? String {
self?.resultHtml = htmlContent
}
self?.continuation?.resume(returning: self?.resultHtml)
}
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
continuation?.resume(throwing: error)
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
continuation?.resume(throwing: error)
}
}
let loader = InternetLoader()
var html = ""
if let confirmedHtml = try? await loader.getHTML(from: URL(string: "https://stackoverflow.com/questions")!) {
html = confirmedHtml
}
print(html)
3
-
它工作得很好,它得到了非常准确的 html 代码。但这是async func而不是sync =(当你将其修复为同步代码时我会给你150个投票点。感谢你的帮助
–
-
@Andrew_STOP_RU_WAR_IN_UA Web 视图需要一些时间来加载内容。如果让confirmedHtml = 尝试一下? wait loader.getHTML(from: URL… 在 Web 视图加载或失败之前,此代码不会转到下一行。
–
-
我知道什么意思
sync
,并且async
:)这就是为什么我请求有关确切sync
功能的帮助:)这就是为什么我尝试sync
用以下方式编写 funcsemaphore.wait()
🙂
–
|
我认为 Immanuel 使用 async/await 的答案很好。您可以创建一个命令行工具,其中结构体main()
的方法@main
为async
,然后使用他的解决方案。
然而,由于该方法的同步似乎很重要,因此我尝试使用 anOperationQueue
和 a RunLoop
。
问题中的代码的问题是等待信号量导致死锁,因为 for 的完成处理程序evaluateJavascript
始终在主线程上执行。由于semaphore.wait()
阻塞了主线程,因此信号永远不会被调用。
在我的解决方案中,我创建了异步操作,然后使用运行循环让它们全部按正确的顺序执行。
enum OpStatus {
case initial, executing, finished
}
class AsyncOp: Operation {
var opStatus: OpStatus = .initial {
willSet {
willChangeValue(for: \.isExecuting)
willChangeValue(for: \.isFinished)
}
didSet {
didChangeValue(for: \.isExecuting)
didChangeValue(for: \.isFinished)
}
}
override var isAsynchronous: Bool { true }
override var isExecuting: Bool { opStatus == .executing }
override var isFinished: Bool { opStatus == .finished }
}
final class LoadOp: AsyncOp, WKNavigationDelegate {
private let url: URL
private let webView: WKWebView
init(url: URL, webView: WKWebView) {
self.url = url
self.webView = webView
super.init()
self.webView.navigationDelegate = self
}
override func start() {
webView.load(URLRequest(url: url))
opStatus = .executing
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
opStatus = .finished
}
}
final class JSOp: AsyncOp {
var resultHtml: String?
private let webView: WKWebView
private let script: String
init(webView: WKWebView, script: String) {
self.webView = webView
self.script = script
super.init()
}
override func start() {
webView.evaluateJavaScript(script) { (result, error) in
if let error = error {
print("Error executing JavaScript: \(error.localizedDescription)")
} else if let htmlContent = result as? String {
self.resultHtml = htmlContent
}
self.opStatus = .finished
}
opStatus = .executing
}
}
final class InternetLoader {
private let webView = WKWebView(frame: .zero)
private let script = "document.documentElement.outerHTML.toString();"
func getHTML(from url: URL) -> String? {
var jsLoaded = false
var isQueueSetup = false
let opQueue = OperationQueue.main
opQueue.maxConcurrentOperationCount = 1
let loadOp = LoadOp(url: url, webView: webView)
let jsOp = JSOp(webView: webView, script: script)
jsOp.addDependency(loadOp)
jsOp.completionBlock = {
jsLoaded = true
}
while !jsLoaded && RunLoop.main.run(mode: .default, before: .distantFuture) {
if !isQueueSetup {
opQueue.addOperations([loadOp, jsOp], waitUntilFinished: false)
isQueueSetup = true
}
}
return jsOp.resultHtml
}
}
let il = InternetLoader()
let html = il.getHTML(from: URL(string: "https://stackoverflow.com/questions")!)
print(String(describing: html))
这是很多代码,但它有效。它仍然需要对 Web 视图进行更多错误处理,并且您应该确保这是 的正确使用RunLoop
,虽然我对此没有那么丰富的经验,但它可能可以作为一个很好的起点。
2
-
非常感谢!即使你给出了同步答案,我可以给以马内利 150 分吗?当您给出同步答案时,我将让您选择谁将获得赏金。
–
-
这是你的问题,你决定!如果您选择其他答案并且它适用于您的用例,我不会感到不安,因为我支持在任何明智的地方采用 async/await。
–
|
–
–
–
didFinish
确实无法获取您想要的 HTML,我将重新打开。–
–
|