测试已测试 | ✓ |
Lang语言 | SwiftSwift |
许可证 | MIT |
Released最新版本 | 2018年3月 |
SwiftSwift 版本 | 4.0 |
SPM支持 SPM | ✓ |
由 Mathias Köhnke 维护。
WKZombie 是一个无图形用户界面的 iOS/OSX 网页浏览器。它的开发是为了试验如何使用 Swift 4 编写功能概念。
它集成了 WebKit (WKWebView) 用于渲染和 hpple (libxml2) 用于解析 HTML 内容。此外,它还可以拍摄快照,并且对解析/解码 JSON 元素 有基本支持。通过 链式异步操作,使代码紧凑且易于使用。
无头浏览器有很多使用场景。其中一些是:
以下示例旨在展示 WKZombie 的功能。假设我们想要在 Apple 开发者门户中 显示所有 iOS 预配文件。
在 iOS 的通用网络浏览器(例如 Mobile Safari)中,您通常会输入您的凭据,登录并通过链接导航到 预配文件 部分。
相同的导航过程可以通过在链接 WKZombie 动作 的 iOS/OSX 应用程序中 自动 生成。此外,现在可以使用 UITextField、UIButton 和 UITableView 以原生方式操纵或显示此数据。
通过查看 Example
目录中的 iOS/OSX 演示来了解如何使用它。
开始的最佳方式是查看示例项目。只需在您的 shell 中运行以下命令即可:
$ cd Example
$ pod install
$ open Example.xcworkspace
注意:您需要 CocoaPods 1.0 beta4 或更高版本。
对于命令行演示,请在 WKZombie
的根目录中运行以下命令
$ swift build -Xcc -I/usr/include/libxml2 -Xlinker -lxml2
$ .build/debug/Example <appleid> <password>
WKZombie 实例相当于一个网页会话。顶级便捷方法如 WKZombie.open() 使用共享实例,该实例已配置为默认设置。
因此,以下三个语句是等效的
let action : Action<HTMLPage> = open(url)
let action : Action<HTMLPage> = WKZombie.open(url)
let browser = WKZombie.sharedInstance
let action : Action<HTMLPage> = browser.open(url)
应用程序也可以创建自己的 WKZombie 实例
self.browser = WKZombie(name: "Demo")
请确保在使用期间将 browser
保存在存储属性中。
网页导航基于 操作,这些操作可以在使用 >>>
或 >>*
(用于快照) 操作符进行操作时隐式执行。所有链式操作都将结果传递到下一个操作。然后,===
操作符启动操作链的执行。
以下示例说明如何使用 WKZombie 从开发者门户收集所有配置文件,并对每一页执行快照
open(url)
>>* get(by: .id("accountname"))
>>> setAttribute("value", value: user)
>>* get(by: .id("accountpassword"))
>>> setAttribute("value", value: password)
>>* get(by: .name("form2"))
>>> submit
>>* get(by: .contains("href", "/account/"))
>>> click(then: .wait(2.5))
>>* getAll(by: .contains("class", "row-"))
=== myOutput
为了输出或处理收集到的数据,可以使用闭包或实现一个接受结果作为参数的自定义函数。
func myOutput(result: [HTMLTableColumn]?) {
// handle result
}
或
func myOutput(result: Result<[HTMLTableColumn]>) {
switch result {
case .success(let value): // handle success
case .error(let error): // handle error
}
}
操作也可以通过调用 start() 方法手动启动。
let action : Action<HTMLPage> = browser.open(url)
action.start { result in
switch result {
case .success(let page): // process page
case .error(let error): // handle error
}
}
这当然是一个更简单的方式,但您需要编写更多代码,当您想要连续执行 操作 时可能会变得混乱。
目前实现了一些基本 操作 帮助您访问和导航网站
返回的 WKZombie Action 将加载并返回指定 URL 的 HTML 或 JSON 页面。
func open<T : Page>(url: URL) -> Action<T>
可选,可以传递一个 PostAction。这是一个特殊的等待/验证操作,在页面加载完成后执行。更多信息请见PostAction。
func open<T : Page>(then: PostAction) -> (url: URL) -> Action<T>
返回的 WKZombie Action 将获取当前页面。
func inspect<T: Page>() -> Action<T>
返回的 WKZombie Action 将提交指定的 HTML 表单。
func submit<T : Page>(form: HTMLForm) -> Action<T>
可选,可以传递一个 PostAction。更多信息请见PostAction。
func submit<T : Page>(then: PostAction) -> (form: HTMLForm) -> Action<T>
返回的 WKZombie Action 将模拟与 HTML 链接/按钮的交互。
func click<T: Page>(link : HTMLLink) -> Action<T>
func press<T: Page>(button : HTMLButton) -> Action<T>
可选,可以传递一个 PostAction。更多信息请见[PostAction](#Special-Parameters)。
func click<T: Page>(then: PostAction) -> (link : HTMLLink) -> Action<T>
func press<T: Page>(then: PostAction) -> (button : HTMLButton) -> Action<T>
注意:HTMLButton 仅在存在 "onClick" HTML 属性时工作。如果您想提交 HTML 表单,应使用 提交 而不是点击。
返回的 WKZombie Action 将搜索指定的 HTML 页面,并返回与通用 HTML 元素类型和传递 SearchType 匹配的第一个元素。
func get<T: HTMLElement>(by: SearchType<T>) -> (page: HTMLPage) -> Action<T>
返回的 WKZombie Action 将搜索并返回所有匹配的元素。
func getAll<T: HTMLElement>(by: SearchType<T>) -> (page: HTMLPage) -> Action<[T]>
返回的 WKZombie Action 将在指定的 HTMLElement 上设置或更新现有属性/值对。
func setAttribute<T: HTMLElement>(key: String, value: String?) -> (element: T) -> Action<HTMLPage>
返回的 WKZombie Actions 将执行一个 JavaScript 字符串。
func execute(script: JavaScript) -> (page: HTMLPage) -> Action<JavaScriptResult>
func execute(script: JavaScript) -> Action<JavaScriptResult>
例如,以下示例展示了如何使用 execute() 方法获取当前加载网站的标题
browser.inspect
>>> browser.execute("document.title")
=== myOutput
func myOutput(result: JavaScriptResult?) {
// handle result
}
以下代码显示了执行 JavaScript 的另一种方式,即属性的值
browser.open(url)
>>> browser.get(by: .id("div"))
>>> browser.map { $0.objectForKey("onClick")! }
>>> browser.execute
>>> browser.inspect
=== myOutput
func myOutput(result: HTMLPage?) {
// handle result
}
一些实现 HTMLFetchable 协议的 HTMLElement (例如 HTMLLink 或 HTMLImage) 包含链接到远程对象或数据的属性,如 "src" 或 "href"。以下方法返回一个可以方便地下载此数据的 WKZombie Action
func fetch<T: HTMLFetchable>(fetchable: T) -> Action<T>
一旦执行了 fetch 方法,就可以检索并 转换 数据。以下示例显示了如何将从一个链接中检索到的数据转换为 UIImage
let image : UIImage? = link.fetchedContent()
抓取数据可以转换为实现 HTMLFetchableContent 协议的类型。目前支持以下类型
注意:有关如何使用此功能的更多信息,请参阅 OSX 示例。
返回的 WKZombie Action 将使用指定的函数 f 将 WKZombie 对象转换成另一个对象。
func map<T, A>(f: T -> A) -> (element: T) -> Action<A>
该函数使用指定的函数 f 将一个对象转换成另一个对象。
func map<T, A>(f: T -> A) -> (object: T) -> A
获取快照对 iOS 是可用的。首先,必须注册一个 snapshotHandler,它在获取快照时将被调用
WKZombie.sharedInstance.snapshotHandler = { snapshot in
let image = snapshot.image
}
其次,添加 >>*
操作符将触发快照事件
open(url)
>>* get(by: .id("element"))
=== myOutput
注意:此操作符仅与 WKZombie 共享实例一起工作。
或者,可以使用 snap 命令
browser.open(url)
>>> browser.snap
>>> browser.get(by: .id("element"))
=== myOutput
有关如何获取快照的更多信息,请参阅 iOS 示例。
某些包含 (重新) 加载网页 (例如 打开、提交 等) 的 Action 具有可用的 PostAction。PostAction 是一个等待或验证操作,将在页面加载完成后执行
PostAction | 描述 |
---|---|
wait (秒数) | 动作将在页面加载后等待指定秒数的时间,然后返回。这适用于页面加载已完成,但 JavaScript 或图像加载仍在进行中的情况。 |
validate (JavaScript) | 如果指定的 JavaScript 表达式或脚本返回 'true' 或发生超时,则操作将完成。 |
为了在页面中找到某些 HTML 元素,您必须指定一个 SearchType。get() 和 getAll() 的返回类型是通用的,它确定了应该搜索哪种标签。例如,以下会返回所有具有 book 类的链接
let books : Action<HTMLLink> = browser.getAll(by: .class("book"))(page: htmlPage)
以下是当前可用和受支持的 6 种类型
SearchType | 描述 |
---|---|
id (字符串) | 返回与指定 id 匹配的元素。 |
name (字符串) | 返回所有匹配指定 name 属性值的元素。 |
text (字符串) | 返回所有包含指定文本的内部内容的元素。 |
class (String) | 返回所有匹配指定类名的元素。 |
attribute (String, String) | 返回所有匹配指定属性名/值组合的元素。 |
contains (String, String) | 返回所有具有包含指定值的属性的元素。 |
XPathQuery (String) | 返回所有匹配指定XPath查询的元素。 |
以下运算符可用于操作,这使得连续的操作更容易阅读。
运算符 | iOS | OSX | 描述 |
---|---|---|---|
>>> |
x | x | 此运算符等效于andThen()方法。在此,左侧操作将被启动,并将结果用作右侧操作的参数。注意:如果右侧的操作不需要参数,那么左侧操作的结果将被忽略,并不会传递。 |
>>* |
x | 这是snap命令的一个方便的运算符。它与>>> 运算符相同,不同之处在于在左侧操作完成后会拍摄快照。注意:此运算符在与其他非共享实例一起使用时会抛出断言。 |
|
=== |
x | x | 此运算符启动左侧操作,并将结果作为Optional传递给右侧函数。 |
有时您可能需要处理认证挑战,例如基本认证或自签名证书。WKZombie提供了一个authenticationHandler
,当内部网页需要响应认证挑战时会被调用。
下面的例子演示了如何处理基本认证。
browser.authenticationHandler = { (challenge) -> (URLSession.AuthChallengeDisposition, URLCredential?) in
return (.useCredential, URLCredential(user: "user", password: "passwd", persistence: .forSession))
}
在自签名证书的情况下,您可以使用认证处理程序如下操作。
browser.authenticationHandler = { (challenge) -> (URLSession.AuthChallengeDisposition, URLCredential?) in
return (.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
}
返回的WKZombie操作将对指定的动作函数f进行大量执行,并使用提供的输入元素。一旦所有操作都执行完毕,将返回收集到的结果。
func batch<T, U>(f: T -> Action<U>) -> (elements: [T]) -> Action<[U]>
返回的WKZombie操作将执行指定的动作(使用前一个动作执行结果作为输入参数),直到满足一定条件。之后,它将返回收集到的动作结果。
func collect<T>(f: T -> Action<T>, until: T -> Bool) -> (initial: T) -> Action<[T]>
注意:由于XPath的限制,WKZombie无法直接访问iframe
内部的元素。交换函数可以通过切换Web上下文来解决这个问题。
返回的WKZombie操作将交换当前页面的上下文与嵌入的<iframe>
的上下文。
func swap<T: Page>(iframe : HTMLFrame) -> Action<T>
func swap<T: Page>(then postAction: PostAction) -> (iframe : HTMLFrame) -> Action<T>
以下示例演示了如何按iframe
中嵌入的按钮。
browser.open(startURL())
>>> browser.get(by: .XPathQuery("//iframe[@name='button_frame']"))
>>> browser.swap
>>> browser.get(by: .id("button"))
>>> browser.press
=== myOutput
此命令对调试非常有用。它将WKZombie浏览器当前状态打印为DOM。
func dump()
清除缓存/cookies数据(例如登录数据等)。
func clearCache()
通过设置以下Logger变量可以启用或禁用WKZombie的日志记录。
Logger.enabled = false
可以通过设置以下变量来更改WKZombie的用户代理。
browser.userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 9_0 like Mac OS X) AppleWebKit/601.1.32 (KHTML, like Gecko) Mobile/13A4254v"
如果需要完成操作的时间超过了本属性指定的值,则操作将被取消。默认值为30秒。
browser.timeoutInSeconds = 15.0
默认值为'true'。如果设置为'false',则一旦传输完'raw' HTML数据,加载进度就会完成。不会加载视频或图像等媒体内容。
browser.loadMediaContent = false
如果设置为true
,则将在状态栏中显示网络活动指示器。默认为true
。
browser.showNetworkActivity = true
使用WKZombie与网站交互时,以下类的参与:
此类代表一个网站的只读DOM。它允许您使用SearchType
参数搜索HTML元素。
HTMLElement类是DOM中所有元素的基本类。它允许您检查该元素的属性或内部内容(例如文本)。目前有7个子类,具有额外的元素特定方法和变量可供使用。
可以通过轻松实现额外的子类来实现,并且可能在未来添加。
如上所述,WKZombie提供了对JSON文档的基本支持。
对于解析和编码JSON,以下方法和协议可用:
WKZombie动作返回将解析数据并创建JSON对象。
func parse<T: JSON>(data: Data) -> Action<T>
以下方法返回WKZombie动作,该动作将接受一个JSONParsable(数组、字典和JSONPage)并将其解码为模型对象。该特定的模型类必须实现JSONDecodable协议。
func decode<T : JSONDecodable>(element: JSONParsable) -> Action<T>
func decode<T : JSONDecodable>(array: JSONParsable) -> Action<[T]>
此协议必须由每个要支持JSON解码的类实现。实现将接收一个JSONElement(Dictionary
static func decode(json: JSONElement) -> Self?
以下示例展示了如何结合使用WKZombie进行JSON解析/解码。
browser.open(bookURL)
>>> browser.decode
=== myOutput
func myOutput(result: Book?) {
// handle result
}
要使用Swift Package Manager构建WKZombie
,将其添加到您的Package.swift
文件中,并运行以下命令:
swift build -Xcc -I/usr/include/libxml2 -Xlinker -lxml2
在与Alamofire和WKZombie在同一项目中使用时,您可能会遇到如下的关键词Result
冲突问题:
'Result' is ambiguous for type lookup in this context
这是因为这两个模块都使用了它们结果枚举类型的相同名称。您可以使用以下语法在该特定文件中明确标识该类型:
import enum WKZombie.Result
从现在起,Result将明确指代WKZombie模块中的那个。
如果这仍然在某些文件中模糊不清或不够理想,您可以为import创建一个Swift文件,并使用类型别名进行重命名:
import enum WKZombie.Result
typealias WKZombieResult<T> = Result<T>
有关更多信息,请参阅此处找到的解决方案:这里。
请参阅CONTRIBUTING文件以了解如何提供帮助。您需要在WKZombie根目录中运行以下命令以设置一个可构建的框架项目(WKZombie.xcworkspace
):
$ Scripts/setup-framework.sh
待办事项(TODOs)
更多资源
近期更改
CocoaPods是一个由以下人员创建的项目: