怎么把知乎里的好答案原封不动的保存下来?

后面给出了现成的程序,直接保存下来,就能用。

而且可以保存收藏夹。


核心思路:


1.让ai写一个把选中文本保存为md格式的程序。
2.再让ai写一个autohotkey脚本,来设置快捷键调用这个程序。
之后选中内容,按下快捷键,就自动保存了。
3.md格式可以用obsidian打开,里面可以装自动保存图片的插件。


(下面有程序说明,还有不懂的直接问ai就好)

说明:


1.这个方案的好处是,简单稳定。基本不依赖任何外在的插件程序了。

2.可以随意修改,包括标题格式,内容格式等等。 我目前的内容格式是,一部分是源代码,另一部分是处理后的简单的md格式。 另外甚至可以加ai自动总结放在最前面(这个我去掉了,如果需要可以让ai帮你写一个)。


代码1,python程序

import win32clipboard
import time
import os
from datetime import datetime
import html2text
from bs4 import BeautifulSoup
import re
from html.parser import HTMLParser
import sys
import inspect



ROOT_PATH = r"D:\剪藏2024-6-20\clipper"
    
class ContentProcessor:
    """Base class for content processing"""
    
    def __init__(self, root_path):
        self.root_path = root_path
        
    def sanitize_filename(self, filename):
        invalid_chars = r'[\\/*?:"<>|]'
        return re.sub(invalid_chars, "_", filename)
    
    def get_first_line(self, text, max_chars=70):
        lines = text.split('\n')
        first_line = ""
        
        for line in lines:
            stripped_line = line.strip().strip('```')
            if not stripped_line:
                continue
            if "SourceURL" in line:
                continue
            if re.match(r'^[A-Za-z0-9-]+$', stripped_line):
                continue
            first_line = line.strip()
            break
            
        first_line = first_line.lstrip('#').strip()
        match = re.search(r'^\[([^\]]+)\]', first_line)
        if match:
            first_line = match.group(1)
            
        return first_line[:max_chars] if len(first_line) > max_chars else first_line
    
    def remove_specific_lines(self, text):
        keywords = ["Version:", "StartHTML:", "EndHTML:", "StartFragment:", "EndFragment:"]
        lines = text.split('\n')
        source_urls = [line for line in lines if "SourceURL:" in line]
        filtered_lines = [line for line in lines if not any(keyword in line for keyword in keywords)]
        return '\n'.join(filtered_lines), source_urls
    
    def remove_unwanted_newlines(self, text):
        pattern = r'(?<![\.\!\?。!?\s])\n\s*(?!\n)'
        text=re.sub(pattern, ' ', text)
        return text
    
    # 通用。对每个文本都处理 2025-4-15 17:40:16
    def remove_unwanted_newlines_normal(self, text):
        pattern_newline_after_link=r'\)\n'
        text=re.sub(pattern_newline_after_link, ')', text)

        pattern_square_bracket_newline = r'\[\s*\n'
        text = re.sub(pattern_square_bracket_newline, '[', text)

        return text


    def convert_html_to_md(self, html_content):
        h = html2text.HTML2Text()
        return self.remove_unwanted_newlines_normal(h.handle(html_content))
        # return h.handle(html_content)
    
    def determine_source_type(self, source_urls):
        if not source_urls or not isinstance(source_urls, list) or not isinstance(source_urls[0], str):
            # return None
            return "other"
            
        first_url = source_urls[0].lower()
        
        source_mappings = {
            "ai_chat": {"chatbox", "yuanbao.tencent.com", "deepseek.com", "qwenlm.ai", "moonshot.cn", "qwen.ai","aistudio.google.com","gemini.google.com","chatgpt.com"},
            "zhihucollection": {"zhihu.com/collection"},
            "zhihu": {"zhihu.com"},
            "weibo": {"weibo.com"},
            "xhs": {"xiaohongshu.com"},
            "wx": {"weixin.qq.com"},
        }
        
        for source_type, keywords in source_mappings.items():
            if any(re.search(r'\b' + re.escape(keyword) + r'\b', first_url) for keyword in keywords):
                return source_type
                
        return "other"
    
    def process_content(self, html_content, need_summary=True):
        html_data, source_urls = self.remove_specific_lines(html_content)
        source_type = self.determine_source_type(source_urls)
        md_data = self.convert_html_to_md(html_data)
        
        summary = ''
        if need_summary:
            try:
                # summary = summarize_message(md_data, source_type, self.ai_model_name)
                summary =''
            except:
                pass
                
        return {
            'html_data': html_data,
            'md_data': md_data,
            'summary': summary,
            'source_type': source_type,
            'source_urls': source_urls
        }
    
    def save_to_file(self, processed_data, custom_path=None):
        summary = processed_data['summary']
        html_data = processed_data['html_data']
        md_data = processed_data['md_data']
        source_type = processed_data['source_type']
        source_urls = processed_data['source_urls']
        
        summary_first_line = self.sanitize_filename(self.get_first_line(summary))
        first_line = self.sanitize_filename(self.get_first_line(md_data))
 
        
        file_path, output_data = self.generate_file_path(
            source_type, first_line, summary, html_data, md_data, summary_first_line, custom_path
        )
        
        os.makedirs(os.path.dirname(file_path), exist_ok=True)
        with open(file_path, "w", encoding="utf-8") as f:
            f.write(output_data)
        print(f"成功保存到 {file_path}")
    
    def generate_file_path(self, source_type, first_line, summary, html_data, md_data, summary_first_line, custom_path=None):
        """To be implemented by subclasses"""
        raise NotImplementedError

