likes
comments
collection
share

Van♂Python | 🐐又写了一个Markdown转换工具

作者站长头像
站长
· 阅读数 57

引言

又?是的!两年前杰哥就写过一个 Markdown转换工具 啦 ↓↓↓

Van♂Python | 🐐又写了一个Markdown转换工具

简单点说就是:自动排版工具(Markdown → 公众号)

本公号所有文章的排版都是有它自动生成的,一键转换,无需手动调样式,节约了笔者大量的时间。

如果你对它感兴趣,可移步至Github下载体验:耗子尾汁Markdown转换工具

1、发现了两个Chrome插件神器

而本节要写的转换工具,它的功能完全不一样,先简单说下产生它的需求场景吧。

有些站点开启了 禁止复制,如 知乎盐选专栏、百度文库、360图书馆等。如果需要里面的文本内容,要么对着 一个个手敲,要么 截图OCR,这多呆啊。得想办法找到 破解禁止复制 的方法,电脑浏览器,自然想到 Chrome插件,随手搜一下就发现了这个神器:SuperCopy

Van♂Python | 🐐又写了一个Markdown转换工具

直接点击 破解禁止复制 (可能需要重启浏览器),然后就可以复制原本不支持复制的文本了:

Van♂Python | 🐐又写了一个Markdown转换工具

Van♂Python | 🐐又写了一个Markdown转换工具

而在发现这个神器插件的同时,笔者还发现了另一个神器插件:MarkDownload

Van♂Python | 🐐又写了一个Markdown转换工具

它能帮我们 在不改变原本排版的基础上,直接把网页上的文本内容 转化为 Markdown格式

Van♂Python | 🐐又写了一个Markdown转换工具

还支持 下载到本地,点击Download,会生成一个md文件,打开看看:

Van♂Python | 🐐又写了一个Markdown转换工具

牛批,简单测试了一下,两个插件都支持绝大部分站点,笔者 采集资料的需求 得到了满足~

Van♂Python | 🐐又写了一个Markdown转换工具

2、可以,但有个问题

不兜圈子,直接说问题 → 图片可能会失效,如上面的图片:

