RSS Can:使用 Golang 实现更好的 RSS Hub 服务(一)

news2024/11/17 0:06:14

聊聊之前做过的一个小东西的踩坑历程,如果你也想高效获取信息,或许这个系列的内容会对你有用。

这个事情涉及的东西比较多,所以我考虑拆成一个系列来聊,每篇的内容不要太长,整理负担和阅读负担都轻一些。

本篇是第一篇内容。

写在前面

两个月前,我创建了一个新的项目 “potted”,尝试使用 Golang 写了一个小工具,用来取代之前使用的基于 Node 编写的 RSS Hub,在折腾过程中做了一些比较关键的技术点验证。

Project: Potted

在得到了确定答案之后,我觉得是一个合适的时间点,将这个点子变成一个新的开源的工具项目:RSS Can(RSS 罐头),项目的地址是:soulteary/RSS-Can 。

项目中的代码,将会伴随文章更新而更新,如果你觉得项目有趣,欢迎“一键三连”。当然,如果你觉得这个事情有价值,也有趣,也欢迎加入项目,一起折腾。

在验证过程中,折腾了一些有趣的实现,比如:

  1. 让 Golang 在运行时能够结合 JS 来实现动态配置(你当然也可以结合 Python、Lua 等你自己喜欢的语言),能够灵活选择哪种语言,来让“静态语言”的产物更“灵动”一些。
  2. 能够灵活的从各种网络环境获取不同网站的资讯内容,以及支持不同技术方式生成的页面信息内容,比如服务端生成、客户端生成(比如 JS)。
  3. 不仅仅输出 RSS 格式数据,也能够输出 JSON 数据,用来形成 Info Pipeline,让信息最终呈现结果,可以经过 AI 服务的处理,再提供给我来阅读使用。
  4. 相比较 Node 服务,更低的运行资源诉求,可以将这个服务运行在廉价的设备或者云主机上,有效降低运行、运维成本。

如果你本地没有 Golang 环境,可以阅读《搭建可维护的 Golang 开发环境》、《M1 芯片 Mac 上更好的 Golang 使用方案》这两篇文章,来快速搞定开发环境。如果你还没有使用过 RSS,也可以阅读 RSS 标签下的文章,来体验下算法推荐之外的定向获取信息的方式。

先来聊聊最基础的,对于传统网站的信息获取和整理。

信息阅读的痛点

我用一个“偶尔会看”的网站 36Kr 为例,在聊具体技术实现之前,先来聊聊我遇到了哪些问题。

36Kr 上有一些专业编辑写出来的稿子还是很棒的,尤其是和我关注领域重合的时候。但之所以我说只是“偶尔使用”,我个人认为原因主要有下面三个:

首先,网站资讯更新还是比较多的,需要在一堆消息里挑选自己感兴趣的内容,要花费不少时间。

但是,让我每天定时上网站搜索,看看有没有新的内容,这样做效率太低了,难以坚持。我希望得到的信息,至少是能够根据关键词进行筛选出来的。

混杂各种类型的信息列表

第二,我常使用的 RSS 工具 无法直接解析它官方的 RSS 源 ,并且官方的 RSS 源里,也没有很好的进行子版块的消息分类。如果,用户想使用 RSS 的方式来获得子版块消息,那么只能靠 DIY 了。

标准输出的网站,一键订阅

无法被 Reeder 直接订阅的 36kr

最后,有一些各种平台都发的“通稿”的内容,我希望他们能合并起来。内容平台生存不易,即使是头部的科技媒体,也一定需要接商单。作为用户可以帮助平台消化内容分发,但是 老看到各种信息来源都会重复出现的内容,慢慢就不想看了

上面的问题,不单是 36Kr 存在,甚至第三条都不是 36Kr 的问题,其他的平台也存在。但是,作为一个关注各种渠道信息动态的人来说,如果官方没有提供用户信息合并能力,不能提供全网的信息(还不是一个权威信息 Hub),想提升阅读体验,也就只能靠 DIY 啦。

使用 Go Query 实现基于页面的信息抽取

因为我们关注的信息来源于网页的列表内容,所以我们可以使用程序来解析列表内容,并进行筛选,得到我们想要的信息。

比如,我们可以通过在网页上右键,打开调试工具的窗口,然后在“元素”选项卡里,先找到包含信息的列表元素。

选择包含信息的元素

接着,在元素上右键选择复制“Selector”,得到程序快速解析必要列表信息的“路径规则”。

获取某个元素的“访问路径”

如果我们将内容粘贴出来,大概是这样的:

