#web-driver #chromedriver #geckodriver #自动化 #网页 #API绑定 #phantomjs

fantoccini-stable

通过WebDriver以编程方式与网页交互的高级API

1个不稳定版本

使用旧的Rust 2015

0.7.2 2017年10月14日

#405 in HTTP客户端

MIT/Apache

78KB
1K SLoC

fantoccini-stable

Crates.io Documentation

通过WebDriver以编程方式与网页交互的高级API。

这是一个原版fantoccini的临时分支,将所有的implFuture替换为Box<Future<>>,以便在Rust稳定版中编译。一旦conservative_impl_trait进入稳定版,我将停止维护/更新此分支。

此库使用WebDriver协议通过相对高级的操作来驱动符合标准(可能是无头)的浏览器,例如“点击此元素”、“提交此表单”等。目前仅限nightly版本,但一旦conservative_impl_trait进入稳定版,这将改变。

大多数交互是通过使用CSS选择器来驱动的。由于大多数WebDriver兼容的浏览器都比较新,CSS标准的高级表达形式也得到支持,提供了相当强大操作符

首先调用Client::form来管理表单,然后使用Form上的方法来操作表单的字段,最后提交表单。

对于对页面的低级访问,可以使用Client::source获取完整的页面HTML源代码,以及使用Client::raw_client_for为特定URL构建原始HTTP请求。

示例

以下示例都假设您有一个运行在4444端口的WebDriver兼容进程。快速获取一个进程的方法是在命令行中运行geckodriver。该代码还对chromedriverghostdriver使用的旧版WebDriver协议提供了部分支持。

示例将大量使用unwrap —— 您可能不应该在代码中这样做,而是应该处理发生时的错误。这尤其适用于您预期可能失败的函数,例如通过CSS选择器的查找。

让我们从在维基百科上点击开始吧

let mut core = tokio_core::reactor::Core::new().unwrap();
let (c, fin) = Client::new("https://127.0.0.1:4444", &core.handle());
let c = core.run(c).unwrap();

{
    // we want to have a reference to c so we can use it in the and_thens below
    let c = &c;

    // now let's set up the sequence of steps we want the browser to take
    // first, go to the Wikipedia page for Foobar
    let f = c.goto("https://en.wikipedia.org/wiki/Foobar")
        .and_then(move |_| c.current_url())
        .and_then(move |url| {
            assert_eq!(url.as_ref(), "https://en.wikipedia.org/wiki/Foobar");
            // click "Foo (disambiguation)"
            c.by_selector(".mw-disambig")
        })
        .and_then(|e| e.click())
        .and_then(move |_| {
            // click "Foo Lake"
            c.by_link_text("Foo Lake")
        })
        .and_then(|e| e.click())
        .and_then(move |_| c.current_url())
        .and_then(|url| {
            assert_eq!(url.as_ref(), "https://en.wikipedia.org/wiki/Foo_Lake");
            Ok(())
        });

    // and set the browser off to do those things
    core.run(f).unwrap();
}

// drop the client to delete the browser session
drop(c);
// and wait for cleanup to finish
core.run(fin).unwrap();

我们最初是如何到达Foobar页面的?我们进行了一次搜索!让我们让程序为我们做这件事

// -- snip wrapper code --
// go to the Wikipedia frontpage this time
c.goto("https://www.wikipedia.org/")
    .and_then(move |_| {
        // find the search form
        c.form("#search-form")
    })
    .and_then(|f| {
        // fill it out
        f.set_by_name("search", "foobar")
    })
    .and_then(|f| {
        // and submit it
        f.submit()
    })
    // we should now have ended up in the rigth place
    .and_then(move |_| c.current_url())
    .and_then(|url| {
        assert_eq!(url.as_ref(), "https://en.wikipedia.org/wiki/Foobar");
        Ok(())
    })
// -- snip wrapper code --

如果我们想下载原始文件怎么办?Fantoccini能为您解决

// -- snip wrapper code --
// go back to the frontpage
c.goto("https://www.wikipedia.org/")
    .and_then(move |_| {
        // find the source for the Wikipedia globe
        c.by_selector("img.central-featured-logo")
    })
    .and_then(|img| {
        img.attr("src")
            .map(|src| src.expect("image should have a src"))
    })
    .and_then(move |src| {
        // now build a raw HTTP client request (which also has all current cookies)
        c.raw_client_for(fantoccini::Method::Get, &src)
    })
    .and_then(|raw| {
        use futures::Stream;
        // we then read out the image bytes
        raw.body().map_err(fantoccini::error::CmdError::from).fold(
            Vec::new(),
            |mut pixels, chunk| {
                pixels.extend(&*chunk);
                futures::future::ok::<Vec<u8>, fantoccini::error::CmdError>(pixels)
            },
        )
    })
    .and_then(|pixels| {
        // and voilla, we now have the bytes for the Wikipedia logo!
        assert!(pixels.len() > 0);
        println!("Wikipedia logo is {}b", pixels.len());
        Ok(())
    })
// -- snip wrapper code --

依赖关系

约15-25MB
约395K SLoC