Huginn 是一个 Ruby 实现的在线的自动化任务构建系统。它可以用来监控网页,监听时间,实现自定义行为。Huginn 的 Agents 可以创建或消费事件,并通过一个直接的 Graph 对事件进行传播。可以把它认为是一个开源版本的 IFTTT, Zapier。

Huginn 可以做:

  • 追踪天气,当发生降雨或下雪天气时提前发送邮件(或其他通信方式)提醒
  • 列举出一些关键字,当这些关键字在 Twitter 上被提及时发送提醒
  • 监控机票价格或在线商城价格
  • 监控网页变化
  • 连接不同的服务,比如 Adioso,HipChat,Basecamp,Growl,FTP,IMAP,Jabber,JIRA,MQTT,nextbus,Pushbullet,Pushover,RSS,Bash,Slack,Translation APIs,Twilio,Twitter 等等
  • 发送或接受 WebHooks
  • 执行自定义的 JavaScript 脚本或 CoffeeScript 脚本
  • 给不提供 RSS 输出的站点输出 RSS,使用免费版的 Feed43 体验不是太好,更新频率太低,不能全文输出
  • 监控价格变化
  • 监控豆瓣高分电影
  • 自动保存 Instagram 发布的照片

安装

单镜像运行调试

使用 Docker 是最方便的了

docker pull huginn/huginn

如果不想自己安装 MySQL 之类的数据库,这个镜像中包含了一个打包的数据库,直接 run 就行

sudo docker run -it --name huginn -p 3000:3000 --rm huginn/huginn

注意命令中的 --rm 当终止命令后容器会被删除,如果不想一次性使用不要使用该参数。如果想要在后台运行使用 -d 参数。

或者使用 docker-compose

镜像连接本机数据库

假设在本机以及启动了一个 MySQL 实例,不想使用捆绑到镜像中的数据库那么可以在启动时指定环境变量。不过首先需要设置数据库的连接设置和权限,设置 /etc/mysql/mysql.conf.d/mysqld.cnf 设置监听地址为 0.0.0.0,然后使用 ifconfig 查看 docker0 的 IP 地址,一般为 172.17.0.1,那么给该 IP 访问数据库的权限,界面或者命令:

GRANT ALL PRIVILEGES ON *.* TO 'root'@'172.17.0.%' IDENTIFIED BY 'pass' WITH GRANT OPTION;
flush privileges;

然后使用 Docker

docker run --name huginn \
    -p 3000:3000 \
    -e MYSQL_PORT_3306_TCP_ADDR=172.17.0.1 \
    -e HUGINN_DATABASE_NAME=huginn \
    -e HUGINN_DATABASE_USERNAME=root \
    -e HUGINN_DATABASE_PASSWORD=pass \
    huginn/huginn

使用 Docker Compose

注意下面的 nginx-proxy network,我一直使用它来做自动反代,和自动生成 SSL 证书,

下面的例子中我使用了一个已经存在的 postgresql 容器,名字叫做 db 所以可以直接在数据库连接那边写 db,如果是新环境,那么需要自己创建一个 PostgreSQL 数据库容器。

version: "3"
services:
  huginn:
    image: huginn/huginn
    container_name: huginn
    restart: always
    environment:
      HUGINN_DATABASE_ADAPTER: postgresql
      POSTGRES_PORT_5432_TCP_ADDR: db
      POSTGRES_PORT_5432_TCP_PORT: 5432
      HUGINN_DATABASE_NAME: huginn
      HUGINN_DATABASE_USERNAME: YOUR_NAME
      HUGINN_DATABASE_PASSWORD: YOUR_PASSWORD
      VIRTUAL_HOST: YOUR_DOMAIN
      VIRTUAL_PORT: 3000
      LETSENCRYPT_HOST: YOUR_DOMAIN
      LETSENCRYPT_EMAIL: YOUR_EMAIL

networks:
  default:
    external:
      name: nginx-proxy

数据库:

version: '3'
services:
  db:
    image: postgres:latest
    container_name: postgres
    restart: always
    environment:
      - POSTGRES_USER=YOUR_NAME
      - POSTGRES_PASSWORD=YOUR_PASSWORD
    volumes:
      - postgresql-db:/var/lib/postgresql/data
    healthcheck:
      interval: 10s
      start_period: 30s
volumes:
  postgresql-db:

概念

Agent

Agent 在 Huginn 中是一个行为动作的抽象。

Scenario

一系列 Agent 的集合

Event

每一个 Agent 执行一次,输出就是 Event,Agent 输出的 Event 可以给其他 Agent 使用。

使用

Agent

在 Agent 标签页会看到一系列的默认 Agent,Agent 可以理解为一系列不同类型的自动化任务,举一个简单的例子,这些 Agent 可以帮助抓取网页,或者读取 API,甚至监听文件变化等等。

每一个 Agent 在创建完成后会有下面一些指标:

Age: 表示这个事件创建了多久
Schedule: 表示的是间隔多长时间执行,从几分钟,到几天,到固定时间都有
Last Check: 表示上次执行时间
Last Event out: 表示上次任务执行输出
Last Event In: 表示上一次外部触发任务,比如输出的,需要由外部调用
Events created: 事件自创建后触发的数目

