Featured image of post 博客迁移脚本

博客迁移脚本

字数: 8738

文章设置

构建hugo文章格式

hugo/content/post/HugoStack主题美化/ 创建 index.md (名字一定要叫 index )

原格式:

1
2
F:\hugo-github-local\my-stack-site\content\post
F:\hugo-github-local\my-stack-site\content\images

使用脚本实现hugo文章格式

 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
import os
import shutil
import glob

def organize_md_files():
    # 获取当前目录中所有的 .md 文件
    md_files = glob.glob("*.md")
    
    if not md_files:
        print("当前目录中没有找到任何 .md 文件")
        return
    
    print(f"找到了 {len(md_files)} 个 .md 文件")
    
    for md_file in md_files:
        # 获取文件名(不含扩展名)
        file_name_without_ext = os.path.splitext(md_file)[0]
        
        # 创建对应的文件夹
        folder_name = file_name_without_ext
        if not os.path.exists(folder_name):
            os.makedirs(folder_name)
            print(f"创建文件夹: {folder_name}")
        else:
            print(f"文件夹已存在: {folder_name}")
        
        # 构建目标路径
        destination_path = os.path.join(folder_name, "index.md")
        
        # 移动并重命名文件
        shutil.move(md_file, destination_path)
        print(f"移动文件: {md_file} -> {destination_path}")
    
    print("所有文件处理完成!")

if __name__ == "__main__":
    organize_md_files()

完成后会根据文章titie名称创建文件夹

image-20250909142137353

文章引入

image-20250909142316952