![](https://pic1.zhimg.com/v2-0d138e9a8d77300328b4bed09f360e9b.webp)

可以看到图片资源地址指向 知乎的CDN,如果某天 资源地址发生改变 或者 被删除,那么我们原先采集的资料对应的图片就显示不出来了。

解决办法:把图片资源都下载到本地 或者 传到自己的图床,然后把MD文件中的图片链接替换成 本地路径或自己图床URL

听着简单,实际干起来还是比较繁琐的,但所幸都是些 重复性操作,完全可以写个脚本来实现 自动转换

所以,本节就是用Python动手实现这个转换工具~

Van♂Python | 🐐又写了一个Markdown转换工具


实践环节

核心思路还是比较简单的:

  • ① 定位到Markdown中的图片
  • ③ 下载图片/上传自己的图床
  • ③ 替换图片URL

哈哈,是不是跟 三步把大象装进冰箱 如出一辙,接着代码具体实践一波~

Van♂Python | 🐐又写了一个Markdown转换工具


1、图片定位

字符串查找,妥妥滴上正则,但要写出正则表达式,得先分析一波规则,Markdown图片的常见语法有下述两种:

![图片描述](http://xxx.yyy.png)


![图片描述][12]

不难发现规律:

直接写出匹配正则 (多行匹配):

import re
import os

# 匹配图片的正则
pic_match_pattern = 
    re.compile(r'(\]: |\()+(http.*?\.(png|PNG|jpg|JPG|gif|GIF|svg|SVG|webp|awebp))\??(\)?)',
                               re.M)  


# 读取文件内容的方法
def read_file_text_content(file_path):
    if not os.path.exists(file_path):
        return None
    else:
        with open(file_path, 'r+', encoding='utf-8') as f:
            return f.read()


if __name__ == '__main__':
    content = read_file_text_content("test.md")
    result_list = pic_match_pattern.findall(content)
    for result in result_list:
        print(result)

运行看下效果:

Van♂Python | 🐐又写了一个Markdown转换工具

四个分组,依次是:左括号图片URL图片后缀右括号

2、下载图片/上传自己的图床

平常用到的 request库 库是同步的,对于这种批量下载图片的场景不是很契合,这里直接用 aiohttp-requests 库来下载图片。

# 默认图片请求头
default_headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                  'Chrome/85.0.4183.121 Safari/537.36 '
}
order_set = {i for i in range(1, 50000)}  # 避免图片名重复后缀

# 下载图片的方法
async def download_pic(pic_path, url, headers=None):
    try:
        if headers is None:
            headers = default_headers
        if url.startswith("http") | url.startswith("https"):
            if os.path.exists(pic_path):
                print("图片已存在,跳过下载:%s" % pic_path)
            else:
                resp = await requests.get(url, headers=headers)
                print("下载图片:%s" % resp.url)
                if resp is not None:
                    if resp.status != 404:
                        async with aiofiles.open(pic_path, "wb+") as f:
                            await f.write(await resp.read())
                            print("图片下载完毕:%s" % pic_path)
                    else:
                        print("图片不存在:{}".format(url))
        else:
            print("图片链接格式不正确:%s - %s" % (pic_path, url))
    except Exception as e:
        print("下载异常:{}\n{}".format(url, e))


if __name__ == '__main__':
    content = read_file_text_content("test.md")
    result_list = pic_match_pattern.findall(content)
    loop = asyncio.get_event_loop()
    for result in result_list:
        # 构造图片名
        img_file_name = "{}_{}.{}".format(int(round(time.time())), order_set.pop(), result[1])
        loop.run_until_complete(download_pic(img_file_name, result[0]))

运行看看效果:

Van♂Python | 🐐又写了一个Markdown转换工具

可以,图片都下载到本地,另外,有些图片需要 登录或者代理 才能访问,设置下header即可~

下载弄完,接着到上传图床,这个找自己用的图床官方API文档和Demo抄抄,笔者以 七牛云CDN 为例:

from qiniu import Auth, put_file

access_key = ""  # Access key
secret_key = ""  # Secret key
q = Auth(access_key, secret_key)    # 构建鉴权对象
bucket_name = ""  # 要上传的空间
outer_host = "http://xxx"  # 外网域名

# 上传图片的方法
def upload_qn_cdn(pic_path):
    file_name = os.path.basename(pic_path)  # 上传后的文件名
    token = q.upload_token(bucket_name, file_name)    # 生成上传Token
    ret, info = put_file(token, file_name, pic_path)
    print("上传成功:{}/{}".format(outer_host, ret.get("key")))

if __name__ == '__main__':
    # 调用上传
    upload_qn_cdn("1673250574_10.svg")

运行后可以看到上传的图片URL:

Van♂Python | 🐐又写了一个Markdown转换工具

可以,紧接着就是替换图片URL咯~

3、替换图片URL

一个比较呆的方法就是循环调用string的 replace() 方法,来全局替换,一种更优雅的方法是 正则替换。re.sub函数的完整语法如下:

re.sub(pattern, repl, string, count=0, flags=0)

# pattern → 表示正则中的模式字符串
# repl → 被替换的字符串,它可以是字符串,也可以是函数!!!
# string → 要被替换的字符串
# count → 只处理几个?0代表所有都处理
# flags → 编译标记,如上面的re.M

不难看出突破口是 repl函数,写个简单例子帮助理解:

import re

match_pattern = re.compile(r"[a-z]+")


# 匹配小写字母串,首字母大写
def replace_lowercase(match_result):
    return match_result.group().title()


if __name__ == '__main__':
    test_str = "abc 123 def 4567 hijk 89"
    result = match_pattern.sub(replace_lowercase, test_str)
    print(result)

运行输出结果如下:

Van♂Python | 🐐又写了一个Markdown转换工具

不难看出 默认把匹配结果传递到函数中,调用 group() 方法可获得匹配字符串,然后返回的是替换后的字符串。

知道函数用法,接着就是套到我们的代码里了,直接写出替换函数:

# 图片转换为本地路径
def pic_to_local(match_result):
    print("替换前的图片路径:", match_result[2])
    # 生成新的图片名
    img_file_name = "{}_{}.{}".format(int(round(time.time())), order_set.pop(), match_result[3])
    print("新的文件名:", img_file_name)
    # 拼接图片相对路径(Markdown用到的)
    relative_path = 'images/{}'.format(img_file_name)
    # 拼接图片绝对路径,下载到本地
    absolute_path = os.path.join(images_dir, img_file_name)
    # 顺带下载图片
    loop.run_until_complete(download_pic(absolute_path, match_result[2]))
    # 还需要拼接前后括号()
    return "{}{}{}".format(match_result[1], relative_path, match_result[4])

补齐调用代码:

if __name__ == '__main__':
    content = read_file_text_content("test.md")
    images_dir = os.path.join(os.getcwd(), "images")
    if not os.path.exists(images_dir):
        os.makedirs(images_dir)
    loop = asyncio.get_event_loop()
    new_content = pic_match_pattern.sub(pic_to_local, content)
    write_text_to_file(new_content, "text_n.md")

运行后可以看到控制台陆续把图片下载到本地:

Van♂Python | 🐐又写了一个Markdown转换工具

接着用支持Markdown预览的工具打开生成的md文件:

Van♂Python | 🐐又写了一个Markdown转换工具

啧啧,完美,此处应有掌声!!!

Van♂Python | 🐐又写了一个Markdown转换工具

再送一个知识点吧,问:如果想动态指定图片的存储路径怎么搞?直接加一个参数吗?

Van♂Python | 🐐又写了一个Markdown转换工具

编译器没显示错误,但运行报错:

Van♂Python | 🐐又写了一个Markdown转换工具

少了一个参数,这里应该怎么传?一种方法就是使用 偏函数 functools.partial() 创建一个新的函数,它可以固定住原函数的部分参数,从而使得调用更加简单:

from functools import partial

new_content = pic_match_pattern.sub(partial(pic_to_local, pic_save_dir=images_dir), content)

修改后点击运行,Markdown成功本地化,图片也放置到相应的目录。本地图片是这样替换,自己图床的替换也是照葫芦画瓢,这里就不复读机了,感兴趣的可以自己写来试试~

Van♂Python | 🐐又写了一个Markdown转换工具

文中代码只要是演示基本的实现思路,比较简单,对完整可用代码感兴趣的可移步至:md_transform.py 自行查阅,感谢~

转载自:https://juejin.cn/post/7186595348310622245
评论
请登录