#app > div > div.kr-layout-main.clearfloat > div.main-right > div > div > div.kr-home-main > div.clearfloat > div.kr-home-flow > div.kr-home-flow-list > div:nth-child(1) > div

其实,有一些开源工具关于元素的可视化选择、以及路径生成已经做的很好了,比如 uBlock 里的元素选择等等。因为这个话题比较大,我们后面的文章再展开。

在得到元素路径之后,我们可以考虑进行一些调整优化,并写一段简单的 JavaScript 代码,来验证程序是否能稳定获取到信息:

document.querySelectorAll("#app .main-right .kr-home-main .kr-home-flow .kr-home-flow-list .kr-flow-article-item")

我们将上面这段程序扔到网页的“控制台”中执行,验证是否能够“圈选”出我们想要的信息列表。

验证“规则”是否正确

因为类似 36 Kr 这类传统一些的内容网站,使用的都是基于服务端渲染的方式提供内容,用大白话说就是 HTML 页面内容中包含了我们想要的信息。所以,我们可以实现程序通过解析网页 DOM 结构,来快速抽取页面中的关键信息,然后整理成 RSS 信息源或者 API,搭配 RSS 阅读器或者其他的工具进行进一步的数据分析,最后进行最终内容呈现或者进行消息推送。

选择 Go 作为基础技术栈之后,我们可以使用生态中的开源项目PuerkitoBio/goquery,来针对页面内容进行解析,为自己整理有价值的信息。

参考项目的例子,我们不难写出下面的程序:

package main

import (
	"fmt"
	"log"
	"net/http"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

func getFeeds() {
	// Request the HTML page.
	res, err := http.Get("https://36kr.com/")
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
	}

	// Load the HTML document
	doc, err := goquery.NewDocumentFromReader(res.Body)
	if err != nil {
		log.Fatal(err)
	}

	// Find the article items
	doc.Find("#app .main-right .kr-home-main .kr-home-flow .kr-home-flow-list .kr-flow-article-item").Each(func(i int, s *goquery.Selection) {
		title := strings.TrimSpace(s.Find(".article-item-title").Text())
		time := strings.TrimSpace(s.Find(".kr-flow-bar-time").Text())
		fmt.Printf("Aritcle %d: %s (%s)\n", i+1, title, time)
	})
}

func main() {
	getFeeds()
}

将上面的程序保存为 main.go,然后执行 go run main.go 将会得到下面的异常退出的结果:

2022/12/12 13:50:56 unexpected EOF
exit status 1

程序看起来正常,但是执行后却没有返回预期内的结果。我们可以简单思考下为什么会出现这个问题,“变量在哪里”。

因为我们无法得到目标网站的代码,所以只能进行推测:我们使用浏览器能够访问信息,但是使用程序却不能访问信息,这个场景下主要的差异点之一在于网络请求中的 User Agent (客户端标识)不同,网站前端服务器过滤掉了非“浏览器”的请求。

还是打开浏览器,在控制台里执行 JavaScript 代码 navigator.userAgent,得到我们自己的浏览器的信息:

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36

获取我们自己浏览器的 UA 信息

接着,调整下上文中面发起网络请求的部分,让程序在发起请求的时候,让程序能够携带上我们的浏览器 UA 信息:

package main

import (
	"fmt"
	"log"
	"net/http"
	"strings"

	"github.com/PuerkitoBio/goquery"
)

const DEFAULT_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"

func getRemoteDocument(url string) (*goquery.Document, error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}

	req.Header.Set("User-Agent", DEFAULT_UA)
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}

	if res.StatusCode != 200 {
		return nil, fmt.Errorf("status code error: %d %s", res.StatusCode, res.Status)
	}

	defer res.Body.Close()

	doc, err := goquery.NewDocumentFromReader(res.Body)
	if err != nil {
		return nil, err
	}
	return doc, nil
}

func getFeeds() {
	// Request the HTML page.
	doc, err := getRemoteDocument("https://36kr.com/")
	if err != nil {
		log.Fatal(err)
	}

	// Find the article items
	doc.Find("#app .main-right .kr-home-main .kr-home-flow .kr-home-flow-list .kr-flow-article-item").Each(func(i int, s *goquery.Selection) {
		title := strings.TrimSpace(s.Find(".article-item-title").Text())
		time := strings.TrimSpace(s.Find(".kr-flow-bar-time").Text())
		fmt.Printf("Aritcle %d: %s (%s)\n", i+1, title, time)
	})
}

