Matrix 首页推荐 

Matrix 是少数派的写作社区,我们主张分享真实的产品体验,有实用价值的经验与思考。我们会不定期挑选 Matrix 最优质的文章,展示来自用户的最真实的体验和观点。

文章代表作者个人观点,少数派仅对标题和排版略作修改。


阅读提示:本文涉及 Tasker、AI、前端、自动化,有一定技术门槛。

背景

我有个坚持,不想浪费宝贵时间在低价值信息上,所以会时常反思自己的信息来源。我感兴趣的领域,通常都能找到相应信息渠道,长期关注。但不能只盯着这些领域,也需要一扇小窗口,来偶尔了解其他领域的大事,防止画地为牢。

以往,我都是利用早晨送老婆孩子的时间,听听新闻电台,了解当天时事。这里面的信息也可以分为两类:

  1. 肯定对我无价值。如体育新闻、娱乐新闻,我一点也不关心体育和八卦;又如军事新闻,军事信息披露少、难查证,各方报喜不报忧,单从新闻报道获得结论,完全不可靠。
  2. 可能有价值,听了才知道。如社会新闻,近期消费趋势、科技发展导致的新社会现象等,有时能从中得到一些数据和洞察。当然,也有许多毫无价值,比如某豪车肇事逃逸这种,社会构成形形色色,单个个体的行为往往不值得关心。

近期巴黎奥运会,我的新闻时光几乎被奥运新闻淹没了。导致我开车时不时要瞄一眼大屏上的新闻标题,判断是不是该切下一条。有时候要连切七八条,才能轮到一则我愿意听的。这样既不安全,又让人火大。

我试过许多可以听新闻的手机 app。如果听头条频道,免不了混进这些不感兴趣的信息。如果订阅几个特定频道,又总会混入上千字的深度报道,敢情我一路就听你一条呢?更新频率的差异也是个问题,订阅的几个频道中,只要有一两个更新量极大,其他频道就相当于不存在了。

我就想,既然只瞄一眼标题就能判断要不要听,这事儿 AI 难道不能做吗?我可以继续听头条频道,只是让 AI 帮我滤掉一道,可不可行?

这个想法一冒出来,就完全停不下来了。

实现思路

仔细一想就发现,这事压根儿没什么技术含量。但就是找不到一款现成产品,可能是需求过于小众,那我就自己干吧!

首先,我要在哪实施我的构想?在电脑上写个程序当然可以,但既然听新闻绕不开手机,干脆整个流程都在手机上完成吧,摆脱对其他设备的依赖,否则我出去度个长假还听不了新闻了?所幸我长期使用 Tasker,安卓手机上的一款编程软件,我知道它能实现我想要的效果。

整个过程不复杂,就这么几步:

  1. 从新闻源获取当日的头条新闻
  2. 把新闻标题交给 AI,让它判断属于哪类新闻
  3. 过滤掉我不要的几类新闻,剩余新闻以文字形式保存下来
  4. 通过语音合成转成音频新闻,存到特定位置
  5. 以上动作做成自动任务,每天深夜执行一遍
  6. 在音乐播放器创建一个专门的歌单,读取音频新闻
  7. 做另一个自动任务,手机连上车载蓝牙启动播放器,播放新闻
  8. 再做个自动任务,每天把新闻清空,为下一轮做准备

准备轮子

以上步骤听起来像个大工程。但好在我不用自己发明轮子,其中许多能力都有现成的工具,把它们整合进来即可。现在,我得把可能用到的基础能力做成一个个小模块,也就是子任务,提前准备好,便于后续组装。

Tasker 简介

Tasker 是这些子任务的载体。它是一个手机上的自动化工具,把硬件控制、数学运算、文件操作、网络请求、判断/循环等能力都打散成原子级别,让你自由组合,构建各种各样的自动化工作流。折腾过 iPhone 快捷指令的朋友应该熟悉这套玩法,只是 Tasker 远比快捷指令强大得多。把它归为自动化工具是低估了它,它实际上是个编程软件。

最基础的用法是根据条件来控制手机硬件,比如连上公司 WiFi 自动静音、连上车载蓝牙启动音乐播放器,这类效果做起来轻轻松松。高级一些的用法,涉及文件操作、网络请求,则需要有编程的思维,但并不需要真的写代码。

网络获取内容

第一个子任务需要具备上网的能力,才能浏览新闻源。

输入:新闻源链接
输出:包含新闻列表的代码