class GeneralContentProcessor(ContentProcessor):
    """Processes general web content"""
    
    def __init__(self, root_path=ROOT_PATH, ai_model_name=''):
        super().__init__(root_path)
        self.ai_model_name = ai_model_name
    
    def generate_file_path(self, source_type, first_line, summary, html_data, md_data, summary_first_line, custom_path=None):
        timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
        if source_type=='other':
            output_data = summary + '\n\n' + html_data + '\n' + self.remove_unwanted_newlines(md_data)
        else:
            output_data = summary + '\n\n' + html_data + '\n' + md_data

        if source_type == 'ai_chat':
            filename = fr"【ai问答】{first_line}{timestamp}.md"
            path = os.path.join(self.root_path, 'ai问答') if not custom_path else custom_path
        elif source_type == 'other':
            filename = fr"{first_line}{timestamp}.md"
            path = self.root_path if not custom_path else custom_path

        elif source_type == 'weibo':
            # 微博来源
            source_mark = 'weibo'
            filename = fr"【{source_mark}{summary_first_line}-{first_line}{timestamp}.md"
            path = self.root_path
        else:
            if len(first_line) < 15:
                filename = fr"【{source_type}{first_line}-{summary_first_line}{timestamp}.md"
            else:
                filename = fr"【{source_type}{first_line}{timestamp}.md"
            path = self.root_path if not custom_path else custom_path
            
        return os.path.join(path, filename), output_data


