图床批量下载and更换

  • python脚本 批量下载markdown文件图片及批量替换到 SM.MS 图床.

  • 更新

    1
    19.03.28 初始
  • 参考资料

    https://sm.ms/doc/
    https://pypi.org/project/smms.py/0.1.1/#description

导语

  • 收到邮件,使用了很久的图床突然要在4月底关闭.只能优先处理图床的问题,数据结构暂时延后.
  • 很久就像整理一遍图片链接了,早期的博文N多的图片已经不能正常访问了,正好由此机会,可惜,,又是个大坑…

需求

  • 需求:

    • 新图床:
      • 支持https,运营商很厉害的..
      • 最好已经运行很久,经常换伤不起..
      • 支持管理/删除等.有时会上传错误..
    • 下载
      • 分文件,下载全部图片
      • 处理图片名称不规范问题(越早的博文,越不规范..例如:压根没写文件名..)
    • 上传
      • 上传图片(废话)
      • 自行替换对应旧链接.
      • 不影响已下载图片.
  • 新图床:

    • 新浪微博,自带国内cdn,支持https,
      • 有时莫名其妙会删图片,有时n久之前的图片莫名挂了.
      • 微博会自动加一层看不见的水印.
      • 可以反查微博帐号
    • 简书,七牛,腾讯云等.
      • 有上手难度.
      • 图片可能会挂
    • SM.MS
      • 支持https
      • 服务器在香港,速度非常好.
      • 已经运行了很久,没有突发情况,推论不会出现关闭.
    • 微软onedrive自建
      • 数据全在自己的onedrive,管理非常方便
      • 上手还是有点难度,研究中.
      • 微软一时半会还不会砍掉onedrive.
    • 最后选择 SM.MS 作为过渡,看看还有没有其他的解决方案.其中的坑在对应代码有说明.
  • 下载

    • 因为早期使用的图床年久失修,链接非常困难,必须要走本地代理.
      • python 内置的 urllib 库 request.urlretrieve,下载非常方便
    • 博文中N多图片链接连图片名都没有,或者有图片名没后缀,都需要处理一下.
      • 只能求助于正则来处理,最后实在不行使用url的文件名替换.(正则真是坑了一把…)
  • 上传

    • SM.MS的API文档很全,成功上传问题不大.
      • 换第三方的 requests 库,也有代码参考.
    • 但是不支持中文图片名.
      • 临时重命名,最重要的是取得图片的链接.

api

  • 这里是把上传/下载的共用函数抽出来放在 file_api 和 image_api 两个文件里了.

file_api

  • 图片名不全的依照下面的规则

    • 有后缀?
    • 没有后缀添加 .png
    • 为空字符,图片名替换为 url 中的图片名.
  • 代码注释很全,有问题请留言或邮件.

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    #!/usr/bin/python3

    import os
    import re


    # 返回本目录下 .md 文件
    def get_dir_md():
    arr = [
    x for x in os.listdir('.')
    if os.path.isfile(x) and os.path.splitext(x)[1] == '.md'
    ]
    return arr


    # 文件名含有中文?
    def is_fname_zh(filename):
    # 比较每一个字符的编码是否在 unicode 分配的中日韩文编码区
    return any('\u4e00' <= char <= '\u9fff' for char in filename)


    # 返回url中图片名
    def get_urlname(url):
    return re.search(r'.*\w\/(.*)$', url).group(1)


    # 返回md文件 list [图片名称,图片链接]
    def get_urlist(flie):
    with open(flie, 'r') as f:
    tmp = re.findall(r'.*!\[(.*?)]\((.*?)\).*', f.read())
    urlist = []
    name = set()
    for t in tmp:
    u = list(t)
    # 如果图片名不全(没有后缀..当时写的不规范),
    if not re.match(r'.*[.].*', u[0]):
    # 图片名为空
    if u[0] == '':
    # 取url的图片名
    u[0] = re.search(r'.*\w\/(.*)$', u[1]).group(1)
    # 没有后缀添加后缀(大部分为png)
    else:
    u[0] += '.png'
    # 如果图片名重名
    if u[0] in name:
    # 取url的文件名
    u[0] = re.search(r'.*\w\/(.*)$', u[1]).group(1)
    print('rename %s' % (u[0]))
    name.add(u[0])
    urlist.append(u)
    return urlist


    # 替换md文件中链接
    def re_md_url(flie, urlist):
    with open(flie, 'r') as f:
    tmp = f.read()
    for ulist in urlist:
    tmp = tmp.replace(ulist[1], ulist[2])
    with open(flie, 'w') as f:
    f.write(tmp)