文章下载cos图片并存本地

  1. 遍历指定目录(包括子目录)下的所有Markdown文件。
  2. 对于每个Markdown文件,查找所有的网络图片(即以http://或https://开头的图片链接)。
  3. 下载这些网络图片到统一的images目录(位于posts目录同级)中,避免重复下载。
  4. 将Markdown文件中的网络图片链接替换为本地相对路径。

python脚本

  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
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import os
import re
import requests
import hashlib
from urllib.parse import urlparse
from pathlib import Path


def download_image(url, folder_path, filename=None):
    """下载图片到指定文件夹"""
    try:
        response = requests.get(url, stream=True, timeout=10)
        if response.status_code == 200:
            # 确定文件名
            if not filename:
                parsed_url = urlparse(url)
                filename = os.path.basename(parsed_url.path)
                if not filename or '.' not in filename:
                    # 从Content-Type获取扩展名
                    content_type = response.headers.get('content-type', '')
                    if 'jpeg' in content_type or 'jpg' in content_type:
                        ext = 'jpg'
                    elif 'png' in content_type:
                        ext = 'png'
                    elif 'gif' in content_type:
                        ext = 'gif'
                    elif 'webp' in content_type:
                        ext = 'webp'
                    else:
                        ext = 'bin'
                    # 使用URL的MD5哈希作为文件名,避免重复下载相同图片
                    url_hash = hashlib.md5(url.encode()).hexdigest()
                    filename = f"image_{url_hash}.{ext}"

            # 确保文件夹存在
            os.makedirs(folder_path, exist_ok=True)

            # 完整文件路径
            filepath = os.path.join(folder_path, filename)

            # 避免重复下载
            if os.path.exists(filepath):
                print(f"图片已存在,跳过: {filename}")
                return filepath

            # 保存图片
            with open(filepath, 'wb') as f:
                for chunk in response.iter_content(1024):
                    f.write(chunk)

            print(f"下载成功: {url} -> {filepath}")
            return filepath
    except Exception as e:
        print(f"下载图片失败: {url}, 错误: {e}")
        return None


def process_markdown_file(md_file_path, image_base_dir="../images"):
    """处理单个Markdown文件中的网络图片"""
    # 确定图片存储目录 (统一放在posts目录下的images文件夹)
    posts_dir = os.path.dirname(os.path.dirname(md_file_path))
    image_dir = os.path.join(posts_dir, "images")

    with open(md_file_path, 'r', encoding='utf-8') as f:
        content = f.read()

    # 正则表达式匹配Markdown图片
    pattern = r'!\[(.*?)\]\((.*?)\)'
    matches = re.findall(pattern, content)

    changed = False
    for alt_text, url in matches:
        if url.startswith(('http://', 'https://')):
            local_path = download_image(url, image_dir)
            if local_path:
                # 使用相对路径替换原URL,并确保使用正斜杠
                relative_path = os.path.relpath(local_path, os.path.dirname(md_file_path))
                relative_path = relative_path.replace('\\', '/')  # 确保使用正斜杠
                content = content.replace(f"]({url})", f"]({relative_path})")
                changed = True

    # 如果有更改,写回文件
    if changed:
        with open(md_file_path, 'w', encoding='utf-8') as f:
            f.write(content)
        print(f"已更新: {md_file_path}")
    else:
        print(f"无需更新: {md_file_path}")


def batch_process_markdown_files(root_dir):
    """批量处理目录中的所有Markdown文件"""
    # 创建统一的图片目录
    image_dir = os.path.join(root_dir, "images")
    os.makedirs(image_dir, exist_ok=True)

    for root, dirs, files in os.walk(root_dir):
        # 跳过images目录本身
        if "images" in dirs and root == root_dir:
            dirs.remove("images")

        for file in files:
            if file.endswith('.md'):
                md_file_path = os.path.join(root, file)
                process_markdown_file(md_file_path)


if __name__ == "__main__":
    # 设置posts目录路径
    posts_dir = "./posts"  # 根据实际情况调整路径

    # 开始批量处理
    batch_process_markdown_files(posts_dir)

    print("所有Markdown文件处理完成!")

升级版带界面选择

  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
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
import os
import re
import requests
import hashlib
from urllib.parse import urlparse
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import threading
from PIL import Image
import io
import time

class MarkdownImageDownloader:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("Markdown图片下载工具")
        self.root.geometry("600x400")
        
        # 创建界面组件
        self.setup_ui()
        
        # 存储处理统计信息
        self.processed_files = 0
        self.downloaded_images = 0
        self.skipped_images = 0
        
    def setup_ui(self):
        # 主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 配置网格权重
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=1)
        
        # 选择目录标签和按钮
        ttk.Label(main_frame, text="Markdown文件目录:").grid(row=0, column=0, sticky=tk.W, pady=5)
        self.dir_path = tk.StringVar()
        ttk.Entry(main_frame, textvariable=self.dir_path, width=50).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=5)
        ttk.Button(main_frame, text="浏览...", command=self.browse_directory).grid(row=0, column=2, padx=5)
        
        # 图片目录设置
        ttk.Label(main_frame, text="图片保存目录:").grid(row=1, column=0, sticky=tk.W, pady=5)
        self.image_dir_path = tk.StringVar()
        ttk.Entry(main_frame, textvariable=self.image_dir_path, width=50).grid(row=1, column=1, sticky=(tk.W, tk.E), padx=5)
        ttk.Button(main_frame, text="浏览...", command=self.browse_image_directory).grid(row=1, column=2, padx=5)
        
        # 选项设置
        options_frame = ttk.LabelFrame(main_frame, text="选项", padding="5")
        options_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
        options_frame.columnconfigure(1, weight=1)
        
        self.use_relative_path = tk.BooleanVar(value=True)
        ttk.Checkbutton(options_frame, text="使用相对路径", variable=self.use_relative_path).grid(row=0, column=0, sticky=tk.W)
        
        self.overwrite_existing = tk.BooleanVar(value=False)
        ttk.Checkbutton(options_frame, text="覆盖已存在图片", variable=self.overwrite_existing).grid(row=0, column=1, sticky=tk.W)
        
        # 进度条
        ttk.Label(main_frame, text="进度:").grid(row=3, column=0, sticky=tk.W, pady=5)
        self.progress = ttk.Progressbar(main_frame, mode='indeterminate')
        self.progress.grid(row=3, column=1, columnspan=2, sticky=(tk.W, tk.E), pady=5)
        
        # 日志文本框
        ttk.Label(main_frame, text="处理日志:").grid(row=4, column=0, sticky=tk.W, pady=5)
        self.log_text = tk.Text(main_frame, height=15, width=70)
        self.log_text.grid(row=5, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
        
        # 滚动条
        scrollbar = ttk.Scrollbar(main_frame, orient=tk.VERTICAL, command=self.log_text.yview)
        scrollbar.grid(row=5, column=3, sticky=(tk.N, tk.S))
        self.log_text['yscrollcommand'] = scrollbar.set
        
        # 按钮框架
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=6, column=0, columnspan=3, pady=10)
        
        ttk.Button(button_frame, text="开始处理", command=self.start_processing).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="清空日志", command=self.clear_log).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="退出", command=self.root.quit).pack(side=tk.RIGHT, padx=5)
        
        # 配置主框架权重
        main_frame.rowconfigure(5, weight=1)
        
    def browse_directory(self):
        directory = filedialog.askdirectory(title="选择Markdown文件所在目录")
        if directory:
            self.dir_path.set(directory)
            # 自动设置图片目录为Markdown目录下的images文件夹
            self.image_dir_path.set(os.path.join(directory, "images"))
    
    def browse_image_directory(self):
        directory = filedialog.askdirectory(title="选择图片保存目录")
        if directory:
            self.image_dir_path.set(directory)
    
    def log_message(self, message):
        """添加消息到日志"""
        self.log_text.insert(tk.END, message + "\n")
        self.log_text.see(tk.END)
        self.root.update_idletasks()
    
    def clear_log(self):
        """清空日志"""
        self.log_text.delete(1.0, tk.END)
    
    def download_image(self, url, folder_path, filename=None):
        """下载图片到指定文件夹"""
        try:
            response = requests.get(url, stream=True, timeout=15)
            if response.status_code == 200:
                # 确定文件名
                if not filename:
                    parsed_url = urlparse(url)
                    original_filename = os.path.basename(parsed_url.path)
                    
                    # 清理文件名
                    if original_filename and '.' in original_filename:
                        name, ext = os.path.splitext(original_filename)
                        # 移除可能存在的查询参数
                        ext = ext.split('?')[0]
                        # 限制扩展名为常见图片格式
                        if ext.lower() not in ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp']:
                            # 从Content-Type获取扩展名
                            content_type = response.headers.get('content-type', '')
                            if 'jpeg' in content_type or 'jpg' in content_type:
                                ext = '.jpg'
                            elif 'png' in content_type:
                                ext = '.png'
                            elif 'gif' in content_type:
                                ext = '.gif'
                            elif 'webp' in content_type:
                                ext = '.webp'
                            else:
                                ext = '.jpg'  # 默认使用jpg
                        
                        # 创建友好文件名(限制长度)
                        clean_name = re.sub(r'[^\w\-_]', '_', name)
                        if len(clean_name) > 50:
                            clean_name = clean_name[:50]
                        filename = f"{clean_name}{ext}"
                    else:
                        # 从Content-Type获取扩展名
                        content_type = response.headers.get('content-type', '')
                        if 'jpeg' in content_type or 'jpg' in content_type:
                            ext = '.jpg'
                        elif 'png' in content_type:
                            ext = '.png'
                        elif 'gif' in content_type:
                            ext = '.gif'
                        elif 'webp' in content_type:
                            ext = '.webp'
                        else:
                            ext = '.jpg'
                        
                        # 使用URL的MD5哈希作为文件名
                        url_hash = hashlib.md5(url.encode()).hexdigest()[:8]
                        filename = f"image_{url_hash}{ext}"

                # 确保文件夹存在
                os.makedirs(folder_path, exist_ok=True)

                # 完整文件路径
                filepath = os.path.join(folder_path, filename)

                # 避免重复下载(除非设置了覆盖)
                if os.path.exists(filepath) and not self.overwrite_existing.get():
                    self.log_message(f"图片已存在,跳过: {filename}")
                    self.skipped_images += 1
                    return filepath

                # 保存图片
                with open(filepath, 'wb') as f:
                    for chunk in response.iter_content(1024):
                        f.write(chunk)

                self.log_message(f"下载成功: {filename}")
                self.downloaded_images += 1
                return filepath
            else:
                self.log_message(f"下载失败: HTTP状态码 {response.status_code} - {url}")
                return None
        except Exception as e:
            self.log_message(f"下载图片失败: {url}, 错误: {e}")
            return None
    
    def process_markdown_file(self, md_file_path, image_dir):
        """处理单个Markdown文件中的网络图片"""
        self.log_message(f"处理文件: {os.path.basename(md_file_path)}")
        
        with open(md_file_path, 'r', encoding='utf-8') as f:
            content = f.read()

        # 正则表达式匹配Markdown图片
        pattern = r'!\[(.*?)\]\((.*?)\)'
        matches = re.findall(pattern, content)

        changed = False
        for alt_text, url in matches:
            if url.startswith(('http://', 'https://')):
                local_path = self.download_image(url, image_dir)
                if local_path:
                    # 确定使用的路径类型
                    if self.use_relative_path.get():
                        # 使用相对路径
                        relative_path = os.path.relpath(local_path, os.path.dirname(md_file_path))
                        replacement_path = relative_path.replace('\\', '/')  # 确保使用正斜杠
                    else:
                        # 使用绝对路径
                        replacement_path = local_path.replace('\\', '/')
                    
                    content = content.replace(f"]({url})", f"]({replacement_path})")
                    changed = True

        # 如果有更改,写回文件
        if changed:
            # 创建备份文件
            backup_path = md_file_path + '.bak'
            with open(backup_path, 'w', encoding='utf-8') as f:
                f.write(content)
            
            # 重命名备份文件为原文件
            os.replace(backup_path, md_file_path)
            self.log_message(f"已更新: {os.path.basename(md_file_path)}")
        else:
            self.log_message(f"无需更新: {os.path.basename(md_file_path)}")
            
        self.processed_files += 1
    
    def batch_process_markdown_files(self):
        """批量处理目录中的所有Markdown文件"""
        root_dir = self.dir_path.get()
        image_dir = self.image_dir_path.get()
        
        if not root_dir or not os.path.exists(root_dir):
            messagebox.showerror("错误", "请选择有效的Markdown文件目录")
            return
            
        # 创建图片目录
        os.makedirs(image_dir, exist_ok=True)
        
        # 重置统计信息
        self.processed_files = 0
        self.downloaded_images = 0
        self.skipped_images = 0
        
        # 查找所有Markdown文件
        md_files = []
        for root, dirs, files in os.walk(root_dir):
            # 跳过图片目录
            if os.path.abspath(root) == os.path.abspath(image_dir):
                continue
                
            for file in files:
                if file.endswith('.md'):
                    md_files.append(os.path.join(root, file))
        
        total_files = len(md_files)
        self.log_message(f"找到 {total_files} 个Markdown文件")
        
        # 处理每个文件
        for i, md_file in enumerate(md_files):
            self.process_markdown_file(md_file, image_dir)
            
        # 显示处理结果
        self.log_message("\n处理完成!")
        self.log_message(f"已处理文件: {self.processed_files}/{total_files}")
        self.log_message(f"下载图片: {self.downloaded_images}")
        self.log_message(f"跳过图片: {self.skipped_images}")
        
        messagebox.showinfo("完成", f"处理完成!\n已处理文件: {self.processed_files}\n下载图片: {self.downloaded_images}")
    
    def start_processing(self):
        """开始处理文件(在线程中运行)"""
        if not self.dir_path.get():
            messagebox.showerror("错误", "请选择Markdown文件目录")
            return
            
        # 禁用开始按钮,防止重复点击
        self.root.children['!frame'].children['!frame2'].winfo_children()[0].config(state=tk.DISABLED)
        
        # 启动进度条
        self.progress.start()
        
        # 在线程中运行处理过程
        thread = threading.Thread(target=self.batch_process_markdown_files)
        thread.daemon = True
        thread.start()
        
        # 检查线程是否完成
        self.check_thread(thread)
    
    def check_thread(self, thread):
        """检查处理线程是否完成"""
        if thread.is_alive():
            # 线程仍在运行,稍后再次检查
            self.root.after(100, lambda: self.check_thread(thread))
        else:
            # 线程已完成,停止进度条并启用按钮
            self.progress.stop()
            self.root.children['!frame'].children['!frame2'].winfo_children()[0].config(state=tk.NORMAL)
    
    def run(self):
        """运行应用程序"""
        self.root.mainloop()

