使用 Huginn 收集资讯并发送电子邮件

短文 ? 次阅读
ABC
本文属初学作品,其时仍在学习、探索;高人不必读。

我对资讯有两种相互矛盾的态度:既希望能够及时掌握各方面的资讯,又不希望自己能在任何时候都看到它们——如经常冒红点的朋友圈、微信订阅号,它们会不断造成干扰。当然,不是说消息产生了就一定是干扰,不看便是;但我控制能力稍差,不能不受到各种提醒的影响,总是下意识的要看一眼。结果,往往是「一直在浏览,从未留印象」,既浪费了时间,又没有能够真正领会资讯的内容。

我一直在寻找解决这种矛盾的方案。目前来看,有一种方案很合我意:低频率、集中式的阅读。将一天之中各个时间收到的所有资讯都集中起来,然后在固定的时间(比如每天晚上饭后休息的时间)发给自己,这样既可以避免在其余时间受干扰,又可以使对资讯的浏览行为变得正式、规律,效果比「没事时」在手机上随意浏览要好不少。

方案讨论

怎样在技术上实现这种想法呢?考虑一下上面的要求:

  • 低频率:不能在接收到新消息时立马提醒、展示;
  • 集中式:将各个平台(或至少是一类平台)的信息统一收集起来。

先说「集中式」,这个有现成的方案:RSS。当然,不是所有的资讯来源都有 RSS 源,很多网站现在已经不提供了;为此,还需再额外利用一些手段从没有提供 RSS 源的网站上抓取数据,自制 RSS。这一方面,也有很多合适的工具。

另一个是「低频率」,这个反而没那么容易——网络上的服务大多都是实时的,不会随你的要求而「中断」。一种合适的手段是:将信息收集起来,通过各种通讯软件定时发出。QQ、Telegram 之类的应用当然很好,电子邮件则是更基本的一种手段;特别地,它还有以下的优势:

  • 电子邮件内能塞下完整的 HTML 页面,可以在其中编写自定义的模板与样式表,将不同来源的资讯内容都弄得「整整齐齐」,附上原文链接等额外信息;
  • 只要有一个 SMTP 服务器,就可以发邮件;或者,也可以直接借用邮件平台的 SMTP 服务发(比如用自己的 Gmail 邮箱给自己的网易邮箱发邮件,或干脆就自己给自己发)。
  • 原则上可以给任意多的人发电子邮件(只要不被拒收),方便地向一群人提供完全一致的资讯服务,而不需要事先加对方为「好友」之类。

综合考虑上述两个需求,最好的解决方案是:找到一个服务器应用,能够按照给定的配置执行信息抓取、简单处理、归并集中、发送邮件等功能。而开源的 Huginn 恰好能够满足这些要求,它是一个功能强大的自动化平台,被视为「开源的 IFTTT」。它的基本工作原理很容易理解:配置若干个模块,其中一些负责主动(定期)或被动(接受外部刺激)的发起一些请求,另外一些则在接受到这些请求后执行一些任务(比如发邮件)。

用 Huginn 来做资讯处理这样的工作,其实有些「大材小用」了;但我手头确实有可用的服务器,用用也无妨,毕竟这相对于自己写脚本、启动服务要更简单一些。以下就以这样一个工作场景为例,来配置一套 Huginn 的工作流程,完成相应的任务:

案例:现在希望 Huginn 能够帮我监控学校教务处发布的新通知,将其汇总后于每天晚上 9 点发送到我的邮箱中。

安装 Huginn

安装方法最好参考官方指南,网上也有很多博客介绍过。无论如何,得先有一个服务器(或者说「一直开着的计算机」),云主机、云服务(Huginn 支持在 Heroku部署)也可以。

由于要让 Huginn 发送电子邮件,还应当同时配置一个 SMTP 服务;在这种情况下,用 docker-compose 把 Huginn 与 SMTP 服务组织在一起是再自然不过了。通过如下的 docker-compose.yml 配置从 Docker 上启动整套 Huginn 服务(参考了这篇文章):