image_api

  • SM.MS上传代码参考

    • https://pypi.org/project/smms.py/0.1.1/#description

  • urllib 使用本地代理

    • https://stackoverflow.com/questions/22967084/urllib-request-urlretrieve-with-proxy

  • 代码注释很全,有问题请留言或邮件.

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    #!/usr/bin/python3

    import os
    import requests
    import json
    from glob import glob
    from urllib import request

    params = {'format': 'json', 'ssl': True}
    prefix = 'https://sm.ms/api'


    # 上传图片
    def api_upload(image):
    '''upload an image file'''
    files = {'smfile': open(image, 'rb')}
    print()
    resp = requests.post(
    '{}/upload'.format(prefix), files=files, params=params)
    return resp.text


    def upload(filename):
    images = glob(filename)
    if not images:
    print('%s not image' % (filename))
    for image in images:
    # upload an image
    resp = json.loads(api_upload(image))
    # 返回上传图片的url
    if resp['code'] == 'error':
    # 打印错误信息
    print('uploda error msg:%s' % (resp['msg'].lower()))
    return None
    else:
    print('%s upload success' % (filename))
    return resp['data']['url']


    # 下载图片
    def down(file, url):
    if not os.path.exists(file):
    print('down %s' % (file))
    # 本地http代理
    proxy = request.ProxyHandler({'http': '127.0.0.1:999'})
    opener = request.build_opener(proxy)
    request.install_opener(opener)
    request.urlretrieve(url, file)
    else:
    # 文件已存在
    print('%s is exist' % (file))

下载

  • 新建一个 .md 文件名的文件夹,下载图片到对应文件夹,对图片名的处理见flie一节.

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #!/usr/bin/python3

    import os
    from image_api import down
    from flie_api import get_dir_md, get_urlist

    if __name__ == "__main__":
    # 获取 .md 文件
    arr = get_dir_md()
    for md in arr:
    # 获取urllist
    urlist = get_urlist(md)
    if urlist:
    # 创建目录
    fdir = os.path.splitext(md)[0]
    try:
    os.mkdir(fdir)
    except FileExistsError as e:
    e
    # 进入子目录
    os.chdir(fdir)
    print('enter %s' % (os.path.abspath('.')))
    for u in urlist:
    down(u[0], u[1])
    print('leave %s' % (os.path.abspath('.')))
    # 返回上级目录
    os.chdir('../')
    else:
    # urlist 为空
    continue

上传

  • 上传到 SM.MS 图床,中文图片名,暂时重命名文件.

  • 图床有单ip的一定时间的上传的次数限制,因此要处理所有博文,加了延迟,上传间隔20s,处理完一个文件延迟60s.测试还好,就是最后一个文件时,遇到了限制..

  • 文件数量有限可以干掉延时.

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    #!/usr/bin/python3

    import os
    import time
    from image_api import upload
    from flie_api import get_dir_md, get_urlist, is_fname_zh, get_urlname, re_md_url

    if __name__ == "__main__":
    # 获取.md文件
    arr = get_dir_md()
    for file in arr:
    # 获取urlist
    urlist = get_urlist(file)
    if urlist:
    # 进入子目录
    fdir = os.path.splitext(file)[0]
    os.chdir(fdir)
    print('enter %s' % (os.path.abspath('.')))
    # 上传图片
    for u in urlist:
    # u[0]图片名含有中文,临时重命名
    if is_fname_zh(u[0]):
    tmp = get_urlname(u[1])
    os.rename(u[0], tmp)
    else:
    tmp = u[0]
    # 上传图片
    u.append(upload(tmp))
    os.rename(tmp, u[0])
    # 延时
    time.sleep(20)
    # 返回上级目录
    print('leave %s' % (os.path.abspath('.')))
    os.chdir('../')
    # 替换链接
    re_md_url(file, urlist)
    # 延时
    time.sleep(60)
    else:
    # urlist 为空
    continue

结语

  • 终于处理完图床的问题了,接下来回到数据结构,被正则坑了好久…