Python庫Feedparser+Atom訂閱源的妙用

解放雙手,每天自動把博客信息更新至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ù)用性,就排除了~

后來看到了Pythonfeedparser庫,感覺非常合適有沒有啊。(feedparserpython中最常用的RSS程序庫,使用它我們可輕松地實現(xiàn)從任何 RSSAtom 訂閱源得到標(biāo)題、鏈接和文章的條目。)

也看了下效果,感覺很不錯,這樣我們只要做兩件事即可:

  • 實現(xiàn) Atom 訂閱源(供feedparser庫使用)
  • 實現(xiàn) README.md 文件的動態(tài)更新(獲取到訂閱信息后更新主頁)
emoji

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 feedRSS 通常被稱為 News feedRSS 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的常見格式就是RSSATOM,網(wǎng)絡(luò)上說的FEED訂閱,更確切的說法應(yīng)該仍然是RSSATOM訂閱。

什么是訂閱

訂閱跟普通大家訂閱報刊類似,不過幾乎所有網(wǎng)站的RSS/ATOM訂閱都是免費的,也有一些“非主流”一族要收費訂閱的,當(dāng)然FEED訂閱只是網(wǎng)絡(luò)上的信息傳遞,一般不涉及實體資料傳遞,所以大家遇到喜歡的網(wǎng)站,并且也喜歡使用在線或離線閱讀,盡可訂閱,而且可以隨時退訂。

總結(jié)

RSSAtom 具有相似的基于 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

hello

編寫 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 中的 & 符號需要替換為 &amp; 否則會有語法錯誤
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, '&amp;')}</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 文件,可以看到

1

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

大功告成~

看下效果:

2

這樣腳本每天都會跑一次,同步博客相關(guān)信息~

emoji

結(jié)語

之前只知道RSS訂閱,完全不清楚還有這么些的細(xì)節(jié),這次也算梳理搞清楚了一些,也嘗試自己玩了一下,還是挺不錯的~

感覺多會一門語言還是很棒的啊,有時會給你完全不一樣的思路,或許就會有更加好的方案~

扶我起來,我還能學(xué)

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險厭惡者,不喜歡去冒險,但是人生放棄了冒險,也就放棄了無數(shù)的可能。 ...
    yichen大刀閱讀 6,098評論 0 4
  • 公元:2019年11月28日19時42分農(nóng)歷:二零一九年 十一月 初三日 戌時干支:己亥乙亥己巳甲戌當(dāng)月節(jié)氣:立冬...
    石放閱讀 6,908評論 0 2