通过Huginn制作微信公众号RSS源

8,418 阅读10分钟

我一直觉得手机上看公众号很不方便,有些公众号质量比较高的话,在手机上很难静下心来学习。可能看一会就有别的事打扰或者去刷短视频了。虽然微信也有电脑版,但用起来每次都要登录感觉不方便。

我想通过RSS的方式将微信公众号聚合以后在电脑上看。RSS在早几年还很流行,现在各大平台都构建了自己的信息圈,用推荐算法给用户构建信息茧房,RSS也逐渐没落,公开的优质RSS源越来越少,好在有一个开源的RSS制作工具Huginn可以用。百度上大部分有关制作微信公众号RSS的文章都已经过时,按着步骤来也不能用,所以这篇文章我记录一下我制作微信公众号RSS的过程与思考。

Huginn官网:github.com/huginn/hugi…

我用的RSS阅读器是Quiterss:quiterss.org/ 不同的桌面RSS阅读器对RSS源的XML文件解析方式不同,我经过测试QuiteRSS能与Huginn的RSS源兼容。

1 前置条件

需要一台属于自己的云服务器,我的云服务器的配置是:1核 2 GiB 的阿里云服务器一台

2 安装Huginn

Huginn的安装可以参考www.cnblogs.com/liujiangblo…这篇文章。建议用docker安装,会省去很多的麻烦。

3 制作微信公众号RSS源存在的困难

实现自己的公众号RSS源存在以下几个问题:

  1. 如何获取微信公众号的文章数据?
  2. 如何采集这些数据?
  3. 如何将采集到的数据进行处理并转化为RSS源。

3.1 如何获取微信公众号的文章数据?

据我所知有两种方法获取公众号文章数据

  1. 可以通过搜狗微信weixin.sogou.com/查询到公众号的最新文章。点击最近文章后会跳转到微信公众号文章的页面。用这个方法可以获得最新的文章,但如果一下发多条文章就没办法获取了。

  2. 在微信客户端内能看到公众号全部的文章,我认为可以用Python脚本模拟鼠标点击的方式获取文章的url,但获取技术难度太高。

我感觉第一种方法能满足我的需求,所以我用第一种方法采集。

3.2 如何采集这些数据?

这时候就轮到Huginn出场了,这篇文章www.cnblogs.com/liujiangblo…的后半部分有用huginn采集电影天堂最新电影的教程。大家可以参考一下。总的来说Huginn是一个可以采集网页信息并对信息进行处理的的工具。(文章后面的内容,我都假设读者已经掌握了Huginn的基础用法)

3.3 如何将采集到的数据进行处理并转化为RSS源?

Huginn拥有将采集到的数据转换为RSS源的功能。

4 制作流程

所有的问题都有解决方案了,那么我们开始动手制作。最终的Huginn的Scenarios图如下,我们一个一个分析。

clipboard.png

4.1 微信公众号抓取:搜狗微信页面分析

Huginn其实就是通过抓取页面元素来采集数据的。所以我们需要先对页面进行分析,通过CSS选择器定位到我们想要的元素,用why神的公众号举例。我需要获取三个信息:作者,文章名,文章URL

clipboard.png

clipboard.png 可以看到通过uigs这个属性可以定位到我们想要的信息,所以CSS selector是a[uigs=account_article_0]。如果嫌麻烦也可以用在a标签上点击右键 -> 复制 -> 复制样式(或者复制CSS selector)这种方式也能获取到。

CSS选择器大全:www.runoob.com/cssref/css-…

4.2 页面抓取Agent的写法

上节我们定位到了元素的位置,然后开始编写Huginn Agent。我们新建一个WebsiteAgent(WebsiteAgent可以抓取网页信息,Huginn还有很多其他功能的Agent,在官方文档里可以看)

clipboard.png

这个Agent的Options写法如下(如果粘贴我的,需要把注释去掉)