version: '3'

services:
  huginn:
    image: huginn/huginn:latest
    container_name: huginn
    restart: always
    ports:
      - "3000:3000"  # 对外的 Web 端口
    volumes:
      - huginn-data:/var/lib/mysql  # 数据保存到 huginn-data 卷中
    environment:
      EMAIL_FROM_ADDRESS: "agent@example.com"  # 发信人地址
      TIME_ZONE: "Beijing"  # 时区一定要设正确!
      SMTP_USER_NAME: "none"
      SMTP_PASSWORD: "none"
      SMTP_PORT: "25"
      SMTP_SERVER: "mail"
      SMTP_AUTENTICATION: "none"  # 使用自建的 smtp 服务,无需验证
      SMTP_ENABLE_STARTTLS_AUTO: "true"

  mail:
    image: namshi/smtp
    restart: always
    environment:
      RELAY_NETWORKS: :0.0.0.0/0  # 允许任何人发送邮件

volumes:
  huginn-data:  # 创建名为 huginn-date 的数据卷

通过 docker-compose up -d 启动服务后,在服务器上访问 http://localhost:3000,以默认的用户名 admin 和密码 password 访问,打开设置面板。第一件事情,当然是修改自己的用户名、密码,顺带着也可以修改一下自己的邮箱——后面会用到。

Huginn 登录页面

Huginn 快速入门

Huginn 机制简介

前面已经说过,Huginn 中的基本工作机制是若干个模块,这些模块被称作 Agent(智能体),它们好比流水线上负责不同工作的工人。若干个接替完成一项任务的 Agent 可以编组,称作 Scenario(情景,流水线)。Agent 之间通过 Event(事件,可以理解为数据包)相互通信,由此完成一整个复杂的任务。

就本篇中选定的案例(见前文)而言,大概会需要这样几个 Agent:

  • Agent 1:负责每 1 个小时在教务处网站的「通知列表」中抓取通知正文的链接,将新发现的链接以 Event 的形式发出去;
  • Agent 2:打开前面收到的链接,进入到通知正文的页面中,抓取标题、正文内容、发布时间等信息,将抓取到的信息发出去;
  • Agent 3:将前面发来的正文信息稍做处,如:调整日期的格式、将文中的相对链接改为绝对链接等;
  • Agent 4:将前面处理好的各条通知信息汇总,合成一个数据对象——这个过程称为 Digest(聚合),这样就可以统一放在一封邮件里发送了;
  • Agent 5:将前面合并得到的一整组通知编成一封邮件,发送到你的邮箱中。

上面几个 Agent 所形成的流水线如下所示。

graph LR A[Agent 1] -- 正文链接 --> B[Agent 2] A -. 抓取 .-> X[[通知列表网页]] X -- 正文链接 --> A B -- 正文内容 --> C[Agent 3] B -. 抓取 .-> Y[[通知正文网页]] Y -- 正文内容 --> B C -- 处理后信息 --> D[Agent 4] D -- 聚合的信息 --> E[Agent 5] E -- 邮件 --> F[(邮箱)]

Agent 配置

在 Huginn 中,配置一个 Agent 在逻辑上需要三步:

  1. 从 Huginn 提供的 Agent 类型中找到自己需要的那种;
  2. 为该 Agent 填写 JSON 格式的配置信息;
  3. 将该 Agent 接入流水线中,并与其他的 Agent 连接起来,形成信息的收发关系(或由一个 Agent 控制另外一些的行为)。

在 Huginn 中,提供了种类丰富的 Agent,具体信息请参见其 Wiki 文档,中文网络中也可以搜到部分内容的翻译整理。以我们当前所要处理的问题——抓取网站并发送邮件——为例,我们会需要以下几类 Agent:

  • Website Agent:用于从网页上抓取信息,汇集为 JSON 格式的数据;
  • Event Formatting Agent:用于处理其他 Agent 发来的 JSON 格式的数据,为其增减条目或调整内容;
  • Digest Agent:用于汇集若干份来源相同的数据,合并为一整个数据包(相当于把若干个 JSON 对象合并到一个数组中);
  • Email Agent:用于将收集到的 JSON 数据以邮件的形式发送出去。

