GitHub - chinanf-boy/ferret-zh: 🇨🇳翻译: 一种声明查询的网络抓取框架 ❤️ 校对 ✅ (original) (raw)

ferret是一个网络抓取系统。 」

中文 | english


校对 ✅

翻译的原文 与日期 最新更新 更多
commit ⏰ 2018-10-11 last 中文翻译

贡献

欢迎 👏 勘误/校对/更新贡献 😊 具体贡献请看

生活

If help, buy me coffee —— 营养跟不上了,给我来瓶营养快线吧! 💰


目录

ferret

Build Status
ferret

它是什么?

ferret是一个网络抓取系统,旨在简化网络上的数据提取,用于 UI 测试,机器学习和分析等.
拥有自己的声明性语言,ferret抽象出技术细节和底层技术的复杂性,帮助关注数据本身.
它非常便携,可扩展且快速.

给我看一些代码

以下示例演示了动态页面的使用.
首先,我们加载主 Google 搜索页面,在输入框中,键入搜索条件,然后单击搜索按钮.
点击操作会触发重定向,因此我们会等到它结束.
页面加载后,我们迭代搜索结果中的所有元素�,并将输出分配给变量.
最后的 for 循环会过滤掉,可能因使用不准确选择器而导致的空元素.

LET google = DOCUMENT("https://www.google.com/", true)

INPUT(google, 'input[name="q"]', "ferret", 25)
CLICK(google, 'input[name="btnK"]')

WAIT_NAVIGATION(google)

FOR result IN ELEMENTS(google, '.g')
    // 过滤videos 和 'People also ask'等的额外元素
    FILTER TRIM(result.attributes.class) == 'g'
    RETURN {
        title: INNER_TEXT(result, 'h3'),
        description: INNER_TEXT(result, '.st'),
        url: INNER_TEXT(result, 'cite')
    }

您可以找到更多示例这里

特性

动机

如今数据就是一切,谁拥有数据 - 拥有世界.
我参与了多个数据驱动的项目,其中数据是系统的重要组成部分,我意识到编写大量的刮刀(爬虫)是多么繁琐.
经过一段时间寻找一个,让我不再编写代码的工具,仅仅给出我需要的数据,这时我决定提出我自己的解决方案.
ferret项目是一项雄心勃勃的计划,旨在为通用平台,不费吹灰之力编写刮刀。

灵感

FQL(Ferret查询语言)受到了AQL(ArangoDB 查询语言)很大的启发.
但由于域名具体,在工作方式上,存在一些差异.

WIP

请注意,该项目正在大力发展。暂时没有文档,且最终版本中可能会有一些变化.
对于查询语法,您可以转到ArangoDB 网站并,使用 AQL 文档作为 FQL 的文档 - 因为它们是相同的.

安装

先决条件

生产

发展

go get github.com/MontFerret/ferret

您可以使用 Google Chrome / Chromium 的本地副本,但为了便于使用,建议您在 Docker 容器中,运行它:

docker pull alpeware/chrome-headless-trunk docker run -d -p=0.0.0.0:9222:9222 --name=chrome-headless -v /tmp/chromedata/:/data alpeware/chrome-headless-trunk

但是,如果您想查看查询执行过程中发生的情况,只需以远程调试端口启动 Chrome:

chrome.exe --remote-debugging-port=9222

快速开始

无浏览器模式

如果你想玩fql,并检查其语法,您可以使用以下命令运行 CLI:

ferret将以 REPL 模式运行.

Welcome to Ferret REPL Please use Ctrl-D to exit this program.

% LET doc = DOCUMENT('https://news.ycombinator.com/') FOR post IN ELEMENTS(doc, '.storylink') RETURN post.attributes.href %

**注意:**符号%用于启动,和结束多行查询。您也可以使用 heredoc 格式.

如果要执行存储在文件中的查询,只需传递文件名:

ferret ./docs/examples/static-page.fql
cat ./docs/examples/static-page.fql | ferret
ferret < ./docs/examples/static-page.fql

浏览器模式

默认情况下,ferret通过 HTTP 协议加载 HTML 页面,因为它更快.
但是现在,有越来越多的网站使用 JavaScript 进行渲染,因此,这种"老派"方法,常常并没有真正起作用。
对于这种情况,您可以通过 Chrome DevTools 协议(也称为 CDP)使用 Chrome 或 Chromium 获取文档页面.
首先,您需要确保已启动 Chrome 的remote-debugging-port=9222参数.
其次,您需要将地址传递给ferretCLI.

ferret --cdp http://127.0.0.1:9222

**注意:**默认情况下,ferret以这个本地地址,作为默认地址,因此在不同端口号或远程地址的情况下,才用显式传递参数.

或者,您可以告诉 CLI 为您启动 Chrome.

**注意:**MacOS 上的启动命令,目前已被破坏.