# 运行应用程序
if __name__ == "__main__":
    app = MarkdownImageDownloader()
    app.run()

把本地图片上传cos并自动修改本地图片地址

  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
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#!/usr/bin/env python3
"""
Markdown本地图片上传到腾讯云COS工具
作者:基于腾讯云SDK和网络资源编写
日期:2025-09-09
"""

import os
import re
import glob
import hashlib
from pathlib import Path
from qcloud_cos import CosConfig, CosS3Client


class MarkdownImageUploader:
    def __init__(self, secret_id, secret_key, region, bucket, folder="markdown-java"):
        """
        初始化腾讯云COS客户端

        :param secret_id: 腾讯云SecretId
        :param secret_key: 腾讯云SecretKey
        :param region: 存储桶地域
        :param bucket: 存储桶名称,格式为BucketName-APPID
        :param folder: COS中存储图片的文件夹路径
        """
        self.secret_id = secret_id
        self.secret_key = secret_key
        self.region = region
        self.bucket = bucket
        self.folder = folder

        # 初始化COS客户端
        config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key)
        self.client = CosS3Client(config)

        # 创建存储桶(如果不存在)
        self._create_bucket_if_not_exists()

        # 用于记录已上传的图片,避免重复上传
        self.uploaded_images = {}

    def _create_bucket_if_not_exists(self):
        """检查存储桶是否存在,如果不存在则创建"""
        try:
            self.client.head_bucket(Bucket=self.bucket)
            print(f"存储桶 {self.bucket} 已存在")
        except Exception:
            print(f"创建存储桶 {self.bucket}")
            try:
                self.client.create_bucket(
                    Bucket=self.bucket,
                    ACL="public-read"
                )
                print("存储桶创建成功")
            except Exception as e:
                print(f"创建存储桶失败: {str(e)}")
                raise

    def _get_file_md5(self, file_path):
        """计算文件的MD5值,用于判断文件是否重复"""
        hash_md5 = hashlib.md5()
        with open(file_path, "rb") as f:
            for chunk in iter(lambda: f.read(4096), b""):
                hash_md5.update(chunk)
        return hash_md5.hexdigest()

    def _upload_image_to_cos(self, image_path):
        """
        上传本地图片到腾讯云COS

        :param image_path: 本地图片路径
        :return: 图片的公共访问URL
        """
        # 检查文件是否已经上传过
        file_md5 = self._get_file_md5(image_path)
        if file_md5 in self.uploaded_images:
            print(f"图片已上传过: {image_path}")
            return self.uploaded_images[file_md5]

        # 生成COS中的文件名
        file_name = Path(image_path).name
        cos_key = f"{self.folder}/{file_name}"

        # 如果COS中已存在同名文件,添加前缀避免冲突
        index = 1
        original_cos_key = cos_key
        while any(obj['Key'] == cos_key for obj in self.client.list_objects(Bucket=self.bucket).get('Contents', [])):
            name, ext = os.path.splitext(original_cos_key)
            cos_key = f"{name}_{index}{ext}"
            index += 1

        try:
            # 上传图片到COS
            response = self.client.upload_file(
                Bucket=self.bucket,
                LocalFilePath=image_path,
                Key=cos_key,
                PartSize=1,
                MAXThread=10,
                EnableMD5=False
            )

            # 构建图片的公共URL
            image_url = f"https://{self.bucket}.cos.{self.region}.myqcloud.com/{cos_key}"

            # 缓存上传结果
            self.uploaded_images[file_md5] = image_url

            print(f"上传成功: {image_path} -> {image_url}")
            return image_url

        except Exception as e:
            print(f"上传图片失败 {image_path}: {str(e)}")
            return None

    def find_markdown_files(self, root_dir):
        """
        递归查找项目中的所有Markdown文件

        :param root_dir: 项目根目录
        :return: 找到的Markdown文件列表
        """
        markdown_files = []
        for root, _, files in os.walk(root_dir):
            for file in files:
                if file.endswith('.md'):
                    markdown_files.append(os.path.join(root, file))
        return markdown_files

    def extract_local_images(self, markdown_content, md_file_path):
        """
        从Markdown内容中提取本地图片引用

        :param markdown_content: Markdown文件内容
        :param md_file_path: Markdown文件路径,用于解析相对路径
        :return: 本地图片路径列表
        """
        # Markdown图片格式: ![alt text](path/to/image.png)
        pattern = r'!\[.*?\]\((.*?)\)'
        local_images = []

        md_dir = os.path.dirname(md_file_path)

        for match in re.findall(pattern, markdown_content):
            # 排除网络图片
            if match.startswith(('http://', 'https://', 'data:image/')):
                continue

            # 处理相对路径
            image_path = os.path.normpath(os.path.join(md_dir, match))

            # 检查文件是否存在
            if os.path.isfile(image_path):
                local_images.append((match, image_path))  # (原引用路径, 实际路径)
            else:
                print(f"图片文件不存在: {image_path}")

        return local_images

    def process_markdown_file(self, md_file_path):
        """
        处理单个Markdown文件:上传其中的本地图片并更新链接

        :param md_file_path: Markdown文件路径
        :return: 更新后的文件内容
        """
        with open(md_file_path, 'r', encoding='utf-8') as f:
            content = f.read()

        # 提取本地图片
        local_images = self.extract_local_images(content, md_file_path)

        if not local_images:
            print(f"文件中未找到本地图片: {md_file_path}")
            return content

        print(f"在文件 {md_file_path} 中找到 {len(local_images)} 张本地图片")

        # 上传图片并更新内容
        updated_content = content
        for orig_ref, image_path in local_images:
            image_url = self._upload_image_to_cos(image_path)
            if image_url:
                # 替换原引用为URL
                updated_content = updated_content.replace(orig_ref, image_url)

        return updated_content

    def process_project(self, project_root='.'):
        """
        处理整个项目中的所有Markdown文件

        :param project_root: 项目根目录路径
        """
        print(f"开始处理项目: {project_root}")

        # 查找所有Markdown文件
        md_files = self.find_markdown_files(project_root)
        print(f"找到 {len(md_files)} 个Markdown文件")

        # 处理每个文件
        for md_file in md_files:
            print(f"\n处理文件: {md_file}")
            try:
                updated_content = self.process_markdown_file(md_file)

                # 备份原文件并写入更新内容
                backup_path = md_file + '.bak'
                if not os.path.exists(backup_path):
                    os.rename(md_file, backup_path)

                with open(md_file, 'w', encoding='utf-8') as f:
                    f.write(updated_content)

                print(f"文件更新完成: {md_file}")

            except Exception as e:
                print(f"处理文件失败 {md_file}: {str(e)}")

        print(f"\n处理完成! 总共上传了 {len(self.uploaded_images)} 张图片")