对应到上面所举的例子中,Agent 1 与 Agent 2 都是 Website Agent,Agent 3 是 Event Formatting Agent,Agent 4 是 Digest Agent,而 Agent 5 则是 Email Agent。所需要的信息大致列表如下:

Agent 类型 配置信息 / 接收的信息 发送的信息 备注
1 Website Agent 通知页面的链接、抓取规则 最新通知的链接 定时抓取
2 Website Agent 通知正文的链接、抓取规则 正文的内容  
3 Event Formatting Agent 正文内容(JSON 数据) 处理后的正文内容  
4 Digest Agent 若干份正文内容(JSON 数据) 一整个正文数据包 定时处理
5 Email Agent 包含若干份正文的数据包 HTML 格式的邮件 发送到自己的邮箱

注意,在构成这个 Scenario 的 5 个 Agent 中,Agent 1 与 Agent 4 是定时运行的,而剩下 3 个 Agent 则在接收到前一个 Agent 发来的数据后立即运行。

布置流水线

接下来,就按照着上面的构思,配置一个完整的流水线。首先,登录 Huginn,进入到欢迎页面。看一看页面上的功能按钮;其中,Crendentials 与 Services 是用于访问一些特定应用的,在我们的情景中用不到。

欢迎页面

要配置流水线,就首先得创建它。切换到 Scenarios 页面,让我们在系统初始流水线外创建一条属于我们自己的流水线。在创建过程中,无需为其加入 Agent,毕竟现在还没有创建它们;在之后创建 Agent 时,再将它们关联到流水线里去。

流水线创建

提示:流水线并不起到任何实际的功能,它的主要作用是将若干相关的 Agent 组织在一起、一同查看,相当于各种网站上提供的「标签」功能。

创建完成之后,会跳转到流水线的详细页面,可以看到这条流水线上还没有任何 Agent。接下来,就开始逐次创建这个流水线上的 5 个 Agent 吧——点击下方的「New Agent」按钮即可。

Agent 1:通知列表抓取

它负责抓取教务处的「综合通知」列表:http://dean.xjtu.edu.cn/jxxx/zhtz.htm。配置信息按如下所示的方法填写。

Agent 1 配置

以上内容中需要说明的是抓取规则。Huginn 支持用 CSS 选择器和 XPath 两种方式选择抓取元素,两者互有利弊:CSS 选择器更方便、容易选中所需的元素,而 XPath 则适用范围更广(特别是对于那些没有好好写 idclass 的网站而言)、更精确。对于我们要处理的教务处网站来说,CSS 选择器就足够了,可以用浏览器的开发者工具确认匹配通知链接的 CSS 选择器为 .list_main_content a。这里的情况比较简单,在更复杂的页面中则要仔细分析,最好先测试一下。

CSS 选择器的确认

选项输入完成之后,可以点 Dry Run 按钮测试一下。Dry Run 的作用是将给定的输入(来自于当前 Agent 的 Sources)馈入 Agent 中,模拟其输出。对我们当前设置的 Agent 来说,它没有任何输入,只是自动到网页上去抓取内容,因此在选择输入时直接留空,就可以得到预演的输出入下:

[
  {
    "url": "../info/1091/9702.htm"
  },
  {
    "url": "../info/1024/9748.htm"
  },
  {
    "url": "../info/1091/9744.htm"
  },
  ...
]

需要注意的是,这里抓取到的链接都是相对链接,在之后抓取正文时是不能正常使用的。Website Agent 内提供了更正抓取数据的一些选项,不过我们可以先不着急,到下一个 Agent 中再将相对链接修正为全局链接。

Agent 2:正文内容抓取