一旦ferret知道如何与 Chrome 通信,您可以使用一个DOCUMENT(url, isDynamic)函数,用布尔值true表明isDynamic是否动态页面:

Welcome to Ferret REPL Please use exit or Ctrl-D to exit this program.

% LET doc = DOCUMENT('https://soundcloud.com/charts/top', true) WAIT_ELEMENT(doc, '.chartTrack__details', 5000) LET tracks = ELEMENTS(doc, '.chartTrack__details') FOR track IN tracks LET username = ELEMENT(track, '.chartTrack__username') LET title = ELEMENT(track, '.chartTrack__title') RETURN { artist: username.innerText, track: title.innerText } %

Welcome to Ferret REPL Please use exit or Ctrl-D to exit this program.

% LET doc = DOCUMENT("https://github.com/", true) LET btn = ELEMENT(doc, ".HeaderMenu a")

CLICK(btn) WAIT_NAVIGATION(doc) WAIT_ELEMENT(doc, '.IconNav')

FOR el IN ELEMENTS(doc, '.IconNav a') RETURN TRIM(el.innerText) %

嵌入模式

ferret是一个注重模块化的系统,因此,可以轻松嵌入到您的 Go 应用程序中.

package main

import ( "context" "encoding/json" "fmt" "github.com/MontFerret/ferret/pkg/compiler" "os" )

type Topic struct { Name string json:"name" Description string json:"description" Url string json:"url" }

func main() { topics, err := getTopTenTrendingTopics()

if err != nil {
    fmt.Println(err)
    os.Exit(1)
}

for _, topic := range topics {
    fmt.Println(fmt.Sprintf("%s: %s %s", topic.Name, topic.Description, topic.Url))
}

}

func getTopTenTrendingTopics() ([]*Topic, error) { query := ` LET doc = DOCUMENT("https://github.com/topics")

    FOR el IN ELEMENTS(doc, ".py-4.border-bottom")
        LIMIT 10
        LET url = ELEMENT(el, "a")
        LET name = ELEMENT(el, ".f3")
        LET desc = ELEMENT(el, ".f5")

        RETURN {
            name: TRIM(name.innerText),
            description: TRIM(desc.innerText),
            url: "https://github.com" + url.attributes.href
        }
`

comp := compiler.New()

program, err := comp.Compile(query)

if err != nil {
    return nil, err
}

out, err := program.Run(context.Background())

if err != nil {
    return nil, err
}

res := make([]*Topic, 0, 10)

err = json.Unmarshal(out, &res)

if err != nil {
    return nil, err
}

return res, nil

}

可扩展性

同样的,ferret更是一个注重模块化的系统,它不仅可以嵌入它,还可以扩展其标准库.

package main

import ( "context" "encoding/json" "fmt" "github.com/MontFerret/ferret/pkg/compiler" "github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/values" "os" )

func main() { strs, err := getStrings()

if err != nil {
    fmt.Println(err)
    os.Exit(1)
}

for _, str := range strs {
    fmt.Println(str)
}

}

func getStrings() ([]string, error) { // 此函数实现了一种函数类型,而ferret支持该函数作为运行时函数 transform := func(ctx context.Context, args ...core.Value) (core.Value, error) { // 它只是一个辅助函数,有助于验证一些传递的args err := core.ValidateArgs(args, 1)

    if err != nil {
        // 建议返回内置的None类型,而不是nil
        return values.None, err
    }

    // 这是另一个允许进行类型验证的辅助函数
    err = core.ValidateType(args[0], core.StringType)

    if err != nil {
        return values.None, err
    }

    // 强制转换为内置字符串类型
    str := args[0].(values.String)

    return str.Concat(values.NewString("_ferret")).ToUpper(), nil
}

query := `
    FOR el IN ["foo", "bar", "qaz"]
        // 通常,所有函数都以大写形式注册
        RETURN TRANSFORM(el)
`

comp := compiler.New()
comp.RegisterFunction("transform", transform)

program, err := comp.Compile(query)

if err != nil {
    return nil, err
}

out, err := program.Run(context.Background())

if err != nil {
    return nil, err
}

res := make([]string, 0, 3)

err = json.Unmarshal(out, &res)

if err != nil {
    return nil, err
}

return res, nil

}

最重要的是,您可以绕过以下选项,完全关闭标准库:

comp := compiler.New(compiler.WithoutStdlib())

之后,您可以轻松地从标准库中,提供自己的函数实现.

如果您不需要标准库中的特定函数集,则可以关闭整个stdlib函数,并注册其他单独的包:

package main

import ( "github.com/MontFerret/ferret/pkg/compiler" "github.com/MontFerret/ferret/pkg/stdlib/strings" )

func main() { comp := compiler.New(compiler.WithoutStdlib())

comp.RegisterFunctions(strings.NewLib())

}