{
  "expected_update_period_in_days": "2",
  "url": [
  //关注公众号的列表,用数组的方式可以同时抓取多个公众号
    "https://weixin.sogou.com/weixin?type=1&s_from=input&query=hello_hi_why&ie=utf8&_sug_=n&_sug_type_=",
    "https://weixin.sogou.com/weixin?type=1&s_from=input&query=pangmenzd&ie=utf8&_sug_=n&_sug_type_="
    ],
  "type": "html",
  "mode": "on_change",
  "extract": {
    //这里面是我们想抽取的目标,title,urlcontent都是自定义的字段
    "title": {
    //huginn通过css selector采集元素
      "css": "a[uigs=account_article_0]",
      //text()的意思是获取标签内的文本
      "value": "text()"
    },
    "urlcontent": {
      "css": "a[uigs=account_article_0]",
      "value": "@href"
    },
    "author": {
      "css": "a[uigs=account_name_0]",
      "value": "text()"
    }
  },
  "template": {
  //template我理解是可以对采集到的数据进行再处理的一种方式
  //这里的_response_.url的作用是有时候href不会包括前面的域名,通过_response_.url转换后会带上域名
    "urlcontent": "{{urlcontent| to_uri: _response_.url }}",
    "url": "{{url| to_uri: _response_.url }}"
  }
}

编写好了以后可以点一下Dry Run进行测试。我的测试结果如下:

clipboard.png

可以看到这三个信息我们都获取到了。

4.3 RSS方案1

其实到了这里,我们如果不追求完美的话就可以发布RSS源了,通过huginn的DataOutputAgent发布RSS源,里面的内容就一个a标签。

clipboard.png

访问的时候点连接,利用RSS自带的浏览器跳转到访问页面即可

clipboard.png 跳转后的结果如下(有时候会进行人机验证,输一次验证码就可以了,不用多次输入)

clipboard.png 看样子似乎已经很完美了,我做出这个版本的时候已经很满意了,跳转的方式不太美观,但就和做产品一样,有时候的小的变通可以避免大量代码修改。直到第二天我发现这种方式有一个巨大的缺陷:这里的urlcontent是有超时时间的,大概十几个小时。如果今天抓的链接没来的急看的话,第二天访问时全都是过期链接页面。这是无法接受的,所以我否决了RSS方案1。

4.4 RSS方案2

否决了方案1以后,我思考了一下,如果url会超时的话,那么我只能抓取文章内容存储在服务器上,才能满足我的需求。通过查询资料我发现selenium这个工具可以通过模拟用户操作的方式获取网页信息。我在本地试了一下确实可以用(需要搭配chrome浏览器)。所以我的方案2的思路是,huginn采集到urlcontent以后,再访问服务器上搭建的selenium服务,通过selenium抓取网页信息,然后存储下来输出为RSS源。

这个方案我遇到了以下几个问题:

  1. 怎么在无图形界面的linux访问Chrome浏览器?

通过查询资料了解到有一个叫xvbf的东西,可以在linux下模拟图形界面。安装方式也很简单

yum install Xvfb -y 
yum install xorg-x11-fonts* -y

linux安装好xvbf以后还需要安装chrome浏览器和chromedriver驱动,python3.10(python版本不能太旧,我最开始安装的3.5程序就报错)

全部的安装过程,可以参考:blog.csdn.net/weixin_3086…

  1. 怎么将selenium服务暴露给Huginn? 其实很简单啦,在服务器上搭建一个rastful的服务,huginn和selenium都通过http访问传输json就可以了。百度了一下,python里有一个fastapi的框架挺简单,就用它了。fastapi需要用uvicorn和gunicorn 进行部署,同时还需要通过nginx代理暴露给外部。

4.5 Python服务的编写

我的python服务代码如下gitee.com/kagami1/hug… 我的python也是现学的,写的比较乱。这里需要注意的点是:

1 启动时需要用非沙盒模式,否则会报DevToolsActivePort file doesn't exist的错

chrome_options.add_argument('--no-sandbox')

2 不能用headless 启动,用headless模式启动会被检测出来后跳转到人机验证页面,就获取不到页面了。

3 最开始我直接访问urlcontent页面也会跳转到人机验证页面。所以我采用完全模拟用户行为的方式采集,先访问url页面再访问urlcontent页面

driver.get(req.url) 
driver.get(req.urlcontent)

4 图片信息是异步加载的,所以需要sleep短暂的时间等它加载完成后才能获得图片的url。

clipboard.png

5 finally块里记得释放资源,我有一次没有释放资源导致服务器差点卡到爆炸

driver.close()
driver.quit() 
display.stop()

6 需要安装的python库有pyvirtualdisplay ,fastapi,pydantic ,selenium,uvicorn,gunicorn