接下来仍然是一个 Website Agent,不过它的抓取链接由前面的 Agent 1 给定。其配置信息填写如下;抓取规则中,title 的选择方法改为了 XPath,因为其 CSS 选择器并不容易写出。具体的抓取规则,可以到任意一篇正文中确定——前提是,各篇正文的网页结构基本一致。

正文抓取 Agent 配置

上面的设置中有几点需要注意:

  1. 在我们的流水线中,要求由 Agent 1 来「控制」Agent 2,但实际上我们把 Agent 1 作为了 Agent 2 的 Sources 而非 Controllers。在 Huginn 中,只有少数几种类型的 Agent 能够作为 Controller 来控制其他的 Agent,而大多数 Agent 之间只有信息收发的关系。它们的控制关系是通过数据的传输实现的:当上游的 Agent 发来消息之后,下游的 Agent 就会自动进行处理,而不受其 Schedule 参数(时间表)的影响。

    但这种机制也有例外,比如后面要设置的 Digest Agent 就不会在上游发来消息后自动运行;这也很好理解,因为 Digest Agent 的作用是聚合信息,显然不可能来一条就「聚合」一条,而是需要积攒一定数量。因此,仍然需要为 Digest Agent 单独设定一个时间表。

  2. 与前面一个 Agent 的 mode(模式)不同,该 Agent 的模式写为 merge,意思是将前一个 Agent 抓取到的信息与当前抓取到的信息合并。比如,在现在的抓取规则中,并没有 url 这个字段,因为前面的 Agent 1 已经抓取过了,会合并到现在这个 Agent 的输出数据中去。

    当然,对于 Agent 2 而言,它也没有办法在正文网页上抓取到当前页面的链接。

  3. 当前 Agent 的 url 字段写作 {{ url | replace: '../', 'http://dean.xjtu.edu.cn/' }},这段内容的大致意思是将 Agent 1 发来的 url 之相对链接替换为绝对链接。

    这里用到了 Liquid 模板语言的语法,玩过 Jekyll 的朋友对这个东西一定不陌生。如果你不熟悉这种写法,可以参考一下 Liquid 官方文档

  4. 对于正文中的 titledate 信息,我们没有采用 CSS 选择器,而是用了内容更复杂的 XPath,这项信息可以通过浏览器的开发者工具生成。特别是 date 的抓取规则,异常复杂,这主要是因为正文页面中的日期信息填在了一个偏僻的文本节点里,按通常的方法几乎无法选中,所以采用了名为 following-siblings 的 XPath 语法(参见这篇问答)。

在 Agent 1 已经有输出数据(Events)的前提下,用 Dry Run 功能选择任意一条 Event 测试一下,得到这样的内容:

