解放雙手,每天自動把博客信息更新至GitHub主頁
背景
最近在弄GitHub主頁美化的時候,搞了一些感覺比較好玩有趣的東西,有興趣的朋友可以看看
這里貼個我的主頁地址:https://github.com/JS-banana,有興趣的可以看看~
當(dāng)時在編輯個人信息介紹的時候,產(chǎn)生了一個想法:可以在我的GitHub主頁同步我的博客更新狀態(tài)嗎?
當(dāng)我更新博客的時候,我的GitHub主頁會自動把我博客最新更新的內(nèi)容同步過去,很棒啊有沒有~
這是當(dāng)時產(chǎn)生的一個想法,后來就研究了一下。最開始是想用nodejs
寫個爬蟲搞一搞的,也沒啥問題,不過這樣搞會有很多缺陷,我自己也只能搞個半成品,也不具有一定的復(fù)用性,就排除了~
后來看到了Python的feedparser
庫,感覺非常合適有沒有啊。(feedparser
是python中最常用的RSS程序庫,使用它我們可輕松地實現(xiàn)從任何 RSS 或 Atom 訂閱源得到標(biāo)題、鏈接和文章的條目。)
也看了下效果,感覺很不錯,這樣我們只要做兩件事即可:
- 實現(xiàn) Atom 訂閱源(供
feedparser
庫使用) - 實現(xiàn)
README.md
文件的動態(tài)更新(獲取到訂閱信息后更新主頁)
RSS、Atom 訂閱源
RSS訂閱我們應(yīng)該不陌生,我們在瀏覽很多大佬博客的時候、知名網(wǎng)站和服務(wù)時會發(fā)現(xiàn)他們都提供有RSS/Atom訂閱,那么什么是RSS?什么是Atom呢?
什么是 RSS?
- 指 Really Simple Syndication(真正簡易聯(lián)合)
- 使您有能力聚合(syndicate)網(wǎng)站的內(nèi)容
- 定義了非常簡單的方法來共享和查看標(biāo)題和內(nèi)容
- 文件可被自動更新
- 允許為不同的網(wǎng)站進(jìn)行視圖的個性化
- 使用
XML
編寫
為什么使用 RSS?
RSS 被設(shè)計用來展示選定的數(shù)據(jù)。
如果沒有 RSS,用戶就不得不每日都來您的網(wǎng)站檢查新的內(nèi)容。對許多用戶來說這樣太費時了。通過 RSS feed(RSS 通常被稱為 News feed 或 RSS feed),用戶們可以使用 RSS 聚合器來更快地檢查您的網(wǎng)站更新(RSS 聚合器是用來聚集并分類 RSS feed 的網(wǎng)站或軟件)。
RSS的未來發(fā)展(Atom的誕生)
因為RSS 2.0的版權(quán)問題,該協(xié)議前途未卜
由于RSS前途未卜,而且RSS標(biāo)準(zhǔn)發(fā)展存在諸多問題或不足,于是ATOM橫空出世,可以先簡單的理解為RSS的替代品。
FEED 是什么
FEED其實就是RSS(或ATOM)和訂閱用戶之間的“中間商”,起到幫忙批發(fā)傳遞信息的作用。所以,FEED的常見格式就是RSS和ATOM,網(wǎng)絡(luò)上說的FEED訂閱,更確切的說法應(yīng)該仍然是RSS或ATOM訂閱。
什么是訂閱
訂閱跟普通大家訂閱報刊類似,不過幾乎所有網(wǎng)站的RSS
/ATOM
訂閱都是免費的,也有一些“非主流”一族要收費訂閱的,當(dāng)然FEED訂閱只是網(wǎng)絡(luò)上的信息傳遞,一般不涉及實體資料傳遞,所以大家遇到喜歡的網(wǎng)站,并且也喜歡使用在線或離線閱讀,盡可訂閱,而且可以隨時退訂。
總結(jié)
RSS 和 Atom 具有相似的基于 XML
的格式。它們的基本結(jié)構(gòu)是相同的,只是在節(jié)點的表達(dá)式上有一點區(qū)別。我們只要了解ATOM是對RSS2.0的改進(jìn)就可以了。
生成自己網(wǎng)站的Atom訂閱源
Atom訂閱源 基本結(jié)構(gòu)
了解 atom.xml
的基本格式和語法,看個最簡單的demo
<!-- 頭信息 -->
<?xml version="1.0" encoding="utf-8"?>
<!-- 主體 -->
<feed xmlns="http://www.w3.org/2005/Atom">
<!-- 基本信息 -->
<title>小帥の技術(shù)博客</title>
<link rel="self"/>
<link />
<updated>2021-08-28 16:25:56</updated>
<id>https://ssscode.com/</id>
<author>
<name>JS-banana</name>
<email>sss213018@163.com</email>
</author>
<!-- 內(nèi)容區(qū) -->
<entry>
<title>Webpack + React + TypeScript 構(gòu)建一個標(biāo)準(zhǔn)化應(yīng)用</title>
<link />
<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>
基本信息那一塊完全可以自己自定義配置好,然后,再去頭去尾之后,可以發(fā)現(xiàn)我們只要關(guān)心 <entry> ... </entry>
標(biāo)簽內(nèi)容即可,也就是每條博客文章的基本信息~
因此,我們只要按照這個規(guī)范、格式、語法,完全可以自己生成atom.xml
,nice??~
不想自己寫的可以試試這個 feed
編寫 atom.xml 文件生成函數(shù)
因為我的博客是以vuepress
搭建的(webpack
+ vue2.x
),這里就以nodejs
為例
讀取所有markdwon文件就不細(xì)說了,我們拿到所有的列表數(shù)據(jù),進(jìn)行一下簡單的處理,這里只填寫一些我們需要的數(shù)據(jù)即可,如果想閱讀訂閱源使用,也可以自己豐富信息內(nèi)容~
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>小帥の技術(shù)博客</title>
<link rel="self"/>
<link />
<updated>${dayjs().format(DATA_FORMAT)}</updated>
<id>https://ssscode.com/</id>
<author>
<name>JS-banana</name>
<email>sss213018@163.com</email>
</author>
${posts
.map(item => {
return `
<entry>
<title>${item.title.replace(/(&)/g, '&')}</title>
<link />
<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
執(zhí)行該文件,應(yīng)該會在同級目錄下生成一個 atom.xml
文件,可以看到
ok,atom訂閱源搞定~
feedparser的簡單用法
python feedparser,網(wǎng)上似乎也有node版本的,這里就先不關(guān)心了
把剛才的demo內(nèi)容片段復(fù)制到atom.xml
文件,簡單測試下用法,看下返回值格式,為了更清晰的看結(jié)構(gòu),我把python執(zhí)行的結(jié)果處理了一下
atom.xml
源文件
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>小帥の技術(shù)博客</title>
<link rel="self"/>
<link />
<updated>2021-08-28 16:25:56</updated>
<id>https://ssscode.com/</id>
<author>
<name>JS-banana</name>
<email>sss213018@163.com</email>
</author>
<entry>
<title>Webpack + React + TypeScript 構(gòu)建一個標(biāo)準(zhǔn)化應(yīng)用</title>
<link />
<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)
輸出結(jié)果大致結(jié)構(gòu)如下
{
bozo: 1,
// entries
entries: [
{
title: "Webpack + React + TypeScript 構(gòu)建一個標(biāo)準(zhǔn)化應(yīng)用",
title_detail: {
type: "text/dplain",
language: None,
base: "",
value: "Webpack + React + TypeScript 構(gòu)建一個標(biāo)準(zhǔn)化應(yīng)用",
},
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(), // 一個日期處理函數(shù),參數(shù)比較多,我刪掉了,只看代碼結(jié)構(gòu)
update: "2021-08-28 16:25:56",
},
],
// feed
feed: {
title: "小帥の技術(shù)博客",
title_detail: { type: "text/plain", language: None, base: "", value: "小帥の技術(shù)博客" },
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: "sss213018@163.com" }],
author_detail: { name: "JS-banana", email: "sss213018@163.com" },
author: "JS-banana (sss213018@163.com)",
},
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
即可,編寫個函數(shù),取一些我們需要的內(nèi)容
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文件指定區(qū)域內(nèi)容
剩下最后一步就是:怎么把我們README.md
主頁文件中指定的區(qū)域內(nèi)容替換掉,然后在推送到GitHub完成更新即可
### Hello, 我是小帥! ??
...
...
其他信息
<!-- start -->
這里顯示博客信息
<!-- end -->
如上,除了指定的區(qū)域需要更新,其他地方是不需要變動的
這時就可以通過Python可以讀取注釋,然后使用正則處理替換,即可
我們在 README.md
中標(biāo)記注釋
<!-- 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)
最后,再結(jié)合接口請求、文件讀取等,完整代碼如下
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也不熟,不過跟著前人的腳步,模仿著使用也能達(dá)到預(yù)期效果,還行~
最近稍微接觸了一些Python相關(guān)的腳本庫,發(fā)現(xiàn)還挺有意思的,覺得還是很有必要學(xué)習(xí)學(xué)習(xí),日常使用中還是很有幫助的,畢竟現(xiàn)在Python也是很火熱的嘛,就算當(dāng)工具用,感覺也很強(qiáng)力~
配置 GitHub Action 定時任務(wù)
實現(xiàn)功能的腳本已經(jīng)搞定了,現(xiàn)在就是希望在我們完成博客更新后,腳本可以自動執(zhí)行
這里我們直接使用 GitHub Action 的定時任務(wù)即可
項目里添加文件 .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 環(huán)境
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 # 執(zhí)行腳本
run: |-
python build_readme.py
cat README.md
- name: Commit and push if changed # Git 提交
run: |-
git diff
git config --global user.email "sss213018@163.com"
git config --global user.name "JS-banana"
git pull
git add -A
git commit -m "Updated README content" || exit 0
git push
大功告成~
看下效果:
這樣腳本每天都會跑一次,同步博客相關(guān)信息~
結(jié)語
之前只知道RSS訂閱,完全不清楚還有這么些的細(xì)節(jié),這次也算梳理搞清楚了一些,也嘗試自己玩了一下,還是挺不錯的~
感覺多會一門語言還是很棒的啊,有時會給你完全不一樣的思路,或許就會有更加好的方案~
扶我起來,我還能學(xué)笑