class ZhihuContentProcessor(ContentProcessor):
    """Processes Zhihu collection content"""
    
    def __init__(self, root_path=ROOT_PATH,ai_model_name=''):
        super().__init__(root_path)
        self.ai_model_name = ai_model_name
    
    def get_zhihu_title(self, html_content):
        try:
            if not html_content or not isinstance(html_content, str):
                return ""
            soup = BeautifulSoup(html_content, 'html.parser')
            title_tag = soup.find(['h2', 'h1'])
            return title_tag.text.strip() if title_tag else ""
        except Exception as e:
            print(f"Error parsing HTML: {e}")
            return ""
    
    def get_zhihu_author(self, html_content):
        soup = BeautifulSoup(html_content, 'html.parser')
        author_tag = soup.find('div', class_='AuthorInfo-head') or soup.find('a', class_='UserLink-link')
        return author_tag.text.strip() if author_tag and author_tag.text.strip() else ""
    

    def generate_file_path(self, source_type, first_line, summary, html_data, md_data, summary_first_line, custom_path=None):
        timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
        output_data = summary + '\n\n' + html_data + '\n' + md_data
        
        # 知乎来源
        source_mark = '知乎'
        title = self.get_zhihu_title(html_data)  # 假设有一个函数 get_zhihu_title
        author = self.get_zhihu_author(html_data)  # 假设有一个函数 get_zhihu_author
        if title:
            filename = fr"【{source_mark}{title}-{author}{timestamp}.md"
        else:
            filename = fr"【{source_mark}{summary_first_line}-{author}{timestamp}.md"
        filename=self.sanitize_filename(filename)
        file_path = os.path.join(self.root_path, filename)

        return file_path, output_data



class ZhihuCollectionContentProcessor(ZhihuContentProcessor):
    """Processes Zhihu collection content"""
    
    def __init__(self, root_path=os.path.join(ROOT_PATH, '知乎收藏夹')):
        super().__init__(root_path)
    
    def get_zhihu_collection_divs(self, html_content):
        soup = BeautifulSoup(html_content, 'html.parser')
        specific_divs = soup.find_all('div', class_='jsNavigable CollectionDetailPageItem css-vurnku')
        return specific_divs if specific_divs else soup.find_all('div', class_='List-item')
    
    def process_collection(self, html_content):
        collection_divs = self.get_zhihu_collection_divs(html_content)
        need_summary = len(collection_divs) == 1
        
        for div in collection_divs:
            html_data = "SourceURL:https://www.zhihu.com/" + '\n' + str(div)
            processed_data = self.process_content(html_data, need_summary)
            self.save_to_file(processed_data)
            
        return len(collection_divs)


class ClipboardManager:
    """Manages clipboard operations"""
    
    @staticmethod
    def get_clipboard_html():
        try:
            win32clipboard.OpenClipboard()
            html_format = win32clipboard.RegisterClipboardFormat("HTML Format")
            if win32clipboard.IsClipboardFormatAvailable(html_format):
                return win32clipboard.GetClipboardData(html_format).decode('utf-8')
            return None
        except Exception as e:
            print(f"发生错误: {str(e)}")
            return None
        finally:
            try:
                win32clipboard.CloseClipboard()
            except:
                pass



def get_all_processors():
    processors = {}
    # 获取当前模块中所有对象
    for name, obj in globals().items():
        # 检查是否是类,并且继承自 ContentProcessor
        if inspect.isclass(obj) and issubclass(obj, ContentProcessor) and obj is not ContentProcessor:
            # 获取类名,去掉 "ContentProcessor" 后缀
            key = name.replace("ContentProcessor", "").lower()
            processors[key] = obj()  # 实例化类并加入字典
    return processors

def main():

    # 初始化所有处理器
    processors = get_all_processors()

    general_processor = GeneralContentProcessor(ROOT_PATH)

    html_content = ClipboardManager.get_clipboard_html()
    if not html_content:
        print("剪贴板中没有HTML格式内容")
        return
    
    
    # 3. 分析内容来源类型,选择对应的处理器
    _, source_urls = general_processor.remove_specific_lines(html_content)
    source_type = general_processor.determine_source_type(source_urls)
    
    # 根据source_type选择处理器
    # "根据 source_type 从 processors 字典中获取对应的处理器,如果找不到匹配的处理器,就默认使用 general_processor"。
    processor = processors.get(source_type, general_processor)
    
    if source_type == 'zhihucollection':
        # 1. 首先尝试处理为知乎收藏夹内容 processors['zhihucollection']
        zhihu_count = processor.process_collection(html_content)
        # if zhihu_count > 0:
        #     return  # 如果是知乎收藏夹内容,处理完成后直接返回


    # 处理内容并保存
    processed_data = processor.process_content(html_content)
    processor.save_to_file(processed_data)