{
  "url": "../info/1095/9681.htm",
  "title": "关于2020年“我最喜爱的老师”评选结果的公示",
  "content": "<style><\/style>\r\n<p style=\"text-align: center; line-height: 28pt;\"><br><\/p>\r\n<p style=\"text-align: center; line-height: 28pt;\"><strong><span style=\"font-family: 宋体; font-size: 18pt;\"> <\/span><\/strong><\/p>\r\n<p style=\"text-align: center;\"><span style=\"color: black; font-family: 宋体; font-size: 21px;\">                                      <\/span><\/p>\r\n<p style=\"line-height: 3em;\"><span style=\"font-family: 宋体,SimSun; font-size: 21px;\">各学院(部、中心)及有关单位:<\/span><\/p>\r\n<p style=\"line-height: 3em; text-indent: 32pt;\"><span style=\"font-family: 宋体,SimSun;\"><span style=\"font-size: 16pt;\">根据《关于开展2020“我最喜爱的老师”评选表彰活动的通知》(西交教〔2020〕62号)文件,我校面向全体本科生组织开展了2020年“我最喜爱的老师”评选工作。经各<\/span><span style=\"color: black; font-size: 16pt;\">书院本科生提名、材料网络展示、学生网络投票等程序<\/span><span style=\"font-size: 16pt;\">,拟确定王晓坡等10名教师为2020年“我最喜爱的老师”,现予公示(见附件),公示期为12月8日-12月10日。<\/span><\/span><\/p>\r\n<p style=\"line-height: 3em; text-indent: 32pt;\"><span style=\"font-family: 宋体,SimSun; font-size: 16pt;\">若对本次评选结果有异议,请于公示期内联系教务处。<\/span><\/p>\r\n<p style=\"line-height: 3em; text-indent: 32pt;\"><span style=\"font-family: 宋体,SimSun; font-size: 16pt;\">联系人:司婧<\/span><\/p>\r\n<p style=\"line-height: 3em; text-indent: 32pt;\"><span style=\"font-family: 宋体,SimSun; font-size: 16pt;\">联系电话:82668306<\/span><\/p>\r\n<p style=\"line-height: 3em; text-indent: 32pt;\"><span style=\"font-family: 宋体,SimSun; font-size: 16pt;\">邮箱:sijing@xjtu.edu.cn<\/span><\/p>\r\n<p style=\"line-height: 3em; text-indent: 22.5pt;\"><span style=\"font-family: 宋体,SimSun; font-size: 16pt;\"> <\/span><\/p>\r\n<p style=\"line-height: 3em; text-indent: -48pt; margin-left: 77.8pt;\"><span style=\"font-family: 宋体,SimSun;\"><span style=\"font-size: 16pt;\">附件:<\/span><span style=\"font-size: 16pt;\">2020<\/span><span style=\"font-size: 16pt;\">年“我最喜爱的老师”获奖名单<\/span><\/span><\/p>\r\n<p style=\"line-height: 3em; text-indent: 22.5pt;\"><span style=\"font-family: 宋体,SimSun; font-size: 16pt;\">                                          <\/span><\/p>\r\n<p style=\"line-height: 3em; text-indent: 22.5pt;\"><span style=\"font-family: 宋体,SimSun; font-size: 16pt;\"> <\/span><\/p>\r\n<p style=\"line-height: 3em; text-indent: 287.2pt;\"><span style=\"color: black; font-family: 宋体,SimSun; font-size: 16pt;\">         教务处<\/span><\/p>\r\n<p style=\"line-height: 3em; text-indent: 288pt;\"><span style=\"color: black; font-family: 宋体,SimSun; font-size: 16pt;\">         学生处                          <\/span><\/p>\r\n<p style=\"line-height: 3em; text-indent: 295.2pt;\"><span style=\"color: black; font-family: 宋体,SimSun; font-size: 16pt;\">         团委<\/span><\/p>\r\n<p style=\"line-height: 3em; text-indent: 31.5pt;\"><span style=\"color: black; font-family: 宋体,SimSun; font-size: 16pt;\">                       2020年12月8日<\/span><\/p>\r\n<p style=\"line-height: 28pt;\"><span style=\"font-family: 宋体,SimSun;\"> <\/span><\/p>",
  "date": "2020-12-08"
}

你会注意到:学校做的网页内容属实格式混乱;url 字段又「变回」相对链接了。这容易理解,因为我们前面用 Liquid 标签替换的只是 Agent 2 抓取网页所用的链接,而最终发出的数据中的 url 仍然是由 Agent 1 提供的(通过 merge 功能)。因此,如果之后需要在邮件中附上「原文链接」,就需要再把这个 url 替换一次。

Agent 3:数据处理

接下来需要安排上一个 Event Formatting 来清理一下由 Agent 2 抓取到的正文数据。究竟做哪些工作,取决于实际需求;比如,我会希望做这样几项工作:

  1. 把抓取到的日期 date 改写为「X 年 X 月 X 日」的形式;
  2. 把有问题的 url 修正为绝对链接;
  3. 正文中乱糟糟的 HTML 样式全部去除,只保留文本;并且,邮件中只想看通知的摘要,故只保留正文内容 content 的前 200 个字。