它用到了 Tasker 内置的 HTTP 请求,我没做任何额外处理,只把从新闻源获得的信息原封不动传递给外层任务。为什么要包这么一层,而不是直接用呢?这和子任务的执行优先级有关系,后面组装轮子的时候我会再讲。

解析 XML

从 RSS 新闻源获得的不是直接能读的新闻,而是一堆 XML 代码,其中包含新闻列表。

RSS 遵循一种通用的格式,无论哪个新闻源,一条新闻都对应一个 item,它的标题、链接、描述分别对应 title、link、description。标准的格式,就有标准的办法从中提取信息。

但在解析之前,我还加了另一个子任务,用来规整 XML 代码的格式。这里需要一点前端知识,因为网页里有时候会遇到代码被写成转义字符的情况,比如左尖括号<被写成&lt;、右尖括号>被写成&gt;。这个子任务可以把转义字符变回常规符号,便于统一处理。

输入:包含转义字符的 XML 代码
输出:标准的 XML 代码

下面该解析 XML 了。这个子任务可以从一堆 XML 中找到所有相邻的特定标签,提取出它们的内容,每个标签用|||分隔开。

输入:完整 XML 代码、要提取内容的标签
输出:所有该标签里的内容

在我的程序里,我需要它找出所有 item 里的内容,也就是获取整个新闻列表。外层任务调用它时,把 item 作为第 2 参数(%par2)传给它,就能得到所有新闻条目的内容,并且以|||分隔开,便于外层任务进一步拆分处理。

从 HTML 提取内容

刚才的子任务能解析新闻列表,但其中只有标题和链接是真正有用的。RSS 新闻源虽然格式统一,各家对于 description 却有不同理解。有的新闻源把全文都写在了 description 里,有的只在这写了摘要,正文藏在详情页里。

这个子任务就是为了干这个。给它一个页面的完整 HTML 代码,再告诉它要提取哪个标签的内容,它就能取出来,把不相干的菜单、评论、广告、页头页尾全撇掉。

输入:完整 HTML 代码、要提取内容的标签
输出:第一个该标签里的内容

这个子任务为何这么复杂?因为它要处理 HTML 标签层层嵌套的情况,这里涉及的前端知识不展开讲了。简单说就是它找到了标签的结尾在哪里,确定了提取内容的范围。整个过程都是靠字符串拆分、替换、拼接来完成的,实现了 JavaScript 里 innerHTML 的能力。

取出来的正文内容仍然是 HTML 代码,这就需要另一个子任务来把 HTML 转成纯文本。这是 Tasker 自带的能力。

输入:HTML 代码
输出:文本内容

AI 判断新闻类型

前面的子任务是获取、加工内容的基础,但关键的筛选能力还得靠这个子任务,这是整个程序的脑子。

输入:要发给 AI 的内容、AI 模型名称
输出:AI 的回复

Groq 的 API 真的是个好东西,里面有许多好用的开源 AI 模型。查阅它的文档,调用这些 AI 模型非常简单。向它发一些文字,它再把生成的文字回给你。等待 2 秒是因为 API 有请求限制,一分钟内最多调用 30 次。

文本转语音

这个子任务把文本文件批量转成音频文件保存。

输入:文本文件所在目录、音频文件保存目录
输出:一批音频文件

关键步骤用到了 Tasker 自带的 Say To File,文本存为音频文件。需要注意的是,Say To File 只是提供了这种操作,合成过程需要的语音合成引擎,Tasker 并没有内置。

我用了谷歌的本地语音合成引擎,Google Play 下载这个 App,就能在 Tasker 里调用。

实测发现,本地免费语音合成引擎,效果大概只能达到地图软件默认语音包的水准。谷歌这个算其中比较优秀的了,甚至比讯飞的好,尽管还是很生硬。

组装轮子

几个轮子准备好了,大多难题都已解决,该组装了。

下载并筛选新闻

先组装出核心任务,它能从单个新闻源下载新闻,筛选后保存为文本文件,完成整个程序里绝大多数工序。

输入:新闻源地址、详情页正文所在 HTML 标签
输出:一批新闻文本文件

我在输入的第 2 个参数上留了个小彩蛋。输入的如果是<description>,则不去新闻详情页获取正文,而是直接把 XML 里的 description 当做正文。这取决于每个新闻源的性质和数据质量,可以定义在它的外层任务上。

从新闻源获得完整 XML 代码,把转义字符规整成标准 XML,去掉一些特殊的内容标记。然后提取新闻列表。