def main():
    """主函数"""
    # 腾讯云配置(需要用户修改)
    secret_id = "AKIDDKEvToxvoJvLYYLvIexlNr"  # 替换为您的SecretId
    secret_key = "LVKzRfqoOGvPgleib"  # 替换为您的SecretKey
    region = "ap-chengdu"  # 替换为您的存储桶地域
    bucket = "tk-13131175"  # 替换为您的存储桶名称,格式为BucketName-APPID

    # 创建上传器实例
    uploader = MarkdownImageUploader(
        secret_id=secret_id,
        secret_key=secret_key,
        region=region,
        bucket=bucket,
        folder="markdown-java"  # COS中存储图片的文件夹
    )

    # 处理当前项目
    uploader.process_project()


if __name__ == "__main__":
    main()

把本地图片上传并在文章添加 以下Front Matter

构建Front Matter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
---
title: '{file_name}'
date: {current_time}
draft: false
categories: ["{folder_name}"]
tags: ["{file_name}"]
keywords: [""]
image: https://tk-
featuredImage: https://t
toc: true
math: false
---

脚本

  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
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
#!/usr/bin/env python3
"""
Markdown本地图片上传到腾讯云COS工具
作者:基于腾讯云SDK和网络资源编写
日期:2025-09-09
"""

