markdown文章快速转微信公众号文章

markdown文章快速转微信公众号文章

我日常习惯使用typora进行文档的记录,由于优快云的骚操作,我计划将原有文档及部分本地文档慢慢更新到微信公众号,但存在以下问题:

  • typora的markdown和微信公众号的格式有差别,导致直接复制会存在问题
  • 当文章涉及图片,每次都要将图片手动复制到文章会格外麻烦,因此我需要设置一个图床
  • 我希望保留本地图片缓存,以避免图床失效

经过一些探索,得出了以下方案:

  • 微信markdown编辑器:https://github.com/doocs/md,该编辑器允许将markdown同步修改为微信公众号格式,只需一键复制然后粘贴到公众号即可

  • 使用PicGo和github实现图床

  • typora仍然保持添加图片会把保存在本地,无需做任意改动

具体操作如下:

  1. 使用docker部署微信markdown编辑器

    docker run -d -p 8085:80 doocs/md
    

    然后访问:

    http://localhost:8085/
    

    image-20250622200223726

  2. 安装picGo(https://github.com/Molunerfinn/PicGo)

    release中下载对应的安装程序直接安装即可,安装路径下有一个叫做PicGo.exe的程序,这很重要

  3. 我的picGo配置如下

    image-20250622202712139

    然后在设置中开启时间戳重命名

  4. 编写脚本对指定路径的markdown进行图片上传并替换其中的链接

    image-20250622203308175

    image-20250622203431502

  5. 将result.md中的内容复制到微信markdown编辑器

    image-20250622203854032

    微调好格式后直接复制

  6. 粘贴到微信公众号的编辑器中

    image-20250622204023961

    公众号会花一点时间将图片自动上传到mmbiz.qlogo.cn这个域名下,稍微等图片处理完成,如果有不行的,公众号会提示对指定图片重试,点重试就可以,等图片都处理完直接发布文章。

代码如下:

# -*- coding:utf-8 -*-
import csv
import json
import re
import os
import argparse
from pathlib import Path
from time import sleep
from typing import List, Dict, Optional, Any
import subprocess
import requests


def save_mapping(list1: List[Any], list2: List[Any], file_path: str, format: str = 'json') -> None:
    """
    将两个等长列表进行一对一映射并保存到本地文件

    参数:
        list1: 第一个列表
        list2: 第二个列表(与list1长度相同)
        file_path: 保存的文件路径
        format: 文件格式,支持'json'或'csv',默认为'json'

    异常:
        ValueError: 如果两个列表长度不一致
        ValueError: 如果指定的格式不支持
    """
    # 检查列表长度是否一致
    if len(list1) != len(list2):
        raise ValueError("两个列表的长度必须一致")

    # 创建映射字典
    mapping = dict(zip(list1, list2))

    # 根据指定格式保存文件
    if format.lower() == 'json':
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(mapping, f, ensure_ascii=False, indent=2)
    elif format.lower() == 'csv':
        with open(file_path, 'w', encoding='utf-8', newline='') as f:
            writer = csv.writer(f)
            writer.writerow(['key', 'value'])  # 表头
            writer.writerows(mapping.items())
    else:
        raise ValueError("不支持的格式,仅支持'json'或'csv'")


def open_command_in_new_window(command, title=None, timeout=None):
    try:
        # 构建启动命令
        start_cmd = "start"

        # 如果指定了标题,添加到命令中
        if title:
            start_cmd += f' "{title}"'

        # 添加命令
        start_cmd += f' cmd /c "{command}"'

        # 执行命令
        process = subprocess.Popen(start_cmd, shell=True)

        # 如果指定了超时时间,等待命令执行完成
        if timeout is not None:
            try:
                process.wait(timeout=timeout)
                return process.returncode == 0
            except subprocess.TimeoutExpired:
                print(f"命令执行超时 ({timeout}秒)")
                return False

        return True
    except FileNotFoundError:
        print(f"错误: 找不到命令或程序 - {command}")
        raise FileNotFoundError
    except PermissionError:
        print("错误: 没有执行命令的权限")
        raise PermissionError
    except subprocess.SubprocessError as e:
        print(f"子进程错误: {e}")
        raise subprocess.SubprocessError
    except Exception as e:
        print(f"未知错误: {e}")
        raise Exception


def extract_image_links(content: str) -> List[Dict[str, str]]:
    """从 Markdown 内容中提取本地图片链接"""
    image_pattern = r'\!\[(.*?)\]\((.*?)\)'
    # 匹配 HTML 图片语法:
    html_image_pattern = r'<img\s+[^>]*src="([^"]+)"[^>]*>'

    image_links = []

    # 提取 Markdown 格式的图片
    for match in re.finditer(image_pattern, content):
        alt_text, image_path = match.groups()
        # 检查是否为本地文件(非 http/https 开头)
        if not image_path.startswith(('http:', 'https:')):
            image_links.append({
                'type': 'markdown',
                'alt': alt_text,
                'path': image_path,
                'match': match
            })

    # 提取 HTML 格式的图片
    for match in re.finditer(html_image_pattern, content):
        image_path = match.group(1)
        # 检查是否为本地文件
        if not image_path.startswith(('http:', 'https:')):
            image_links.append({
                'type': 'html',
                'alt': '',  # HTML 中的 alt 可能在其他属性中,这里简化处理
                'path': image_path,
                'match': match
            })

    return image_links


def replace_image_links(content: str, old_links: list, new_links: list) -> str:
    """替换Markdown内容中的图片链接"""
    updated_content = content

    for old_link, new_link in zip(old_links, new_links):
        # 转义特殊字符,避免正则表达式错误
        escaped_old_link = re.escape(old_link)

        # 构建匹配模式,确保只替换完整的图片链接
        pattern = fr'(!\[(.*?)\]\()({escaped_old_link})(\))'

        # 使用lambda函数处理替换,确保只替换路径部分
        updated_content = re.sub(pattern, lambda m: f"{m.group(1)}{new_link}{m.group(4)}", updated_content)

    return updated_content


class MarkdownImageUploader:
    def __init__(self, input_file: str, picgo_path, image_dir, output_file: str = None, ):
        """初始化 Markdown 图片上传器"""
        self.input_file = Path(input_file)
        self.image_dir = image_dir
        self.output_file = Path(output_file) if output_file else self.input_file.with_name(
            f"{self.input_file.stem}_new{self.input_file.suffix}")
        self.picgo_path = picgo_path
        self.image_mapping = {}  # 存储原始图片路径与图床链接的映射
        self.storage_path = "image_mapping.json"
        # 尝试启动picGO,如果已经启动就啥问题没有
        open_command_in_new_window(command=picgo_path, title="pico server", timeout=None)
        self.load_image_mapping()

    def load_image_mapping(self):
        """
        尝试加载本地映射表 origin_image_path:new_image_url
        :return:
        """
        if os.path.exists(self.storage_path):
            try:
                with open(self.storage_path, 'r') as f:
                    self.image_mapping = json.load(f)
                print(f"数据已成功从 {self.storage_path} 加载")
            except Exception as e:
                print(f"加载数据时出错: {e}")
        else:
            print(f"存储文件 {self.storage_path} 不存在,将使用空字典")

    def save_mapping(self):
        """将image_mapping保存到本地JSON文件"""
        try:
            with open(self.storage_path, 'w') as f:
                json.dump(self.image_mapping, f, indent=4)
            print(f"数据已成功保存到 {self.storage_path}")
        except Exception as e:
            print(f"保存数据时出错: {e}")

    def upload_images(self, image_origin_path: str or list[str]):
        headers = {
            'Content-Type': 'application/json'
        }
        url = "http://127.0.0.1:36677/upload"

        image_list = []
        if isinstance(image_origin_path, str):
            image_list.append(image_origin_path)
        elif isinstance(image_origin_path, list):
            image_list = image_origin_path
        else:
            print("image_origin_path格式错误,请选择str or list[str]")
            return None

        for image_link in image_list:
            try:
                path = os.path.abspath(image_link)

                # 如果不存在,尝试获取图片路径
                if not os.path.exists(path):
                    image_filename = os.path.basename(image_link)
                    path= os.path.normpath(self.image_dir +"/"+ image_filename)
                # 如果本地映射表存在该映射就循环下一个
                if self.image_mapping.get(image_link):
                    continue
                # 重复上传直到当前url成功
                count = 0
                while 1:
                    payload = json.dumps({"list": [path]})
                    response = requests.post(url, headers=headers, data=payload)
                    result = json.loads(response.text)
                    if "result" in result and isinstance(result["result"], list):
                        if not self.image_mapping.get(image_link):
                            self.image_mapping[image_link] = result["result"][0]
                        # 上传成功停止1秒
                        sleep(1)
                        break
                    count += 1
                    if count > 5:
                        print("当前图片重复上传次数超过5次,检查图片路径。原始路径:%s, 请求路径:%s"%(image_link, path))

            except Exception as e:
                print("处理%s时出错"%image_link)
                print(e)
        return None

    def process_markdown(self) -> None:
        """处理 Markdown 文件并替换图片链接"""
        try:
            # 读取 Markdown 文件内容
            with open(self.input_file, 'r', encoding='utf-8') as f:
                content = f.read()

            # 提取本地图片链接
            image_links = extract_image_links(content)
            print(image_links)
            if not image_links:
                print("没有找到需要处理的本地图片链接")
                return

            print(f"找到 {len(image_links)} 个本地图片链接")

            origin_image_list = []
            for item in image_links:
                path = item.get('path')
                origin_image_list.append(path)
            print(origin_image_list)
            self.upload_images(origin_image_list)

            # 替换文档中的url之前需要保存图片映射表
            self.save_mapping()

            for origin_image_path, url in self.image_mapping.items():
                content = re.sub(re.escape(origin_image_path), url, content)

            # 写入更新后的内容
            with open(self.output_file, 'w', encoding='utf-8') as f:
                f.write(content)

            print(f"Markdown文件已更新并保存至: {self.output_file}")


        except Exception as e:
            print(f"错误: 处理 Markdown 文件时发生意外错误: {e}")


def main():
    parser = argparse.ArgumentParser(description='Markdown 图片上传并替换链接工具')
    parser.add_argument('input', help='输入的 Markdown 文件路径')
    parser.add_argument('-o', '--output', help='输出的 Markdown 文件路径,默认为原文件名添加 "_new" 后缀')
    parser.add_argument('-p', '--picgo', default='picgo', help='PicGo 可执行文件路径,默认为 "picgo"')
    parser.add_argument('-i', '--image_dir', required=True, help='图片所在目录')
    args = parser.parse_args()

    # 检查输入文件是否存在
    if not os.path.exists(args.input):
        print(f"错误: 输入文件不存在: {args.input}")
        return

    # 检查输入文件是否为 Markdown 文件
    if not args.input.lower().endswith(('.md', '.markdown')):
        print(f"错误: 输入文件不是 Markdown 文件: {args.input}")
        return

    # 创建并运行 Markdown 图片上传器
    uploader = MarkdownImageUploader(args.input, picgo_path=args.picgo, image_dir=args.image_dir, output_file=args.output)
    uploader.process_markdown()


if __name__ == "__main__":
    # python md_image_uploader.py E:\document\笔记\网络安全\(4)无线安全\无线局域网\无线局域网技术分析及攻击实战.md -p D:\picgo\PicGo.exe -o result.md -i E:\document\笔记\网络安全\(4)无线安全\无线局域网\assets
    main()

文章已同步至公众号,求关注:

image-20250622210310220

### 微信公众号爬虫 PDF 资料获取方法 为了获取有关微信公众号爬虫的 PDF 资料,可以考虑以下几个途径: #### 1. 使用现有工具和服务 一些第三方服务提供微信公众号文章的采集功能,并支持多种格式导出。这些平台通常具备强大的数据分析能力以及丰富的接口文档[^1]。 例如,某些专业的数据采集工具有如下特点: - 支持采集微信公众号文章、阅读量、点赞数等信息。 - 提供将文章导出为 HTML、Word、PDF、Markdown 和 TXT 等不同格式的功能。 ```python import requests def download_pdf(url, filename="weixin_article.pdf"): response = requests.get(url) with open(filename, 'wb') as file: file.write(response.content) download_pdf("https://example.com/path/to/pdf", "wechat_spider_guide.pdf") ``` #### 2. 参考开源项目和技术社区分享 许多开发者会在 GitHub 或其他代码托管平台上发布自己的微信公众号爬虫项目源码。通过参与这样的开源项目或加入相关技术交流群组,可以获得一手的技术资料和支持[^2]。 此外,在线论坛和技术博客也是寻找此类资源的好地方。很多博主会撰写详细的教程并附上完整的实现方案,甚至直接提供打包好的 PDF 文件下载链接。 #### 3. 访问学术数据库和电子图书馆 对于更深入的研究需求,还可以访问各大高校院系网站上的开放存取论文库或是商业性质较强的 IEEE Xplore Digital Library 这样的专业文献检索系统来查找最新的研究成果报告。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值