Home

Awesome

MontFerret/ferret explain translate-svg

<!-- [![size-img]][size] -->

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

中文 | english


校对 ✅

<!-- doc-templite START generated --> <!-- repo = 'MontFerret/ferret' --> <!-- commit = '88188cac2c0c603d24377cd2b321f7cf9a2991e5' --> <!-- time = '2018-10-11' -->
翻译的原文与日期最新更新更多
commit⏰ 2018-10-11last中文翻译
<!-- doc-templite END generated -->

贡献

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

生活

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


目录

<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->

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

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.

ferret --cdp-launch

**注意:**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())
}