背景
最近在弄GitHub主页美化的时候,搞了一些感觉比较好玩有趣的东西,有兴趣的朋友可以看看
这里贴个我的主页地址:https://github.com/JS-banana,有兴趣的可以看看~
当时在编辑个人信息介绍的时候,产生了一个想法:可以在我的GitHub主页同步我的博客更新状态吗?
当我更新博客的时候,我的GitHub主页会自动把我博客最新更新的内容同步过去,很棒啊有没有~
这是当时产生的一个想法,后来就研究了一下。最开始是想用nodejs
写个爬虫搞一搞的,也没啥问题,不过这样搞会有很多缺陷,我自己也只能搞个半成品,也不具有一定的复用性,就排除了~
后来看到了Python的feedparser
库,感觉非常合适有没有啊。(feedparser
是python中最常用的RSS程序库,使用它我们可轻松地实现从任何 RSS 或 Atom 订阅源得到标题、链接和文章的条目。)
也看了下效果,感觉很不错,这样我们只要做两件事即可:
- 实现 Atom 订阅源(供
feedparser
库使用) - 实现
README.md
文件的动态更新(获取到订阅信息后更新主页)
RSS、Atom 订阅源
RSS订阅我们应该不陌生,我们在浏览很多大佬博客的时候、知名网站和服务时会发现他们都提供有RSS/Atom订阅,那么什么是RSS?什么是Atom呢?
什么是 RSS?
- 指 Really Simple Syndication(真正简易联合)
- 使您有能力聚合(syndicate)网站的内容
- 定义了非常简单的方法来共享和查看标题和内容
- 文件可被自动更新
- 允许为不同的网站进行视图的个性化
- 使用
XML
编写
为什么使用 RSS?
RSS 被设计用来展示选定的数据。
如果没有 RSS,用户就不得不每日都来您的网站检查新的内容。对许多用户来说这样太费时了。通过 RSS feed(RSS 通常被称为 News feed 或 RSS feed),用户们可以使用 RSS 聚合器来更快地检查您的网站更新(RSS 聚合器是用来聚集并分类 RSS feed 的网站或软件)。
RSS的未来发展(Atom的诞生)
因为RSS 2.0的版权问题,该协议前途未卜
由于RSS前途未卜,而且RSS标准发展存在诸多问题或不足,于是ATOM横空出世,可以先简单的理解为RSS的替代品。
FEED 是什么
FEED其实就是RSS(或ATOM)和订阅用户之间的“中间商”,起到帮忙批发传递信息的作用。所以,FEED的常见格式就是RSS和ATOM,网络上说的FEED订阅,更确切的说法应该仍然是RSS或ATOM订阅。
什么是订阅
订阅跟普通大家订阅报刊类似,不过几乎所有网站的RSS
/ATOM
订阅都是免费的,也有一些“非主流”一族要收费订阅的,当然FEED订阅只是网络上的信息传递,一般不涉及实体资料传递,所以大家遇到喜欢的网站,并且也喜欢使用在线或离线阅读,尽可订阅,而且可以随时退订。
总结
RSS 和 Atom 具有相似的基于 XML
的格式。它们的基本结构是相同的,只是在节点的表达式上有一点区别。我们只要了解ATOM是对RSS2.0的改进就可以了。
生成自己网站的Atom订阅源
Atom订阅源 基本结构
了解 atom.xml
的基本格式和语法,看个最简单的demo
<!-- 头信息 -->
<?xml version="1.0" encoding="utf-8"?>
<!-- 主体 -->
<feed xmlns="http://www.w3.org/2005/Atom">
<!-- 基本信息 -->
<title>小帅の技术博客</title>
<link href="https://ssscode.com/atom.xml" rel="self"/>
<link href="https://ssscode.com/"/>
<updated>2021-08-28 16:25:56</updated>
<id>https://ssscode.com/</id>
<author>
<name>JS-banana</name>
<email>[email protected]</email>
</author>
<!-- 内容区 -->
<entry>
<title>Webpack + React + TypeScript 构建一个标准化应用</title>
<link href="https://ssscode.com/pages/c3ea73/" />
<id>https://ssscode.com/pages/c3ea73/</id>
<published>2021-08-28 16:25:56</published>
<update>2021-08-28 16:25:56</update>
<content type="html"></content>
<summary type="html"></summary>
<category term="webpack" scheme="https://ssscode.com/categories/?category=JavaScript"/>
</entry>
<entry>
...
</entry>
...
</feed>
基本信息那一块完全可以自己自定义配置好,然后,再去头去尾之后,可以发现我们只要关心 <entry> ... </entry>
标签内容即可,也就是每条博客文章的基本信息~
因此,我们只要按照这个规范、格式、语法,完全可以自己生成atom.xml
,nice😎~
不想自己写的可以试试这个 feed
编写 atom.xml 文件生成函数
因为我的博客是以vuepress
搭建的(webpack
+ vue2.x
),这里就以nodejs
为例
读取所有markdwon文件就不细说了,我们拿到所有的列表数据,进行一下简单的处理,这里只填写一些我们需要的数据即可,如果想阅读订阅源使用,也可以自己丰富信息内容~
const DATA_FORMAT = 'YYYY-MM-DD HH:mm:ss';
// posts 是所有的博客文章信息
// xml 中的 & 符号需要替换为 & 否则会有语法错误
function toXml(posts) {
const feed = `<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>小帅の技术博客</title>
<link href="https://ssscode.com/atom.xml" rel="self"/>
<link href="https://ssscode.com/"/>
<updated>${dayjs().format(DATA_FORMAT)}</updated>
<id>https://ssscode.com/</id>
<author>
<name>JS-banana</name>
<email>[email protected]</email>
</author>
${posts
.map(item => {
return `
<entry>
<title>${item.title.replace(/(&)/g, '&')}</title>
<link href="https://ssscode.com${item.permalink}" />
<id>https://ssscode.com${item.permalink}</id>
<published>${item.date.slice(0, 10)}</published>
<update>${item.date}</update>
</entry>`;
})
.join('\n')}
</feed>`;
fs.writeFile(path.resolve(process.cwd(), './atom.xml'), feed, function(err) {
if (err) return console.log(err);
console.log('文件写入成功!');
});
}
node
执行该文件,应该会在同级目录下生成一个 atom.xml
文件,可以看到
ok,atom订阅源搞定~
feedparser的简单用法
python feedparser,网上似乎也有node版本的,这里就先不关心了
把刚才的demo内容片段复制到atom.xml
文件,简单测试下用法,看下返回值格式,为了更清晰的看结构,我把python执行的结果处理了一下
atom.xml
源文件
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>小帅の技术博客</title>
<link href="https://ssscode.com/atom.xml" rel="self"/>
<link href="https://ssscode.com/"/>
<updated>2021-08-28 16:25:56</updated>
<id>https://ssscode.com/</id>
<author>
<name>JS-banana</name>
<email>[email protected]</email>
</author>
<entry>
<title>Webpack + React + TypeScript 构建一个标准化应用</title>
<link href="https://ssscode.com/pages/c3ea73/" />
<id>https://ssscode.com/pages/c3ea73/</id>
<published>2021-08-28 16:25:56</published>
<update>2021-08-28 16:25:56</update>
</entry>
</feed>
main.py
脚本
import feedparser
blog_feed_url = "./atom.xml"
feeds = feedparser.parse(blog_feed_url)
print (feeds)
输出结果大致结构如下
{
bozo: 1,
// entries
entries: [
{
title: "Webpack + React + TypeScript 构建一个标准化应用",
title_detail: {
type: "text/dplain",
language: None,
base: "",
value: "Webpack + React + TypeScript 构建一个标准化应用",
},
links: [{ href: "https://ssscode.com/pages/c3ea73/", rel: "alternate", type: "text/html" }],
link: "https://ssscode.com/pages2/c3ea73/",
id: "https://ssscode.com/pages/c3ea73/",
guidislink: False,
published: "2021-08-28 16:25:56",
publoished_parsed: time.struct_time(), // 一个日期处理函数,参数比较多,我删掉了,只看代码结构
update: "2021-08-28 16:25:56",
},
],
// feed
feed: {
title: "小帅の技术博客",
title_detail: { type: "text/plain", language: None, base: "", value: "小帅の技术博客" },
links: [
{ href: "https://ssscode.com/atom.xml", rel: "self", type: "application/atom+xml" },
{ href: "https://ssscode.com/", rel: "alternate", type: "text/html" },
],
link: "https://ssscode.com/",
updated: "2021-08-28 16:25:56",
updated_parsed: time.struct_time(),
id: "https://ssscode.com/",
guidislink: False,
authors: [{ name: "JS-banana", email: "[email protected]" }],
author_detail: { name: "JS-banana", email: "[email protected]" },
author: "JS-banana ([email protected])",
},
headers: {},
encoding: "utf-8",
version: "atom10",
bozo_exception: SAXParseException("XML or text declaration not at start of entity"),
namespaces: { "": "http://www.w3.org/2005/Atom" },
}
可以看到,拿到所有的entries
即可,编写个函数,取一些我们需要的内容
def fetch_blog_entries():
entries = feedparser.parse(blog_feed_url)["entries"]
return [
{
"title": entry["title"],
"url": entry["link"].split("#")[0],
"published": entry["published"].split("T")[0],
}
for entry in entries
]
替换markdown文件指定区域内容
剩下最后一步就是:怎么把我们README.md
主页文件中指定的区域内容替换掉,然后在推送到GitHub完成更新即可
### Hello, 我是小帅! 👋
...
...
其他信息
<!-- start -->
这里显示博客信息
<!-- end -->
如上,除了指定的区域需要更新,其他地方是不需要变动的
这时就可以通过Python可以读取注释,然后使用正则处理替换,即可
我们在 README.md
中标记注释
<!-- blog starts -->
...
<!-- blog ends -->
代码:
def replace_chunk(content, marker, chunk, inline=False):
r = re.compile(
r"<!\-\- {} starts \-\->.*<!\-\- {} ends \-\->".format(marker, marker),
re.DOTALL,
)
if not inline:
chunk = "\n{}\n".format(chunk)
chunk = "<!-- {} starts -->{}<!-- {} ends -->".format(marker, chunk, marker)
return r.sub(chunk, content)
最后,再结合接口请求、文件读取等,完整代码如下
import feedparser
import json
import pathlib
import re
import os
import datetime
blog_feed_url = "https://ssscode.com/atom.xml"
root = pathlib.Path(__file__).parent.resolve()
def replace_chunk(content, marker, chunk, inline=False):
r = re.compile(
r"<!\-\- {} starts \-\->.*<!\-\- {} ends \-\->".format(marker, marker),
re.DOTALL,
)
if not inline:
chunk = "\n{}\n".format(chunk)
chunk = "<!-- {} starts -->{}<!-- {} ends -->".format(marker, chunk, marker)
return r.sub(chunk, content)
def fetch_blog_entries():
entries = feedparser.parse(blog_feed_url)["entries"]
return [
{
"title": entry["title"],
"url": entry["link"].split("#")[0],
"published": entry["published"].split("T")[0],
}
for entry in entries
]
if __name__ == "__main__":
readme = root / "README.md"
readme_contents = readme.open(encoding='UTF-8').read()
entries = fetch_blog_entries()[:5]
entries_md = "\n".join(
["* <a href='{url}' target='_blank'>{title}</a> - {published}".format(**entry) for entry in entries]
)
rewritten = replace_chunk(readme_contents, "blog", entries_md)
readme.open("w", encoding='UTF-8').write(rewritten)
我对Python也不熟,不过跟着前人的脚步,模仿着使用也能达到预期效果,还行~
最近稍微接触了一些Python相关的脚本库,发现还挺有意思的,觉得还是很有必要学习学习,日常使用中还是很有帮助的,毕竟现在Python也是很火热的嘛,就算当工具用,感觉也很强力~
配置 GitHub Action 定时任务
实现功能的脚本已经搞定了,现在就是希望在我们完成博客更新后,脚本可以自动执行
这里我们直接使用 GitHub Action 的定时任务即可
项目里添加文件 .github/workflows/ci.yml
name: Build README
on:
workflow_dispatch:
schedule:
- cron: "30 0 * * *" # 每天 0:30 时运行,北京时间需要 + 8
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out repo # 获取代码分支
uses: actions/checkout@v2
- name: Set up Python # python 环境
uses: actions/setup-python@v2
with:
python-version: 3.8
- uses: actions/cache@v2 # 依赖缓存
name: Configure pip caching
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install Python dependencies # 安装依赖
run: |
python -m pip install -r requirements.txt
- name: Update README # 执行脚本
run: |-
python build_readme.py
cat README.md
- name: Commit and push if changed # Git 提交
run: |-
git diff
git config --global user.email "[email protected]"
git config --global user.name "JS-banana"
git pull
git add -A
git commit -m "Updated README content" || exit 0
git push
大功告成~
看下效果:
这样脚本每天都会跑一次,同步博客相关信息~
结语
之前只知道RSS订阅,完全不清楚还有这么些的细节,这次也算梳理搞清楚了一些,也尝试自己玩了一下,还是挺不错的~
感觉多会一门语言还是很棒的啊,有时会给你完全不一样的思路,或许就会有更加好的方案~
扶我起来,我还能学~笑~