if __name__ == "__main__":
    main()


说明:

1.这个是你文件想保存的路径:ROOT_PATH = r"D:\剪藏2024-6-20\clipper"


保存收藏夹的说明:

2.这个程序,除了保存选中的单个回答,可以全选你的收藏夹页面,用同样的方式保存。

但是需要先展开收藏夹页面的回答: 可以用这个代码,保存为书签栏,点击一下自动展开

javascript:setInterval(function(){ let buttonElements2=document.querySelectorAll('.Button.ContentItem\\-more.Button\\-\\-plain');for(var element of buttonElements2){element.click()};},100);void(0);

2.autohotkey程序

^q:: ; Ctrl+Q快捷键
    Send, ^c ; 发送Ctrl+C进行复制
    Sleep, 200 ; 等待100毫秒,确保复制操作完成
    Run, pythonw.exe d:\auto_clipper_module.py ; 运行Python文件
return


说明:

  1. 这个改成你python文件保存的路径: d:\auto_clipper_module.py




AutoHotkey 脚本安装与使用说明

这个脚本的作用是将 Ctrl+Q 设置为快捷键,一键完成“复制内容 + 运行Python脚本处理”的自动化流程。以下是详细的安装和使用步骤:

一、 环境准备

  1. 安装 AutoHotkey 软件
  • 如果尚未安装,请前往 AutoHotkey 官网 下载并安装 v1.1 或 v2.0 版本(你提供的代码语法兼容 v1.1,建议安装 v1.1 版本以保证完美兼容)。
  • 安装完成后,电脑中 .ahk 后缀的文件将显示为 AutoHotkey 图标。


  1. 准备 Python 脚本
  • 确保你已经保存了上一段对话中的 Python 代码。
  • 关键步骤:根据你提供的 AHK 代码,Python 脚本必须命名为 auto_clipper_module.py 并且必须放在 D:\ 盘根目录下(即 D:\auto_clipper_module.py)。
  • 如果你的 Python 脚本位置或名称不同,请修改下方 AHK 代码中的路径。
    二、 脚本安装步骤


  1. 创建脚本文件
  • 在桌面或任意文件夹空白处,右键 -> 新建 -> AutoHotkey Script (如果没有此选项,新建文本文档也可)。
  • 将文件重命名为 QuickClipper.ahk(名字可自定义,后缀必须是 .ahk)。


  1. 编辑代码
  • 右键点击该文件,选择 Edit Script (或使用记事本打开)。
  • 将以下代码复制并粘贴进去(已为你修正注释中的时间描述):

^q:: ; Ctrl+Q 快捷键 Send, ^c ; 发送 Ctrl+C 进行复制 Sleep, 200 ; 等待 200 毫秒,确保复制操作完成 Run, pythonw.exe d:\auto_clipper_module.py ; 静默运行 Python 文件(无黑框) return

  1. 保存并运行
  • 保存文件并关闭编辑器。
  • 双击 QuickClipper.ahk 文件运行。
  • 如果在任务栏右下角出现绿色的 “H” 图标,说明脚本已开始后台监听。
    三、 使用方法


  1. 选中内容:在浏览器、PDF 或文档中选中你想要保存的文字或网页区域。
  2. 按下快捷键:按下键盘上的 Ctrl + Q
  3. 自动执行
  • 脚本会自动模拟按下 Ctrl+C(复制)。
  • 等待 0.2 秒。
  • 后台运行 Python 脚本处理剪贴板内容并保存文件。


  1. 查看结果:前往 D:\剪藏2024-6-20\clipper 目录查看生成的 Markdown 文件。
    四、 进阶设置(开机自启)
    如果你希望电脑开机后自动运行这个快捷键脚本:
  2. 选中你的 QuickClipper.ahk 文件。
  3. Ctrl+C 复制。
  4. Win+R 打开运行窗口,输入 shell:startup 并回车。
  5. 在打开的文件夹中按 Ctrl+V 粘贴快捷方式即可。
    五、 常见问题
  • Q: 按下快捷键没反应?
    • 检查任务栏是否有 AutoHotkey 图标。如果没有,说明脚本未启动。
    • 检查 Python 脚本路径是否正确。代码中写的是 d:\auto_clipper_module.py,如果你的脚本在别的文件夹,请修改 AHK 代码中的路径。


  • Q: 为什么用 pythonw.exe 而不是 python.exe
    • pythonw.exe 运行时不会弹出黑色的命令行窗口(CMD),体验更流畅。如果需要调试错误,可以临时改回 python.exe 以查看报错信息。