以上的这些要求,可以按照 Liquid 模板语言的语法写出来:

  1. {{ date | date: "%Y 年 %m 月 %d 日" }}(参考:data
  2. {{ url | replace: '../', 'http://dean.xjtu.edu.cn/' }}(参考:replace
  3. {{ content | strip_html | truncate: 200 }}(参考:strip_html & truncate

另外,前面输入的标题不需要替换,也需要采用 Liquid 的语法写为 {{ title }}

思路既已理清,直接填到配置表里去就可以了,如下图所示。以上的替换规则,都需要写在 instruction 对象里;如果你需要新增任何的条目,同样也可以写在 instruction 中,其内容可以是通过 Liquid 模板处理后的输入数据,也可以是你自行输入的其他内容(比如新增一个 source 字段,内容写做 教务通知,表示这条信息的来源)。

Event Formatting Agent 配置

Dry Run 一下前面抓取到的网页,现在变成了这个样子:

{
  "date": "2020 年 12 月 08 日",
  "url": "http://dean.xjtu.edu.cn/info/1095/9681.htm",
  "title": "关于2020年“我最喜爱的老师”评选结果的公示",
  "content": "\r\n\r\n \r\n                                      \r\n各学院(部、中心)及有关单位:\r\n根据《关于开展2020“我最喜爱的老师”评选表彰活动的通知》(西交教〔2020〕62号)文件,我校面向全体本科生组织开展了2020年“我最喜爱的老师”评选工作。经各书院本科生提名、材料网络展示、学生网络投票等程序,拟确定王晓坡等10名教师为2020年“我最喜爱的老师..."
}

果不其然,内容一下子干净了很多,日期、链接等也都改正了。当然,你还可以再做其他事情,比如将上面凭空多出来的 \r\n 也一起替换掉——前面已经说过了,这取决于你的实际需求。

Agent 4:内容聚合

前面三个 Agent 发来的都是单条的通知。为了在一封邮件中塞下一天之内收到的所有通知,需要用一个 Digest Agent 将前面发来的所有数据聚合起来。

Digest Agent 的配置如下,基本上只需要设置时间参数。这里的 message 字段,我们没有用到;如果你需要为聚合的数据设置任何附加信息(如:聚合的时间、信息来源等),可以用 Liquid 的语法填入。

Digest Agent 配置

在 Digest Agent 的配置信息中,有一项 Retained Events,表示每次聚合时的数量下限。例如,设其值为 6,假如到某天聚合时总计只收集到了 4 条信息,则这次 Agent 不会执行聚合,等到下一天「攒」够 6 条为止。在当前的情景中,我们需要每天及时的收到通知,不需要控制每天发来的通知数量,所以将其设为默认值 0(表示不设下限)。

Agent 5:发送邮件

信息聚合完成之后,就该通过邮件发送出去了。这一步需要用到 Email Agent,先看看 Email Agent 的配置指南:

You can specify the email's subject line by providing a subject option, which can contain Liquid formatting. E.g., you could provide "Huginn email" to set a simple subject, or {{subject}} to use the subject key from the incoming Event.

By default, the email body will contain an optional headline, followed by a listing of the Events' keys.

You can customize the email body by including the optional body param. Like the subject, the body can be a simple message or a Liquid template. You could send only the Event's some_text field with a body set to {{some_text}}. The body can contain simple HTML and will be sanitized. Note that when using body, it will be wrapped with <html> and <body> tags, so you do not need to add these yourself.

You can specify one or more recipients for the email, or skip the option in order to send the email to your account's default email address.

太长不看,总结一下:

  • subject 字段:设置邮件的主题(标题)。
  • headline 字段:设置邮件正文中的标题。
  • body 字段:可选,设置邮件正文的内容,HTML 格式;如果不设置的话,邮件的内容就是将前面输入的 JSON 数据的各个字段直接罗列出来(那一定很难看)。
  • reccipinets 字段:可选,指定邮件的收件人;如果不设置的话,就默认发送到当前 Huginn 账户的邮箱里。

为了定制邮件的样式,关键就在写好这个 body 字段,它的作用就相当于 Jekyll 中放在 _layouts 下的 HTML 模板。下面以我自己编写的一个简单模板为例,你也可以充分发挥自己的创意——比如,把现成的全套样式(如 Bootstrap)直接塞到模板里去。

<!-- 首先定制正文的样式 -->
<style>
html { padding: 10pt; }
body { font-size: 14pt !important; }
#mainbox { max-width: 960px; margin: auto; }
h1 { border-bottom: 1px solid rgba(0, 0, 0, 0.5); }
h2 { border-bottom: 1px solid rgba(0, 0, 0, 0.5); }
.digest-message { border: 1px solid rgba(0, 0, 0, 0.5); margin-bottom: 2ex; border-radius: 10px; padding: 1em; }
</style>

<!-- 然后确定 HTML 框架 -->
<div id="mainbox">
  <h1>教务处通知</h1>
  <p>本次共收到 {{ events | size }} 条信息。</p>
  {% for event in events %}
  <div class="digest-message">
    <h2>{{ event.title }}</h2>
    <p>发布时间:{{ event.date }} | <a href="{{ event.url }}">阅读原文</a></p>
    {{ event.content }}
  </div>
  {% endfor %}
</div>

上面用到了一个新的字段 events,它就是前面由 Digest Agent 发来的整体数据包;要想获取到其中的每一条信息,可以用上面所展示的 for 语法遍历。

将这里总结出来的信息填到 Email Agent 的配置中去,如下图所示。配置完成之后,在前面的 Digest Agent 已经聚合产生一些数据的前提下,可以通过 Dry Run 按钮模拟发送邮件——注意,与其他的 Dry Run 不同,这里的 Dry Run 真的会发送邮件(所以并不是很 Dry)……

Email Agent 配置

最终发送出来的邮件效果如下:

邮件预览

看起来还比较干净。你可以在上面所列出的模板之基础上进一步加工改造,让邮件的显示效果更好——如果你对此很在乎的话。

关于邮件的补充说明

  1. 有些邮箱的客户端 / 网页端中对邮件的样式做了进一步处理,所以未必能够看到自己定制的样式的效果。例如,Gmail 中会用其自己的样式表覆盖掉邮件中的大部分样式,所以在那里可能就看不到如上图所示的圆角边框。
  2. 如果你试图在邮件中塞下太多内容,你的邮件也许会因为尺寸过大而被收信方拦截。因此,除非你很享受邮件客户端的阅读效果,建议你像上面的示例一样:只发送纯文本格式的正文摘要,同时附上原文的链接,毕竟你不太可能需要把每一篇文章都好好看完。
  3. Huginn 的发信人地址是通过环境变量设置的(也可以在 Email Agent 的配置中填写),在我们的例子中(用 Docker Compose 安装)写在 docker-compose.yml 里面,具体请参考前面的配置示例。无论你填写的是真实存在的域名,或是自己随便编的域名(比如 example.local 之类),邮件都是能正常发出去的,但都有可能被收信端拦截、扔到垃圾箱中,因此你可能需要将 Huginn 的发信地址写到白名单里去。
  4. 在 Huginn 中其实提供了一种 Email Digest Agent,从名字就可以看出它实现了 Digest Email + Email Agent 的功能。那么为什么我们不用它呢?因为它不能自定义邮件的样式……仅此而已。

最后……

以上只是展示了使用 Huginn 的一种途径。Huginn 能够实现的功能远超于此,你完全可以花上好几天时间,好好探索一下 Huginn 提供的各种 Agent,甚至可以自己编写特定功能的 Agent(参考:创建一个新的 Agent)。

我个人不喜欢 RSS,更喜欢使用电子邮件接收资讯。如果你希望用 Huginn 生成、管理 RSS 源,你可以阅读下面几篇前人的文章:

友情提醒:前几年曾经有通过 Huginn 抓取微信公众号 / 订阅号的策略,目前基本上都失效了——诸如网页版微信、搜狗微信搜索之类的端口已经全部被封锁了。如果你想这么做,请注意检查一下网上各种教程的发布时间,不要白白浪费时间。(2020.12.27)