import os
import re
import glob
import hashlib
from pathlib import Path
from datetime import datetime
from qcloud_cos import CosConfig, CosS3Client


class MarkdownImageUploader:
    def __init__(self, secret_id, secret_key, region, bucket, folder="markdown-tktest"):
        """
        初始化腾讯云COS客户端

        :param secret_id: 腾讯云SecretId
        :param secret_key: 腾讯云SecretKey
        :param region: 存储桶地域
        :param bucket: 存储桶名称,格式为BucketName-APPID
        :param folder: COS中存储图片的文件夹路径
        """
        self.secret_id = secret_id
        self.secret_key = secret_key
        self.region = region
        self.bucket = bucket
        self.folder = folder

        # 初始化COS客户端
        config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key)
        self.client = CosS3Client(config)

        # 创建存储桶(如果不存在)
        self._create_bucket_if_not_exists()

        # 用于记录已上传的图片,避免重复上传
        self.uploaded_images = {}

    def _create_bucket_if_not_exists(self):
        """检查存储桶是否存在,如果不存在则创建"""
        try:
            self.client.head_bucket(Bucket=self.bucket)
            print(f"存储桶 {self.bucket} 已存在")
        except Exception:
            print(f"创建存储桶 {self.bucket}")
            try:
                self.client.create_bucket(
                    Bucket=self.bucket,
                    ACL="public-read"
                )
                print("存储桶创建成功")
            except Exception as e:
                print(f"创建存储桶失败: {str(e)}")
                raise

    def _get_file_md5(self, file_path):
        """计算文件的MD5值,用于判断文件是否重复"""
        hash_md5 = hashlib.md5()
        with open(file_path, "rb") as f:
            for chunk in iter(lambda: f.read(4096), b""):
                hash_md5.update(chunk)
        return hash_md5.hexdigest()

    def _upload_image_to_cos(self, image_path):
        """
        上传本地图片到腾讯云COS

        :param image_path: 本地图片路径
        :return: 图片的公共访问URL
        """
        # 检查文件是否已经上传过
        file_md5 = self._get_file_md5(image_path)
        if file_md5 in self.uploaded_images:
            print(f"图片已上传过: {image_path}")
            return self.uploaded_images[file_md5]

        # 生成COS中的文件名
        file_name = Path(image_path).name
        cos_key = f"{self.folder}/{file_name}"

        # 如果COS中已存在同名文件,添加前缀避免冲突
        index = 1
        original_cos_key = cos_key
        while any(obj['Key'] == cos_key for obj in self.client.list_objects(Bucket=self.bucket).get('Contents', [])):
            name, ext = os.path.splitext(original_cos_key)
            cos_key = f"{name}_{index}{ext}"
            index += 1

        try:
            # 上传图片到COS
            response = self.client.upload_file(
                Bucket=self.bucket,
                LocalFilePath=image_path,
                Key=cos_key,
                PartSize=1,
                MAXThread=10,
                EnableMD5=False
            )

            # 构建图片的公共URL
            image_url = f"https://{self.bucket}.cos.{self.region}.myqcloud.com/{cos_key}"

            # 缓存上传结果
            self.uploaded_images[file_md5] = image_url

            print(f"上传成功: {image_path} -> {image_url}")
            return image_url

        except Exception as e:
            print(f"上传图片失败 {image_path}: {str(e)}")
            return None

    def find_markdown_files(self, root_dir):
        """
        递归查找项目中的所有Markdown文件

        :param root_dir: 项目根目录
        :return: 找到的Markdown文件列表
        """
        markdown_files = []
        for root, _, files in os.walk(root_dir):
            for file in files:
                if file.endswith('.md'):
                    markdown_files.append(os.path.join(root, file))
        return markdown_files

    def extract_local_images(self, markdown_content, md_file_path):
        """
        从Markdown内容中提取本地图片引用

        :param markdown_content: Markdown文件内容
        :param md_file_path: Markdown文件路径,用于解析相对路径
        :return: 本地图片路径列表
        """
        # Markdown图片格式: ![alt text](path/to/image.png)
        pattern = r'!\[.*?\]\((.*?)\)'
        local_images = []

        md_dir = os.path.dirname(md_file_path)

        for match in re.findall(pattern, markdown_content):
            # 排除网络图片
            if match.startswith(('http://', 'https://', 'data:image/')):
                continue

            # 处理相对路径
            image_path = os.path.normpath(os.path.join(md_dir, match))

            # 检查文件是否存在
            if os.path.isfile(image_path):
                local_images.append((match, image_path))  # (原引用路径, 实际路径)
            else:
                print(f"图片文件不存在: {image_path}")

        return local_images

    def _generate_front_matter(self, md_file_path):
        """
        生成Front Matter内容

        :param md_file_path: Markdown文件路径
        :return: Front Matter字符串
        """
        # 获取文件名(不含扩展名)
        file_name = Path(md_file_path).stem
        # 获取父文件夹名
        folder_name = Path(md_file_path).parent.name

        # 生成当前日期时间
        current_time = datetime.now().strftime("%Y-%m-%dT%H:%M:%S+08:00")

        # 构建Front Matter
        front_matter = f"""---
title: '{file_name}'
date: {current_time}
draft: false
categories: ["{folder_name}"]
tags: ["{file_name}"]
keywords: [""]
image: https://
featuredImage: h
toc: true
math: false
---

"""
        return front_matter

    def process_markdown_file(self, md_file_path):
        """
        处理单个Markdown文件:上传其中的本地图片并更新链接,并添加Front Matter

        :param md_file_path: Markdown文件路径
        :return: 更新后的文件内容
        """
        with open(md_file_path, 'r', encoding='utf-8') as f:
            content = f.read()

        # 检查是否已经有Front Matter(以---开头的内容)
        has_front_matter = content.startswith('---')

        # 提取本地图片
        local_images = self.extract_local_images(content, md_file_path)

        if not local_images:
            print(f"文件中未找到本地图片: {md_file_path}")
            # 如果没有图片需要上传,但仍然需要添加Front Matter
            if not has_front_matter:
                front_matter = self._generate_front_matter(md_file_path)
                content = front_matter + content
            return content

        print(f"在文件 {md_file_path} 中找到 {len(local_images)} 张本地图片")

        # 上传图片并更新内容
        updated_content = content
        for orig_ref, image_path in local_images:
            image_url = self._upload_image_to_cos(image_path)
            if image_url:
                # 替换原引用为URL
                updated_content = updated_content.replace(orig_ref, image_url)

        # 添加Front Matter(如果还没有)
        if not has_front_matter:
            front_matter = self._generate_front_matter(md_file_path)
            updated_content = front_matter + updated_content

        return updated_content

    def process_project(self, project_root='.'):
        """
        处理整个项目中的所有Markdown文件

        :param project_root: 项目根目录路径
        """
        print(f"开始处理项目: {project_root}")

        # 查找所有Markdown文件
        md_files = self.find_markdown_files(project_root)
        print(f"找到 {len(md_files)} 个Markdown文件")

        # 处理每个文件
        for md_file in md_files:
            print(f"\n处理文件: {md_file}")
            try:
                updated_content = self.process_markdown_file(md_file)

                # 直接写入更新内容(不创建备份)
                with open(md_file, 'w', encoding='utf-8') as f:
                    f.write(updated_content)

                print(f"文件更新完成: {md_file}")

            except Exception as e:
                print(f"处理文件失败 {md_file}: {str(e)}")

        print(f"\n处理完成! 总共上传了 {len(self.uploaded_images)} 张图片")