4.6 去重Agent的写法

因为我们采集是两小时一次,所以肯定会重复采集很多页面。我们需要对这些信息进行去重。所以新建一个DeDuplicationAgent,sources填刚才的抓取agent

clipboard.png

去重key我们用title,有些读者可能有疑问为什么不用urlcontent进行去重,因为urlcontent是会超时的哦,超时以后就变了,所以无法定位唯一一篇文章。title也可能重复,但这种概率比较小我选择忽略。

clipboard.png

4.7 微信公众号内容获取

这个Agent就是上文说的fastapi与huginn交互的Agent,类别是PostAgent功能是发起post请求,参数放在payload里

{
  "post_url": "http://你的ip:8000/get_html",
  "expected_receive_period_in_days": "1",
  "content_type": "json",
  "method": "post",
  "payload": {
    "url": "{{url}}",
    "urlcontent": "{{urlcontent}}"
  },
  "headers": {},
  "emit_events": "true",
  "no_merge": "true",
  "output_mode": "merge"
}

获取到的内容如下:可以看到fastapi返回的消息放在body字段里

clipboard.png

4.8 微信公众号JSON解析

这个Agent需要对刚才的body进行json解析,所以采用JsonParseAgent这么填

clipboard.png

解析后的结果

clipboard.png

4.9 微信公众号最终结果

这个Agent负责将上一个Agent的imgs遍历,组装成img标签,同时还需要组装原文链接(有些文章会很长,图片会很多,这时候如果urlcontent还没失效的情况下,去原文看肯定更好)。这个agent是JavaScriptAgent可以自由编写js

Agent.receive = function() {
  var events = this.incomingEvents();
  var imgs = events[0].payload.data.imgs
  var list = []
  for(var key in imgs) {
    list.push("<img src='"+imgs[key]+"'/><br/>")
  }
  var text = events[0].payload.data.text.replace(/#/g,"<br/>")
  var url = events[0].payload.url
  var atag = "<a href='"+url+"'>原文链接</a><br/>"
  this.createEvent({ 'atag': atag, 'url': url,'author': events[0].payload.author, 'title': events[0].payload.title,'text': text,'imgs':list});  
  
}

这个agent的运行结果是

clipboard.png

4.10 微信公众号输出

最后我们想要的信息都已经有了,可以输出RSS源了。这个Agent是DataOutputAgent它的options这样写

clipboard.png

title字段我将作者名和文章名进行了拼接,方便识别。 description字段我将原文链接放在了最开始,然后是文章的文字和图片

4.11 最终效果

clipboard.png

文章后面是图片

clipboard.png

这样也不是很完美,因为文字应该和图片按照顺序出现。但我对python的运用不太熟练加上春节时间不多。所以我用了selenium自带的text方法偷懒,这个方法可以直接获取到元素内所有的文字。其实如果遍历这个元素,肯定能将图片和文字调整好,按照顺序出现。

return resp_200(data={
    "imgs": list,
    "text": article.text.replace("\n","\\n")
})

不过现在已经能满足我的需求了。大不了还有原文链接嘛。

4.12 RSS方案3

我在浏览selenium的api的时候发现selenium有将元素保存成图片的api

clipboard.png

这时候我就有了方案3的想法,其实我们可以通过模拟点击下拉框将文章一页页截图拼接起来。这样不仅可以保留文字和图片的顺序,还可以保留文章的样式。这种方式更麻烦,但我觉得更好。因为我的服务器只有1M的带宽,加载图片很慢,所以我就没有考虑方案3。

还有一点是:方案2和方案3都有的问题是没办法处理视频链接,这个后续还需要完善。

5 最后想说

春节期间没有回家,所以闲着无聊学了学Huginn,虽然没啥用,但学习的过程中还是很快乐的,比在单位每天当CRUD boy开心多了。我其实不太会js和python还有css,但兵来将挡水来土掩,遇到问题就囫囵吞枣不求甚解的学一下。最终一点点实现了我的目标,这个过程很开心。

selenium在python爬虫中用的非常多,其实我觉得这个用来做前端测试也是很不错的。我感觉大部分公司前端测试都是人力黑盒测试(俗称牛马测试),自动化还是少。

写这篇文章的目的是希望大家能了解huginn这个神器,可以做的事很多,可以参考官网的例子,程序员嘛就喜欢折腾。