menu Chancel's Blog
rss_feed lightbulb_outline

Python转换Markdown本地图片链接为HTTP引用链接(Lsky图床)

warning 这篇文章距离上次更新于644天前,文中部分信息可能已失效,请自行甄别无效内容。

前几天安装了lsky图床,但是使用VSC写Markdown的时候右键粘贴文件是存储到本地的,于是思考了下如何将Markdown的本地文件链接转换成在线形式的

Lsky图床安装

如何安装Lsky图床可以参考我之前写的

我只想要一个自建本地图床 -Chancel's blog

Visual Studio Code 粘贴图片自动保存插件

Visual Studio Code 安装Paste Image之后,如下图在配置文件中配置好图片保存的本地路径,使用Ctrl+Shift+V即可直接粘贴图片

Python脚本

这里直接贴出我写的自动上传本地Markdown中引用的图片文件到Lsky图床的代码,也方便有需求的同学可以自行更改这个脚本

# /usr/bin/python3.8
# author:chancel.yang
import os
import sys
import requests
import json
import re

token_url = 'https://example.com/api/token'
upload_url = 'https://example.com/api/upload'
lsky_email = 'example@gmail.com'
lsky_password = 'example'
lsky_token = None


def get_token():
    """获取Lsky的Token
    """
    data = {'email': lsky_email, 'password': lsky_password}
    r = requests.post(url=token_url, data=data)
    if r.status_code == 200:
        reponse = json.loads(r.text)
        reponse_data = reponse['data']
        return reponse_data['token']
    return None


def get_all_local_image_quote():
    """获取所有本地Markdown文件的图片链接
    """
    # ^!\[\]\([^http].+\)$
    def get_all_files(diretory_path):
        _list = []
        files = os.listdir(diretory_path)
        for _file in files:
            if not os.path.isdir(os.path.join(diretory_path, _file)):
                _list.append(os.path.join(diretory_path, _file))
            if os.path.isdir(os.path.join(diretory_path, _file)):
                _list.extend(get_all_files(os.path.join(diretory_path, _file)))
        return _list

    file_list = get_all_files(sys.path[0])
    image_quote_list = {}
    # re_telephone = re.compile(r'!\[\]\([^http].+?\)')
    re_telephone = re.compile(u'!\[\]\([^http].+?\)')
    for _file in file_list:
        if _file[-2:] == 'md':
            with open(_file, 'r') as f:
                _content = f.read()
                _re = re_telephone.findall(_content)
                if len(_re) > 0:
                    image_quote_list[f.name] = _re
    return image_quote_list


def upload_file(file_path):
    """ 上传文件方法,返回上传之后的url
    :params file_path 文件路径
    """
    with open(file_path, 'rb') as f:
        files = {'image': f}
        headers = {'token': lsky_token}
        r = requests.post(upload_url, files=files, headers=headers)
        if r.status_code == 200:
            reponse = json.loads(r.text)
            reponse_data = reponse['data']
            return reponse_data['url']
    return None


def replace_quote_url(screenshot_path, markdown_file_path, quote_list):
    """ 上传图片文件并替换Markdown文件中的图片链接
    :params screenshot_path Markdown引用图片链接的目录(Paste_Image插件设置的目录)
    :params markdown_file_path 包含图片链接的文件路径
    :params quote_list 包含图片链接的集合
    """
    for quote_item in quote_list:
        split_list = quote_item.split('/')
        quote_file_name = split_list[-1]
        quote_file_name = quote_file_name.replace(')', '')
        file_name = os.path.join(screenshot_path, quote_file_name)
        if os.path.exists(file_name) is False:
            print('上传失败,本地引用的图片文件并不存在:"' + markdown_file_path)
            continue
        print('正在上传' + file_name)
        url = upload_file(file_name)
        if url is None:
            print('上传失败,文件路径"' + markdown_file_path + '",图片引用路径"' + quote_item +
                  '"')
            continue
        print('上传' + file_name + '成功,URL是' + url)
        file_content = None
        with open(file=markdown_file_path, mode='r', encoding='utf-8') as f:
            file_content = f.read()
            url = '![](' + url + ')'
            file_content = file_content.replace(quote_item, url)
        with open(file=markdown_file_path, mode='w', encoding='utf-8') as f:
            f.write(file_content)
            print(markdown_file_path + '中的' + quote_item + '替换成' + url)