func main() {
	getFeeds()
}

再次执行 go run main.go,会发现已经能够得到信息列表啦。

Aritcle 1: 为什么 Google 总是在不断地关闭产品呢? (1分钟前)
Aritcle 2: 放弃L5全自动驾驶,苹果造车能走多远? (11分钟前)
Aritcle 3: 圆桌论坛:Web3.0与元宇宙的融合趋势 | WISE2022 新经济之王大会 (23分钟前)
...

最后

接下来的内容里,我们来聊聊,如何将这些信息源转换为 RSS 阅读器可以使用的信息源,以及如何针对不同类型的网站进行信息整理。当然,还有文章中开头提到的有趣的几个技术点。

–EOF


我们有一个小小的折腾群,里面聚集了一些喜欢折腾的小伙伴。

在不发广告的情况下,我们在里面会一起聊聊软硬件、HomeLab、编程上的一些问题,也会在群里不定期的分享一些技术沙龙的资料。

喜欢折腾的小伙伴,欢迎阅读下面的内容,扫码添加好友。

  • 关于“交友”的一些建议和看法
  • 添加好友,请备注实名和公司或学校、注明来源和目的,否则不会通过审核。
  • 关于折腾群入群的那些事

本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)

本文作者: 苏洋

创建时间: 2022年12月12日
统计字数: 5798字
阅读时间: 12分钟阅读
本文链接: https://soulteary.com/2022/12/12/rsscan-better-rsshub-service-build-with-golang-part-1.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/83058.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Go代码审计学习(二)

文章目录环境搭建漏洞一:代码逻辑错误、没有做有效的鉴权漏洞二:目录穿越、任意文件读取漏洞三:条件竞争漏洞四:钩子函数执行命令参考链接环境搭建 Gitea是从gogs衍生出的一个开源项目,是一个类似于Github、Gitlab的多…

自学python第三年才懂,这事儿影响着最终的学习结果

前言 如果这件事你还没搞明白,那你学python会跟我一样,学了几年跟没学差不多! 当然,这件事仅限于学python是想赚钱或者提升职场竞争力的人明白即可,其他人别浪费时间了。 这事儿很重要 我从2018年底开始自学python&a…

SpringBoot2核心技术(基础入门)- 03 自动配置原理【3.1依赖管理+2容器功能】

1、SpringBoot特点 1.1、依赖管理 ● 父项目做依赖管理 依赖管理 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.4.RELEASE</version> </parent…

(已更新)2023春节倒计时新款HTML单页源码

2023春节倒计时新款HTML单页自适应页面&#xff0c;有兴趣的可以看看&#xff01;背景图片自己修改喜欢的&#xff01; 源码介绍 自适应页面&#xff0c;支持安卓和ioswx背景音乐播放附带多个背景音乐&#xff0c;由于技术原因&#xff0c;一些js进行了加密&#xff08;支持i…

Spring boot 使用@DS 配合druid连接池切换数据源及切换数据源失效情况

一、导入包 <!-- dynamic-datasource --> <dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.2.1</version> </dependency> 二、配置yam配置文…

【Flask框架】——09 视图和URL总结

目录一、创建flask项目1、安装环境2、创建项目3、启动程序4、访问项目二、flask参数说明1.初始化flask项目2.参数说明3.DEBUG调试模式三、应用程序的参数加载1.从配置对象中加载2.从配置文件中加载3.从环境变量中加载4.从Pycharm中的运行时设置环境变量的方式加载5.企业项目开发…

Flink-多流转换(合流,分流,双流join)

8 多流转换 8.1 分流 简单实现 对流三次filter算子操作实现分流 // 筛选 Mary 的浏览行为放入 MaryStream 流中DataStream<Event> MaryStream stream.filter(new FilterFunction<Event>(){Overridepublic boolean filter(Event value) throws Exception {retur…

vue-admin-template侧边栏修改成抽屉式

目录 一、修改sidebar组件 二、修改store 三、修改sidebaritem页面 四、修改navbar页面 五、修改layout 六、修改样式 一、修改sidebar组件 src—layout—components—sidebar—index.vue 把组件sidebar改成drawer <template><div :class"{ has-logo: s…

【AGC】崩溃服务flutter报缺失recordFatalException方法的问题

问题背景&#xff1a; cp反馈集成AGC-崩溃服务的flutter插件&#xff0c;使用最新的1.3.0300版本&#xff0c;出现下面错误 /Users/yin/Documents/projects/flutter/.pub-cache/hosted/pub.dartlang.org/agconnect_crash-1.3.0300/android/src/main/java/com/huawei/agconnec…