def main():
    """主函数"""
    # 腾讯云配置(需要用户修改)
    secret_id = "AKIDDKEvToGtJWWYYLIexlNr"  # 替换为您的SecretId
    secret_key = "LVKzRfqonfLIPNOGgeib"  # 替换为您的SecretKey
    region = "ap-chengdu"  # 替换为您的存储桶地域
    bucket = "tk-1312331175"  # 替换为您的存储桶名称,格式为BucketName-APPID

    # 创建上传器实例
    uploader = MarkdownImageUploader(
        secret_id=secret_id,
        secret_key=secret_key,
        region=region,
        bucket=bucket,
        folder="markdown-tktest"  # COS中存储图片的文件夹
    )

    # 处理当前项目
    uploader.process_project()


if __name__ == "__main__":
    main()

下载网络图片到指定目录,并转换为webp格式 返回新的文件名

  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
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
import os
import re
import requests
import hashlib
import time
from urllib.parse import urlparse
from pathlib import Path
from PIL import Image
import io

def download_image(url, output_dir):
    """
    下载网络图片到指定目录,并转换为webp格式
    返回新的文件名
    """
    try:
        # 发送HTTP请求获取图片
        response = requests.get(url, timeout=10)
        response.raise_for_status()
        
        # 生成唯一文件名(使用时间戳和哈希值)
        timestamp = time.strftime("%Y%m%d%H%M%S")
        hash_str = hashlib.md5(response.content).hexdigest()[:8]
        
        # 从URL获取文件扩展名,如果没有则使用png
        parsed_url = urlparse(url)
        orig_filename = os.path.basename(parsed_url.path)
        name, ext = os.path.splitext(orig_filename)
        if not ext:
            ext = ".png"
        
        # 转换为webp格式
        img = Image.open(io.BytesIO(response.content))
        
        # 生成新文件名
        new_filename = f"image-{timestamp}-{hash_str}.webp"
        output_path = os.path.join(output_dir, new_filename)
        
        # 保存为webp格式
        img.save(output_path, "WEBP")
        
        return new_filename
    except Exception as e:
        print(f"下载图片失败: {url}, 错误: {e}")
        return None