在创建 Agent 的时候有几个参数可以注意下:

  • Name 无需多数
  • Schedule 定义了默认的 Agent 执行频率
  • Controller 如果官方定义的执行频率无法满足需求,可以自定义 Agent 来控制该 Agent 的执行频率
  • Keep Event 表示保留该 Event 的时间长度,Huginn 会保留数据一段时间用来校验,这个参数可以定义过期时间
  • Source 表示当前 Agent 会收到来自这些 Agent 的事件
  • Receivers 表示该 Agent 创建的事件会传递给这些 Agent
  • Scenarios 用来组织一系列的 Agent,方便管理和分享
  • Options 是一个 JSON 串,这是 Agent 最最重要的部分,用来定义 Agent 的核心逻辑

这里将 Options 单独拿出来,这里举一个例子,比如抓取豆瓣高分科幻电影,这个 JSON 的整体格式大致如下:

{
  "expected_update_period_in_days": "2",
  "url": [
    "https://movie.douban.com/tag/%E7%A7%91%E5%B9%BB?type="
  ],
  "type": "html",
  "mode": "on_change",
  "extract": {
    "url": {
      "css": "td:nth-child(2) > div > a",
      "value": "@href"
    },
    "title": {
      "css": "td:nth-child(2) > div > a",
      "value": ".//text()"
    },
    "cover": {
      "css": "img",
      "value": "@src"
    }
  },
  "headers": {
    "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.94 Safari/537.36",
    "Host": "movie.douban.com"
  }
}

说明:

  • 这里的 URL 是要抓取的内容
  • type 指定类型可以选择 xml, html, json, or text
  • mode 表示获取数据的方式,有 all,on_change, merge
    • on_change 在数据更改时才会获取产生事件
    • merge 把新数据和输入数据合并
    • all 获取所有数据
  • extract 中主要提取标题,链接,内容等等

extract 中用的语法可以是 css 选择器,或者是 xpath 语法。等创建好,可以点击下面的 Dry Run 来测试是否运行正确。

实例

输出 RSS

如果要输出一个网站的 RSS,可能需要需要新建两个 Agent,一个 Agent 来抓取网站内容,一个用来导出数据。

第一步,首先要新建 Agent,然后选择 Website Agent. Website Agent 会去抓取网页,包括 XML 文档,JSON feed 然后根据结果来触发事件。

获取网页中部分信息,可以在 extract 中使用 CSS 选择器,选择网页中想要提取的部分,比如下方提取页面中 url 和 title

Options

{
  "expected_update_period_in_days": "2",
  "url": "http://wufazhuce.com/",
  "type": "html",
  "mode": "on_change",
  "extract": {
    "url": {
      "css": ".one-articulo-titulo/a",
      "value": "@href"
    },
    "title": {
      "css": ".one-articulo-titulo/a",
      "value": "normalize-space()"
    }
  }
}

第二步新建 Agent,选择,Data Output Agent,这个 Agent 用来输出 RSS 或者 JSON 内容。这个时候需要注意将上一个 Agent 填到 Sources 中,在 Options 中将上一个 Agent 获取到的变量填到对应的位置。

{
  "secrets": [
    "one"
  ],
  "expected_receive_period_in_days": 2,
  "template": {
    "title": "ONE RSS",
    "description": "RSS ",
    "item": {
      "title": " - ",
      "description": "",
      "link": ""
    }
  },
  "ns_media": "true"
}

保存之后,Agent 会暴露一个地址:

https://localhost:3000/users/1/web_requests/:id/:secret.xml

地址中的 :secret 是 Options 中设定的值,结尾可以是 xml 或者 json。 在 RSS 阅读器中订阅该地址即可。

微信公众号转 RSS

大致思路和上面 One 一样,找到入口,抓取内容,微信没有公开入口,所以只能送搜狗的入口来

https://weixin.sogou.com/weixin?type=1&s_from=input&query=%E5%8F%8D%E6%B4%BE%E5%BD%B1%E8%AF%84&ie=utf8&_sug_=n&_sug_type_=

从页面获取最新文章链接

{
  "expected_update_period_in_days": "2",
  "url": [
    "http://weixin.sogou.com/weixin?type=1&query= 反派影评 &ie=utf8&_sug_=n&_sug_type_=&w=01019900&sut=2064&sst0=1470553392399&lkt=0%2C0%2C0"
  ],
  "type": "html",
  "mode": "on_change",
  "extract": {
    "title": {
      "css": "#sogou_vr_11002301_box_0 > dl:nth-child(3) > dd > a",
      "value": ".//text()"
    },
    "url": {
      "css": "#sogou_vr_11002301_box_0 > dl:nth-child(3) > dd > a",
      "value": "@href"
    }
  }
}

然后根据上一步的 url 获取全文内容

{
  "expected_update_period_in_days": "2",
  "url_from_event": "",
  "type": "html",
  "mode": "merge",
  "extract": {
    "title": {
      "css": "#activity-name",
      "value": "."
    },
    "fulltext": {
      "css": "#js_content",
      "value": "."
    }
  }
}

最后和导出 RSS 一样,将内容部分导出即可。

huginn 例子

下面的网站提供了很多 huginn 实现的例子,对刚刚接触 Huginn 的人来说帮助很大。

官网有更多的例子

衍生

IFTTT 同类型的网站有很多,上面提及的 Zapier,还有这个开源版本的 Huginn,然后在用的过程中又发现了 integromat 这个站点。

reference