if __name__ == "__main__":
    # 配置信息
    screenshot_path = os.path.join(sys.path[0], '.config/screenshot/')
    print('配置文件路径:' + screenshot_path)
    # 获取所有带本地图片引用链接的笔记文件
    image_quote_list = get_all_local_image_quote()
    if len(image_quote_list) == 0:
        print('没有检测到需要上传的本地文件,程序运行结束')
        os._exit(0)
    # 获取图床Token
    lsky_token = get_token()
    if lsky_token is None:
        print('Error: Can\'t get user token')
        os._exit(0)
    print('图床返回Token:' + lsky_token)
    print('即将上传数据内容为:' + str(image_quote_list))
    while True:
        str = input("确认上传(y/n):");
        if str == 'y':
            for key, value in image_quote_list.items():
                replace_quote_url(screenshot_path, key, value)
            print('程序运行结束')
            break
        if str == 'n':
            print('用户终止运行')
            break

下面简单讲一下这个代码的构成部分

Token获取

根据Lsky的接口文档,向'/api/token'发起POST请求,表单内容帐号密码即可返回请求成功之后的Token

使用Python的requests模块可以比较简单的发起POST请求,如果显示'requests not found'则需要安装requests模块(默认不自带这个模块)

pip install requests

本地Markdown文件分析

这一块主要比较麻烦主要是循环递归目录找出所有markdown文件,然后根据正则表达式解析内容

循环递归这块没什么好讲的,主要是正则表达式,Markdown的本地图片链接大抵如下

![](../../example.png)

与HTTP链接有些相似

![](https://example.com/example.png)

所以我们需要写一条可以解析这种格式同时也要排除已经是HTTP格式的图片链接

!\[\]\([^http].+?\)

Python中负责正则表达式解析的模块是re,在代码里我们需要多次循环正则解析内容,所以每次都创建一个Python的re对象比较消耗资源

我们可以先预编译正则表达式再使用"findall"方法找出文本中所有符合该正则表达式的内容

_content="![](../../example.png)"
re_telephone = re.compile(u'!\[\]\([^http].+?\)')
_re = re_telephone.findall(_content)

文件上传

文件上传与Token的获取其实很类似,只是这次我们需要上传一个文件,不是传统的字符串类型表单,requests也支持直接POST文件

得益于强大的动态类型支持,我们直接创建一个带文件二进制的字典对象并上传即可

with open(file_path, 'rb') as f:
    files = {'image': f}
    headers = {'token': lsky_token}
    r = requests.post('https://www.example.com/api/upload', files=files, headers=headers)

链接替换

文件上传成功之后我们就获取了文件url,已知Markdown文件路径与图片本地链接

那么直接替换内容即可,这一部分直接查看代码即可,没有什么特别的地方

最后,有一点比较糟糕的是,如果你的图床速度不怎么样,那么将Markdown转换图片输出时(VSC插件支持)会非常慢甚至程序假死

博文目录

[[replyMessage== null?"发表评论":"@" + replyMessage.m_author]]

account_circle
email
web_asset
textsms

评论列表([[messageList.data.items.length]])

[[messageItem.m_author]] [[messageItem.m_author]]
[[messageItem.create_time]]
[[messageItem.m_environ.browser]] [[messageItem.m_environ.os]] [[messageItem.m_environ.device]]
[[subMessage.m_author]] [[subMessage.m_author]] @ [[subMessage.parent_message.m_author]] [[subMessage.parent_message.m_author]]
[[subMessage.create_time]]
[[subMessage.m_environ.browser]] [[subMessage.m_environ.os]] [[subMessage.m_environ.device]]