用python批量替換MD檔案中的圖片地址

wait4friend發表於2018-05-09

背景

在用Markdown格式記錄我的技術筆記過程中,我習慣把所有的圖片先儲存在本地,而不是直接上傳到圖床上去。這樣的好處是,一方面我可以有本地備份,不用擔心圖床哪一天掛掉了;另一方面,如果我想把某一篇筆記釋出到部落格,我可以隨時把圖片上傳,然後更新圖片地址。

Mac平臺下的Typora + iPic的組合,雖然很強大,但是不能滿足我奇葩的需求。所以就寫了一個簡單的python指令碼來處理。因為會配合KM一起使用,所以這個指令碼的任務就只需要處理單一檔案即可。

KM指令碼參見 KeyboardMaestro_PathFinder_依次處理多個選中檔案

需求

  1. 通過指定入參處理單一檔案,更新圖片URL;
  2. 能把MD檔案中的圖片拷貝到本地其他目錄(方便備份);
  3. 能把MD檔案中的圖片從本地上傳到圖床(方便釋出);
  4. 能把部落格上文章內嵌的圖片抓到本地;

原始碼

整體邏輯如下:

  1. 判斷入參是一個合法的MD檔案;
  2. 正規表示式查詢所有圖片地址的列表;
  3. 根據不同的操作模式
    1. 本地模式,把圖片拷貝到指定目錄下,生成新url
    2. 圖床模式,把本地圖片上傳到阿里雲OSS,生成新url
    3. 下拉模式,暫未實現。。。
  4. 用新url替換原來的url

replace_md_url.py

#!/usr/bin/env -S -P${HOME}/anaconda/bin python
# -*- coding:utf-8 -*-

import re, os, shutil, time, sys, argparse
from itertools import chain
import oss2

# 需要替換url的MD檔案
md_file = ''

# 操作型別, L2L (預設本地到本地), L2W(本地到圖床), W2L(圖床到本地)
action = 'L2L'

# 儲存圖片檔案的根目錄
dir_base = '/*******/_MD_Media'

# Markdown中圖片語法 ![](url) 或者 <img src='' />
img_patten = r'!\[.*?\]\((.*?)\)|<img.*?src=[\'\"](.*?)[\'\"].*?>'


def get_img_local_path(md_file, path):
    """
    獲取MD檔案中嵌入圖片的本地檔案絕對地址
    :param md_file: MD檔案
    :param path: 圖片URL
    :return: 圖片的本地檔案絕對地址
    """

    result = None

    # /a/b/c
    if path.startswith('/'):
        result = path
    # ./a/b/c
    elif path.startswith('.'):
        result = '{0}/{1}'.format(os.path.dirname(md_file), path)
    # file:///a/b/c
    elif path.startswith('file:///'):
        result = path[8:]
        result = result.replace('%20',' ')
    else:
        result = '{0}/{1}'.format(os.path.dirname(md_file), path)

    return result

def local_2_local(md_file, dir_ts, match):
    """
    把MD中的本地圖片移動到指定目錄下,並返回URL。 這裡並沒有進行URL的替換
    :param md_file:
    :param dir_ts:
    :param match:
    :return: new_url,新本地檔案地址。如果不需要替換,就返回空
    """
    dir_tgt = '{0}/{1}'.format(dir_base, dir_ts)
    new_url = None
    # 判斷是不是已經是一個圖片的網址,或者已經在指定目錄下
    if not (re.match('((http(s?))|(ftp))://.*', match) or re.match('{}/.*'.format(dir_base), match)):
        # 如果圖片url是本地檔案,就替換到指定目錄
        img_file = get_img_local_path(md_file, match)
        if os.path.isfile(img_file):
            new_url = '{0}/{1}'.format(dir_tgt, os.path.basename(match))
            os.makedirs(dir_tgt, exist_ok=True)
            # 移動物理檔案
            shutil.move(img_file, dir_tgt)

    return new_url

def local_2_web(md_file, dir_ts, match):
    """
    把MD中的本地圖片上傳到OSS下,並返回URL。 這裡並沒有進行URL的替換
    :param md_file:
    :param dir_ts:
    :param match:
    :return: new_url,新本地檔案地址。如果不需要替換,就返回空
    """

    # 阿里雲OSS資訊
    bucket_name = "b******ce"
    endpoint = "http://oss-cn-beijing.aliyuncs.com"
    access_key_id = "******"
    access_key_secret = "******"
    web_img_prfix = 'https://******.oss-cn-beijing.aliyuncs.com'
    # 建立Bucket物件,所有Object相關的介面都可以通過Bucket物件來進行
    bucket = oss2.Bucket(oss2.Auth(access_key_id, access_key_secret), endpoint, bucket_name)

    new_url = None
    # 判斷是不是已經是一個圖片的網址
    if not (re.match('((http(s?))|(ftp))://.*', match) ):
        # 如果圖片url是本地檔案,就上傳
        img_file = get_img_local_path(md_file, match)
        if os.path.isfile(img_file):
            key_url = '{0}/{1}'.format(dir_ts, os.path.basename(match))
            bucket.put_object_from_file(key_url, img_file)
            new_url = '{}/{}'.format(web_img_prfix, key_url)

    return new_url

def replace_md_url(md_file):
    """
    把指定MD檔案中引用的圖片移動到指定地點(本地或者圖床),並替換URL
    :param md_file: MD檔案
    :return:
    """

    if os.path.splitext(md_file)[1] != '.md':
        print('{}不是Markdown檔案,不做處理。'.format(md_file))
        return

    cnt_replace = 0
    # 本次操作時間戳
    dir_ts = time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime())

    with open(md_file, 'r',encoding='utf-8') as f: #使用utf-8 編碼開啟
        post = f.read()
        matches = re.compile(img_patten).findall(post)
        if matches and len(matches)>0 :
            # 多個group整合成一個列表
            for match in list(chain(*matches)) :
                if match and len(match)>0 :
                    new_url = None

                    # 進行不同型別的URL轉換操作
                    if action == 'L2L':
                        new_url = local_2_local(md_file, dir_ts, match)
                    elif action == 'L2W':
                        new_url = local_2_web(md_file, dir_ts, match)

                    # 更新MD中的URL
                    if new_url :
                        post = post.replace(match, new_url)
                        cnt_replace = cnt_replace + 1

        # 如果有內容的話,就直接覆蓋寫入當前的markdown檔案
        if post and cnt_replace > 0:
            open(md_file, 'w', encoding='utf-8').write(post)
            print('{0}的{1}個URL被替換到<{2}>/{3}'.format(os.path.basename(md_file), cnt_replace, action, dir_ts))
        elif cnt_replace == 0:
            print('{}中沒有需要替換的URL'.format(os.path.basename(md_file)))





if __name__ == '__main__':
    parser = argparse.ArgumentParser()

    parser.add_argument('-f', '--file', help='檔案Full file name ofMarkdown file.')
    parser.add_argument('-a', '--action', help='操作型別: L2L, L2W, W2L .')
    parser.add_argument('-d', '--dir', help='Base directory to store MD images.')

    args = parser.parse_args()

    if args.action:
        action = args.action
    if args.dir:
        dir_base = args.dir
    if args.file:
        replace_md_url(args.file)



複製程式碼

相關文章