详细使用说明(ai撰写)

功能概述

此脚本用于自动处理剪贴板中的HTML格式内容,根据内容来源(如知乎、微博、微信、AI对话等)进行分类处理,转换为Markdown格式并保存到本地文件。支持以下功能:

  1. 自动识别来源:通过URL关键词识别内容来源(如知乎、微博等)。
  2. 格式转换:将HTML内容转换为Markdown格式。
  3. 文件命名:根据内容来源、标题、时间戳等自动生成文件名。
  4. 分类存储:不同来源的内容保存到对应子目录(如AI问答内容保存到ai问答目录)。
  5. 特殊处理:对知乎收藏夹内容进行批量处理。
    运行环境要求
  • 操作系统:Windows(依赖 win32clipboard 模块)。
  • Python版本:Python 3.x。
    安装依赖
    在运行前,需安装以下依赖库:
pip install pywin32 html2text beautifulsoup4

使用步骤

  1. 复制内容:在浏览器或其他应用中复制需要保存的内容(需复制为HTML格式)。
  2. 按快捷键ctrl+q
  3. 查看结果
  • 处理后的文件将保存到 ROOT_PATH 指定的目录(默认为 D:\剪藏2024-6-20\clipper)。
  • 不同来源的内容会保存到对应子目录(如 ai问答知乎收藏夹)。
    配置修改
    如需修改默认存储路径,可编辑脚本中的 ROOT_PATH 变量:
ROOT_PATH = r"D:\你的自定义路径\clipper"

支持的来源类型及保存规则

来源类型识别关键词(URL中)保存路径/文件名格式
AI对话chatbox, deepseek.com, chatgpt.comai问答/【ai问答】{标题}{时间戳}.md
知乎收藏夹zhihu.com/collection知乎收藏夹/【知乎】{标题}-{作者}{时间戳}.md
知乎zhihu.com【知乎】{标题}-{作者}{时间戳}.md
微博weibo.com【weibo】{摘要首行}-{首行}{时间戳}.md
微信weixin.qq.com【wx】{首行}{时间戳}.md
其他不匹配上述关键词{首行}{时间戳}.md

注意事项

  1. 剪贴板格式:必须复制HTML格式内容。如果剪贴板无HTML格式,脚本会提示“剪贴板中没有HTML格式内容”。
  2. 知乎收藏夹:如果识别为知乎收藏夹内容,脚本会自动批量处理收藏夹中的每个条目。
  3. 文件名清理:文件名中的非法字符(如 \ / * ? : " < > |)会被替换为下划线 _
  4. 摘要功能:代码中摘要生成部分被注释,如需启用需自行实现 summarize_message 函数。
    示例
  5. 复制一篇知乎文章内容,运行脚本后,会在 D:\剪藏2024-6-20\clipper 目录下生成类似 【知乎】如何学习Python-张三2026-03-12_15-30-00.md 的文件。
  6. 复制一段ChatGPT对话,运行后会在 D:\剪藏2024-6-20\clipper\ai问答 目录下生成类似 【ai问答】Python编程技巧2026-03-12_15-30-00.md 的文件。 如有其他需求,可修改脚本中的 source_mappings 字典或自定义 ContentProcessor 子类进行扩展。

编辑于 2026-03-14 · 著作权归作者所有