新闻列表根据分隔符分成数组,设定好 AI 提示词,设定正文长度上限(过滤掉太长的正文)。开始循环,每条新闻从 XML 里读出标题,标题转成纯文本,交给 AI 分类。

AI 的提示词我是这么写的,没用到什么技巧,直白说出需求就行。由于这里处理的都是中文信息,Groq 上的 Gemma2 9b 模型比较适合,比 Llama 3.1 的中文能力强。这种简单需求,开源小模型足以胜任。实际使用效果很好,没出过错。

根据 AI 分的类型来判断,过滤掉体育/娱乐/军事新闻。再从 XML 得到新闻详情页链接,顺藤摸瓜取得详情页完整 HTML,规整代码格式,根据正文所在 HTML 标签取出其内容。

把正文 HTML 代码转成文本,判断正文长度,太长的过滤掉,太短的可能是图片新闻也过滤掉。剩下的作为文本文件存到特定目录里。

优先级问题

调试核心任务的过程中,很多次出现取不到内容的情况,卡了很久。深入研究找到了原因:原来子任务的执行竟然是并行的!

Tasker 的灵魂是它的 Perform Task,作用是在当前任务里执行一个子任务。执行时可以把当前任务的信息传递给子任务,并获得子任务处理后的结果。

传入参数,获得返回值,这不就是编程里的函数吗?虽然 Tasker 有限制,最多只能往子任务里传 2 个参数,但如果把多个参数用特定分隔符拼接成字符串,传到子任务里再拆分开,理论上多少个参数都能传进来。用这种结构层层嵌套,什么复杂的逻辑做不出来?Perform Task 的存在,使 Tasker 成为一款编程软件。

仔细阅读了 Perform Task 的帮助文档,里面提到了执行顺序问题。触发子任务时,外层任务并不会等子任务执行完再继续(我一直这么以为),而是并行执行下去了。我的程序中,许多子任务要去网上获取内容,或对页面代码进行大量的循环处理,耗费时间很长。在子任务给出处理结果前,外层任务继续执行,当然就接不上了。

按照帮助文档里建议的做法,把子任务 Priority 属性设为 %priority+1,让子任务的优先级数值比外层任务多 1,这样外层任务就会等子任务执行完才继续。

多渠道下载新闻

呼~ 好长一个任务写完了,现在来调用它。

把我选出的几个 RSS 新闻源传递给核心任务,从哪里取正文也告诉它。每个新闻源都执行一次。

再单独做一个批量转语音的任务,把文本新闻的目录和音频新闻的目录都告诉它,让它往音频新闻目录里输出。

定时下载并转语音

上面都是任务,怎么启动它们呢?切换到 Tasker 的 Profiles 页面,这里可以为任务添加各种各样的触发条件。

每天凌晨 4 点,把新闻都存成文本文件。这个过程要 5-10 分钟。

每天凌晨 5 点,把文本新闻转成音频。

最终效果

这样我一觉醒来,News 目录下就有两个文件夹。

text 保存了文字版新闻,如果有需要我还能二次分享出去。

audio 文件夹里是音频新闻。虽然还有一些没什么意思的社会新闻混在其中,但这不能怪AI,至少我再也没有听到过体育新闻了。

手机上的音乐播放器里新建了一个叫每日新闻的歌单,专门读取 audio 文件夹。

更新一下内容,当天新闻就都来了。这个更新过程仍然需要手动点一下,我还在找自动化的办法。

播新闻也是自动的。早晨连上车载蓝牙,播放器就自动打开了,而我用的 AIMP 播放器能设置打开自动播放,这下就完全不用动手了。

最后,我还有另一个自动任务,每天凌晨 3 点把新闻文件夹清空,为下一轮任务做准备。

后记

用了几天自制的新闻头条程序,这下舒坦了,开车不用分心了。除了语音比较生硬之外,其他毛病没有。语音嘛也许等哪天我受不了了,就再找个效果好的付费 TTS API,把 Say To File 这一步替换掉就可以了。

一番操作下来,不仅解决了我生活中的问题,还积累了一些有用的子任务。我在制作网络获取内容、解析 XML、从 HTML 提取内容、向 AI 提问这些子任务时,充分考虑了通用性。未来还能组装出其他程序,在手机上轻松实现各种网络爬虫,甚至 AI agent。手机上的网络爬虫真的香,没有任何服务器费用,还能实现全天候运行,以后有具体需求再折腾吧。

> 关注 少数派小红书,感受精彩数字生活 🍃

> 实用、好用的 正版软件,少数派为你呈现 🚀