【脚本项目源码】Python制作艺术签名生成器,打造专属你的个人艺术签名

前言 本文给大家分享的是如何通过利用Python制作艺术签名生成器&#xff0c;废话不多直接开整~ 开发工具 Python版本&#xff1a; 3.6 相关模块&#xff1a; requests模块 PIL模块 PyQt5模块 环境搭建 安装Python并添加到环境变量&#xff0c;pip安装需要的相关模块即…

一个.NET的轻量级JWT库

这两天网上闲逛的时候&#xff0c;看到一个.NET的轻量级JWT库LitJWT&#xff0c;LitJWT号称主要关注性能&#xff0c;能提升至少5倍的编码/解码速度&#xff0c;以及更少的配置&#xff01; LitJWT支持的平台为netstandard 2.1或net5.0更高。 LitJWT宣传的性能对比图&#xf…

vulnhub靶机:presidential1

目录 进行靶机ip的扫描 nmap扫描开发的端口和服务信息 目录扫描 修改host文件 子域名搜集 phpmyadmin管理端登录 phpmyadmin漏洞利用 反弹shell capabilities提权 获取root权限 靶机总结 靶机下载网址&#xff1a;Presidential: 1 ~ VulnHub Kali ip&#xff1a;19…

数据分析方法-五大理论、分析框架、应用、数据分析作用

1、统计学理论 1.1 大数定量 定义&#xff1a; 指大量重复某一实验时&#xff0c;最后的频率会无限接近于事件的概率 数据的样本量越大&#xff0c;我们预测和计算的概率就越准确 数据的样本量越小&#xff0c;我们预测和计算的概率就越可能失效 举例&#xff1a; 某产品用户…

keepalived 主备使用

keepalived 主备使用 本篇主要介绍一下 keepalived 的基本的 主备使用 1.概述 什么是 keepalived呢,它是一个集群管理中 保证集群高可用的软件,防止单点故障,keepalived是以VRRP协议为实现基础的&#xff0c;VRRP全称Virtual Router Redundancy Protocol&#xff0c;即虚拟路冗…

CH36X系列芯片Linux系统使用教程

一、概述 CH365是一个连接PCI总线的通用接口芯片&#xff0c;CH367/CH368是连接PCI-Express总线的通用接口芯片。支持I/O端口映射、存储器映射、扩展ROM以及中断&#xff0c;提供主动并口、SPI、I2C、GPIO等硬件接口。基于如上芯片可将PCI/PCIe总线转换为简便易用的类似于ISA总…

R语言使用Rasch模型分析学生答题能力

最近我们被客户要求撰写关于IRT的研究报告&#xff0c;包括一些图形和统计输出。几个月以来&#xff0c;我一直对序数回归与项目响应理论&#xff08;IRT&#xff09;之间的关系感兴趣。 在这篇文章中&#xff0c;我重点介绍Rasch分析。 最近&#xff0c;我花了点时间尝试理解…

今天面了个阿里拿 38K 出来的,让我见识到了基础的天花板

前言 人人都有大厂梦&#xff0c;对于程序员来说&#xff0c;BAT 为首的一线互联网公司肯定是自己的心仪对象&#xff0c;毕竟能到这些大厂工作&#xff0c;不仅薪资高待遇好&#xff0c;而且能力技术都能够得到提升&#xff0c;最关键的是还能够给自己镀上一层金&#xff0c;…

【数集项目之 MCDF】(二) 从输入端 slave_FIFO

由于slave_FIFO调用了子模块同步FIFO SCFIFO.v&#xff0c;因此首先简单介绍同步FIFO的设计。 第一节 同步FIFOSCFIFO设计 同步FIFO实体是一组存储单元&#xff0c;因此需要先用数组方式来实现 reg [DATA_WIDTH - 1 : 0] fifo_buffer[DATA_DEPTH - 1 : 0]; 其中在参数中进行…

【随机分形搜索算法】一种新的全局数值优化的适应度-距离平衡随机分形搜索算法FDB-SFS附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

模型精度再被提升,统一跨任务小样本学习算法 UPT 给出解法!

近日&#xff0c;阿里云机器学习平台PAI与华东师范大学高明教授团队、达摩院机器智能技术NLP团队合作在自然语言处理顶级会议EMNLP2022上发表统一多NLP任务的预训练增强小样本学习算法UPT&#xff08;Unified Prompt Tuning&#xff09;。这是一种面向多种NLP任务的小样本学习算…