def process_markdown_files(md_dir, image_dir):
    """
    处理指定目录下的所有Markdown文件
    """
    # 确保图片目录存在
    os.makedirs(image_dir, exist_ok=True)
    
    # 遍历所有md文件
    for root, _, files in os.walk(md_dir):
        for file in files:
            if file.endswith('.md'):
                md_path = os.path.join(root, file)
                print(f"处理文件: {md_path}")
                
                with open(md_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                
                # 使用正则表达式查找所有图片标记
                pattern = r'!\[(.*?)\]\((.*?)\)'
                matches = re.findall(pattern, content)
                
                if not matches:
                    continue
                
                new_content = content
                for alt_text, url in matches:
                    # 只处理网络图片
                    if url.startswith('http'):
                        print(f"发现网络图片: {url}")
                        
                        # 下载图片
                        new_filename = download_image(url, image_dir)
                        
                        if new_filename:
                            # 构建新的相对路径
                            relative_path = f"../images/{new_filename}"
                            
                            # 替换原链接
                            new_content = new_content.replace(
                                f"![{alt_text}]({url})", 
                                f"![{alt_text}]({relative_path})"
                            )
                            print(f"已替换为: {relative_path}")
                
                # 写回更新后的内容
                with open(md_path, 'w', encoding='utf-8') as f:
                    f.write(new_content)

if __name__ == "__main__":
    # 设置目录路径
    current_dir = os.getcwd()
    md_dir = os.path.join(current_dir, "tkpost")
    image_dir = os.path.join(current_dir, "images")
    
    # 检查tkpost目录是否存在
    if not os.path.exists(md_dir):
        print(f"错误: 目录 {md_dir} 不存在")
        exit(1)
    
    # 处理Markdown文件
    process_markdown_files(md_dir, image_dir)
    print("处理完成!")
最后更新于 2025年9月10日 13:28
发布了 5 篇文章 | 共 23.8K 字 | 总访客数: Loading | 总访问量: Loading
Hugo theme Stack designed by Jimmy | 黔ICP备2025049100号-1