一个简单的羊毛claude-4.6最新版本的方法

如果翻墙后,https://arena.ai/,这个网站是个大模型竞技场的对比网站,里面N个网站,都可以免费使用,包括更新速度快,最新的比如claude 4.6都可以免费用的,但可惜

这个网站只能支持上传PDF,那么比如你有很多程序文件,假设放在某个文件夹下,则可以编写这一个PYTHO程序,运行后,把你的所有某个文件夹下的文件,每个内容都放到一个PDF中去,因为网站只支持每个PDF90页内,所以呢,你的文件多的话,则可以用这个程序,自动分卷为多个PDF,再上传到这个网站,就可以自动帮你分析你这些PDF里面的内容了,就等于你把你的很多个程序文件都上传到这个网站,可以羊毛claude-4.6了!下面给出程序:

python 复制代码
"""
代码文件夹导出为PDF工具 - 支持中文 + 自动分卷
将指定文件夹下所有代码文件的【完整代码内容】导出为PDF
超过指定页数时自动分卷,保证同一文件不会跨PDF
"""

import os
import sys
import io
from datetime import datetime
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.units import cm
from reportlab.lib.colors import HexColor
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, PageBreak, Flowable
)
from reportlab.lib.enums import TA_CENTER
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont


# ============================================================
# 配置
# ============================================================
MAX_PAGES_PER_PDF = 90  # 每个PDF最大页数

CODE_EXTENSIONS = {
    '.py', '.html', '.htm', '.css', '.js', '.json', '.xml',
    '.yaml', '.yml', '.txt', '.md', '.sql', '.sh', '.bat',
    '.java', '.cpp', '.c', '.h', '.ts', '.jsx', '.tsx', '.vue',
    '.conf', '.ini', '.cfg'
}

IGNORE_DIRS = {
    '__pycache__', '.git', '.svn', 'node_modules',
    '.idea', '.vscode', 'venv', 'env', '.env',
    'dist', 'build', '.tox', '.pytest_cache'
}

IGNORE_FILES = {'.DS_Store', 'Thumbs.db', '.gitignore'}


# ============================================================
# 注册中文字体
# ============================================================
def register_fonts():
    """注册字体,返回 (正文字体名, 代码字体名)"""

    if sys.platform == 'win32':
        font_dir = r'C:\Windows\Fonts'
        text_candidates = [
            (os.path.join(font_dir, 'msyh.ttc'), 'MSYaHei'),
            (os.path.join(font_dir, 'simhei.ttf'), 'SimHei'),
            (os.path.join(font_dir, 'simsun.ttc'), 'SimSun'),
        ]
    elif sys.platform == 'darwin':
        text_candidates = [
            ('/System/Library/Fonts/PingFang.ttc', 'PingFang'),
            ('/System/Library/Fonts/STHeiti Light.ttc', 'STHeiti'),
        ]
    else:
        text_candidates = [
            ('/usr/share/fonts/truetype/wqy/wqy-microhei.ttc', 'WQYMicroHei'),
            ('/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc', 'NotoSansCJK'),
        ]

    text_font = 'Helvetica'
    for fpath, fname in text_candidates:
        if os.path.exists(fpath):
            try:
                pdfmetrics.registerFont(TTFont(fname, fpath))
                text_font = fname
                print(f"  [OK] 字体: {fname}")
                break
            except Exception as e:
                print(f"  [WARN] {fname} 失败: {e}")

    # 代码字体直接用中文字体,保证中文能显示
    code_font = text_font
    print(f"  [INFO] 代码字体: {code_font}")

    return text_font, code_font


# ============================================================
# 水平线
# ============================================================
class HLine(Flowable):
    def __init__(self, width, color=HexColor('#CCCCCC'), thickness=1):
        Flowable.__init__(self)
        self.line_width = width
        self.color = color
        self.thickness = thickness
        self.height = thickness + 4

    def draw(self):
        self.canv.setStrokeColor(self.color)
        self.canv.setLineWidth(self.thickness)
        self.canv.line(0, 2, self.line_width, 2)


# ============================================================
# 页面计数器
# ============================================================
class PageCountDocTemplate(SimpleDocTemplate):
    """继承SimpleDocTemplate,记录总页数"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.page_count = 0

    def afterPage(self):
        self.page_count += 1


# ============================================================
# 工具函数
# ============================================================
def scan_folder(root_path):
    result = {}
    for dirpath, dirnames, filenames in os.walk(root_path):
        dirnames[:] = sorted([d for d in dirnames if d not in IGNORE_DIRS])
        rel_dir = os.path.relpath(dirpath, root_path)
        if rel_dir == '.':
            rel_dir = ''

        code_files = []
        for fname in sorted(filenames):
            if fname in IGNORE_FILES:
                continue
            ext = os.path.splitext(fname)[1].lower()
            if ext in CODE_EXTENSIONS:
                code_files.append((fname, os.path.join(dirpath, fname)))

        if code_files:
            result[rel_dir] = code_files

    sorted_result = []
    if '' in result:
        sorted_result.append(('', result['']))
    for key in sorted(result.keys()):
        if key != '':
            sorted_result.append((key, result[key]))
    return sorted_result


def read_file(file_path):
    try:
        size = os.path.getsize(file_path)
        if size > 5 * 1024 * 1024:
            return f"[文件过大跳过: {size / 1024 / 1024:.1f} MB]"
        for enc in ['utf-8', 'gbk', 'gb2312', 'latin-1']:
            try:
                with open(file_path, 'r', encoding=enc) as f:
                    return f.read()
            except (UnicodeDecodeError, UnicodeError):
                continue
        return "[无法解码文件内容]"
    except Exception as e:
        return f"[读取出错: {e}]"


def get_icon(ext):
    icons = {
        '.py': '[PY]', '.html': '[HTML]', '.htm': '[HTML]', '.css': '[CSS]',
        '.js': '[JS]', '.json': '[JSON]', '.xml': '[XML]', '.md': '[MD]',
        '.txt': '[TXT]', '.sql': '[SQL]', '.yaml': '[YAML]', '.yml': '[YAML]',
        '.sh': '[SH]', '.bat': '[BAT]', '.java': '[JAVA]', '.vue': '[VUE]',
    }
    return icons.get(ext, '[FILE]')


def format_size(size):
    if size < 1024:
        return f"{size} B"
    elif size < 1024 * 1024:
        return f"{size / 1024:.1f} KB"
    else:
        return f"{size / 1024 / 1024:.1f} MB"


def escape_xml(text):
    return (text
            .replace('&', '&amp;')
            .replace('<', '&lt;')
            .replace('>', '&gt;')
            .replace('"', '&quot;')
            .replace("'", '&#39;'))


# ============================================================
# 创建样式
# ============================================================
def create_styles(text_font, code_font):
    """创建所有需要的样式"""
    styles = {}

    styles['cover_title'] = ParagraphStyle(
        'CoverTitle', fontName=text_font, fontSize=26,
        alignment=TA_CENTER, textColor=HexColor('#2C3E50'), spaceAfter=20)

    styles['cover_info'] = ParagraphStyle(
        'CoverInfo', fontName=text_font, fontSize=12,
        alignment=TA_CENTER, textColor=HexColor('#7F8C8D'), spaceAfter=8)

    styles['toc_title'] = ParagraphStyle(
        'TOCTitle', fontName=text_font, fontSize=20,
        textColor=HexColor('#2C3E50'), spaceBefore=5, spaceAfter=12)

    styles['toc_dir'] = ParagraphStyle(
        'TOCDir', fontName=text_font, fontSize=12,
        textColor=HexColor('#E67E22'), spaceBefore=10, spaceAfter=3, leftIndent=10)

    styles['toc_file'] = ParagraphStyle(
        'TOCFile', fontName=text_font, fontSize=10,
        textColor=HexColor('#34495E'), spaceBefore=1, spaceAfter=1, leftIndent=30)

    styles['section'] = ParagraphStyle(
        'Section', fontName=text_font, fontSize=18,
        textColor=HexColor('#2980B9'), spaceBefore=10, spaceAfter=8)

    styles['fname'] = ParagraphStyle(
        'FName', fontName=text_font, fontSize=13,
        textColor=HexColor('#FFFFFF'), backColor=HexColor('#34495E'),
        spaceBefore=12, spaceAfter=4, borderPadding=(6, 10, 6, 10))

    styles['finfo'] = ParagraphStyle(
        'FInfo', fontName=text_font, fontSize=8,
        textColor=HexColor('#95A5A6'), spaceAfter=6, leftIndent=5)

    styles['code'] = ParagraphStyle(
        'Code', fontName=code_font, fontSize=7.5, leading=10,
        leftIndent=5, rightIndent=5, spaceBefore=3, spaceAfter=3,
        backColor=HexColor('#F7F8FA'), borderWidth=0.5,
        borderColor=HexColor('#DEE2E6'), borderPadding=6,
        wordWrap='CJK')

    styles['empty'] = ParagraphStyle(
        'Empty', fontName=text_font, fontSize=9,
        textColor=HexColor('#999999'), leftIndent=10)

    styles['vol_info'] = ParagraphStyle(
        'VolInfo', fontName=text_font, fontSize=14,
        alignment=TA_CENTER, textColor=HexColor('#E74C3C'), spaceAfter=10)

    return styles


# ============================================================
# 为单个文件构建 story 元素
# ============================================================
def build_file_story(fname, fpath, root_path, styles, usable_width):
    """为一个文件生成 story 元素列表"""
    elements = []
    ext = os.path.splitext(fname)[1].lower()
    rel_path = os.path.relpath(fpath, root_path)

    # 文件标题
    elements.append(Paragraph(f"{get_icon(ext)}  {fname}", styles['fname']))

    # 文件信息
    size = format_size(os.path.getsize(fpath))
    mtime = datetime.fromtimestamp(os.path.getmtime(fpath))
    elements.append(Paragraph(
        f"路径: {rel_path}  |  大小: {size}  |  "
        f"修改: {mtime.strftime('%Y-%m-%d %H:%M:%S')}", styles['finfo']))

    # 读取完整代码
    content = read_file(fpath)

    if content.strip():
        lines = content.split('\n')
        num_w = len(str(len(lines)))

        coded_lines = []
        for line_no, line in enumerate(lines, 1):
            safe = escape_xml(line)
            safe = safe.replace(' ', '&nbsp;')
            safe = safe.replace('\t', '&nbsp;&nbsp;&nbsp;&nbsp;')
            coded_lines.append(
                f"<font color='#AAAAAA'>{str(line_no).rjust(num_w)}</font>"
                f"<font color='#DDDDDD'>&nbsp;|&nbsp;</font>"
                f"<font color='#333333'>{safe}</font>"
            )

        code_html = '<br/>'.join(coded_lines)

        try:
            elements.append(Paragraph(code_html, styles['code']))
        except Exception:
            try:
                simple = escape_xml(content)
                simple = simple.replace(' ', '&nbsp;')
                simple = simple.replace('\t', '&nbsp;&nbsp;&nbsp;&nbsp;')
                simple = simple.replace('\n', '<br/>')
                elements.append(Paragraph(
                    f"<font color='#333333'>{simple}</font>", styles['code']))
            except Exception:
                elements.append(Paragraph(
                    f"[{fname} 内容渲染失败]", styles['empty']))
    else:
        elements.append(Paragraph("[空文件]", styles['empty']))

    elements.append(Spacer(1, 8))
    elements.append(HLine(usable_width, HexColor('#EEEEEE'), 0.5))

    return elements


# ============================================================
# 试算单个文件占多少页
# ============================================================
def estimate_pages(file_elements, usable_width):
    """
    将文件元素写入一个临时PDF(内存中),获取实际页数
    """
    buf = io.BytesIO()
    tmp_doc = PageCountDocTemplate(
        buf, pagesize=A4,
        leftMargin=1.5 * cm, rightMargin=1.5 * cm,
        topMargin=1.5 * cm, bottomMargin=1.5 * cm,
    )
    try:
        tmp_doc.build(list(file_elements))  # 用副本
        pages = tmp_doc.page_count
    except Exception:
        pages = 1
    buf.close()
    return pages


# ============================================================
# 构建封面和目录
# ============================================================
def build_cover(folder_name, root_path, file_list, styles, usable_width,
                vol_num=None, total_vols=None):
    """构建封面 + 目录页"""
    story = []

    # 封面
    story.append(Spacer(1, 80))
    story.append(Paragraph("代码文件导出", styles['cover_title']))
    story.append(Spacer(1, 20))

    if vol_num is not None:
        story.append(Paragraph(
            f"第 {vol_num} 卷 / 共 {total_vols} 卷", styles['vol_info']))
        story.append(Spacer(1, 10))

    story.append(Paragraph(f"项目: {folder_name}", styles['cover_info']))
    story.append(Paragraph(f"路径: {root_path}", styles['cover_info']))
    story.append(Paragraph(
        f"本卷文件数: {len(file_list)}", styles['cover_info']))
    story.append(Paragraph(
        f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
        styles['cover_info']))
    story.append(PageBreak())

    # 目录
    story.append(Paragraph("目 录", styles['toc_title']))
    story.append(HLine(usable_width, HexColor('#2C3E50'), 2))
    story.append(Spacer(1, 8))

    # file_list: [(rel_dir, fname, fpath), ...]
    current_dir = None
    idx = 0
    for rel_dir, fname, fpath in file_list:
        if rel_dir != current_dir:
            current_dir = rel_dir
            dir_label = "根目录" if rel_dir == '' else rel_dir
            story.append(Paragraph(f"[DIR] {dir_label}/", styles['toc_dir']))

        idx += 1
        ext = os.path.splitext(fname)[1].lower()
        size = format_size(os.path.getsize(fpath))
        story.append(Paragraph(
            f"{idx}. {get_icon(ext)} {fname}  ({size})", styles['toc_file']))

    story.append(PageBreak())
    return story


# ============================================================
# 主函数
# ============================================================
def generate_pdf(root_path, output_pdf=None, max_pages=MAX_PAGES_PER_PDF):
    root_path = os.path.abspath(root_path)
    folder_name = os.path.basename(root_path)

    if not os.path.isdir(root_path):
        print(f"  [ERROR] 不是有效文件夹: {root_path}")
        sys.exit(1)

    if output_pdf is None:
        output_pdf = f"{folder_name}_code_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"

    # 去掉扩展名,后面拼接卷号
    base_name, base_ext = os.path.splitext(output_pdf)
    if not base_ext:
        base_ext = '.pdf'

    print(f"\n{'=' * 60}")
    print(f"  源文件夹:    {root_path}")
    print(f"  输出文件名:  {base_name}{base_ext}")
    print(f"  每卷最大页数: {max_pages}")
    print(f"{'=' * 60}")

    # 1. 注册字体
    text_font, code_font = register_fonts()

    # 2. 扫描文件
    file_structure = scan_folder(root_path)
    total_files = sum(len(f) for _, f in file_structure)

    if total_files == 0:
        print("\n  [ERROR] 未找到代码文件!")
        return

    # 展开为扁平列表: [(rel_dir, fname, fpath), ...]
    flat_files = []
    for rel_dir, files in file_structure:
        for fname, fpath in files:
            flat_files.append((rel_dir, fname, fpath))

    print(f"\n  共 {total_files} 个文件:")
    for rel_dir, fname, fpath in flat_files:
        prefix = f"  /{folder_name}/" if rel_dir == '' else f"  /{folder_name}/{rel_dir}/"
        print(f"    {prefix}{fname}")

    # 3. 创建样式
    styles = create_styles(text_font, code_font)

    # 临时doc参数(用于估算页数)
    doc_kwargs = dict(
        pagesize=A4,
        leftMargin=1.5 * cm, rightMargin=1.5 * cm,
        topMargin=1.5 * cm, bottomMargin=1.5 * cm,
    )

    # 计算可用宽度
    page_w, page_h = A4
    usable_width = page_w - 1.5 * cm * 2

    # ============================================================
    # 4. 预计算每个文件的 story 和页数
    # ============================================================
    print(f"\n  正在预估每个文件页数...")

    file_data = []  # [(rel_dir, fname, fpath, story_elements, page_count), ...]

    for i, (rel_dir, fname, fpath) in enumerate(flat_files):
        rel_path = os.path.relpath(fpath, root_path)
        elements = build_file_story(fname, fpath, root_path, styles, usable_width)
        pages = estimate_pages(elements, usable_width)
        file_data.append((rel_dir, fname, fpath, elements, pages))

        line_count = len(read_file(fpath).split('\n'))
        print(f"    [{i + 1}/{total_files}] {rel_path}  "
              f"({line_count} 行, ~{pages} 页)")

    total_content_pages = sum(d[4] for d in file_data)
    print(f"\n  代码内容预估总页数: ~{total_content_pages} 页")

    # ============================================================
    # 5. 分卷:按页数限制分组,同一文件绝不拆分
    # ============================================================
    # 封面+目录估算为 2~3 页,预留 3 页
    COVER_TOC_RESERVE = 3

    volumes = []       # [[(rel_dir, fname, fpath, elements, pages), ...], ...]
    current_volume = []
    current_pages = COVER_TOC_RESERVE  # 当前卷已用页数(预留封面目录)

    for item in file_data:
        rel_dir, fname, fpath, elements, pages = item

        # 如果当前卷加上这个文件会超过限制
        if current_volume and (current_pages + pages > max_pages):
            # 保存当前卷,开始新卷
            volumes.append(current_volume)
            current_volume = []
            current_pages = COVER_TOC_RESERVE

        current_volume.append(item)
        current_pages += pages

    # 最后一卷
    if current_volume:
        volumes.append(current_volume)

    total_vols = len(volumes)

    if total_vols == 1:
        print(f"\n  总页数未超过 {max_pages},生成单个PDF")
    else:
        print(f"\n  需要分为 {total_vols} 卷:")
        for vi, vol in enumerate(volumes, 1):
            vol_pages = sum(item[4] for item in vol) + COVER_TOC_RESERVE
            vol_files = len(vol)
            print(f"    第 {vi} 卷: {vol_files} 个文件, ~{vol_pages} 页")

    # ============================================================
    # 6. 逐卷生成PDF
    # ============================================================
    output_files = []

    for vol_idx, volume_items in enumerate(volumes, 1):

        # 确定输出文件名
        if total_vols == 1:
            pdf_filename = f"{base_name}{base_ext}"
        else:
            pdf_filename = f"{base_name}_vol{vol_idx}of{total_vols}{base_ext}"

        print(f"\n{'- ' * 30}")
        print(f"  生成: {pdf_filename}")
        print(f"  包含 {len(volume_items)} 个文件")

        # 创建 doc
        doc = SimpleDocTemplate(pdf_filename, **doc_kwargs)
        story = []

        # 当前卷的文件列表(用于封面目录)
        vol_file_list = [(item[0], item[1], item[2]) for item in volume_items]

        # 封面 + 目录
        if total_vols == 1:
            cover = build_cover(
                folder_name, root_path, vol_file_list,
                styles, usable_width)
        else:
            cover = build_cover(
                folder_name, root_path, vol_file_list,
                styles, usable_width,
                vol_num=vol_idx, total_vols=total_vols)
        story.extend(cover)

        # 代码内容
        current_dir = None
        for item_idx, (rel_dir, fname, fpath, elements, pages) in enumerate(volume_items):

            # 如果进入新目录,打印目录标题
            if rel_dir != current_dir:
                current_dir = rel_dir
                dir_label = (f"根目录 ({folder_name}/)"
                             if rel_dir == '' else f"{rel_dir}/")
                story.append(Paragraph(f"[DIR] {dir_label}", styles['section']))
                story.append(HLine(usable_width, HexColor('#2980B9'), 1))
                story.append(Spacer(1, 6))

            # 加入文件内容
            story.extend(elements)

            rel_path = os.path.relpath(fpath, root_path)
            print(f"    + {rel_path}")

        # 写入PDF
        doc.build(story)
        pdf_size = format_size(os.path.getsize(pdf_filename))
        pdf_path = os.path.abspath(pdf_filename)
        output_files.append(pdf_path)
        print(f"  [OK] {pdf_path} ({pdf_size})")

    # ============================================================
    # 7. 汇总
    # ============================================================
    print(f"\n{'=' * 60}")
    print(f"  全部完成!")
    print(f"  项目: {folder_name}")
    print(f"  文件总数: {total_files}")
    print(f"  生成PDF: {total_vols} 个")

    for i, f in enumerate(output_files, 1):
        size = format_size(os.path.getsize(f))
        print(f"    [{i}] {f} ({size})")

    print(f"{'=' * 60}\n")
    return output_files


# ============================================================
# 入口
# ============================================================
if __name__ == '__main__':
    print("\n" + "=" * 60)
    print("  代码文件夹 -> PDF 导出工具")
    print("  - 支持中文注释/内容")
    print(f"  - 超过 {MAX_PAGES_PER_PDF} 页自动分卷")
    print("  - 同一文件不会跨卷拆分")
    print("=" * 60)

    if len(sys.argv) >= 2:
        folder = sys.argv[1]
        out = sys.argv[2] if len(sys.argv) >= 3 else None
    else:
        folder = input("\n请输入文件夹路径: ").strip().strip('"').strip("'")
        out_input = input("输出PDF文件名 (回车=自动命名): ").strip()
        out = out_input if out_input else None

    if not folder:
        print("  未输入路径!")
        sys.exit(1)

    generate_pdf(folder, out)
相关推荐
weixin_468466856 小时前
从“看懂”到“做到”:视觉-语言-动作模型如何让机器人真正执行指令
人工智能·ai·机器人·大语言模型·前沿科技
效率工作实验室11 小时前
自定义Agent平台有哪些?灵活搭建专属AI智能体的方案
ai·自定义·agent·平台·平台有哪些
知了一笑11 小时前
人与AI互相效力:谁才是受益者?
ai·aigc·ai产品·ai工作流
一只游鱼12 小时前
LangGraph快速入门 —— Agent 是怎么转起来的
人工智能·ai·agent·langgraph
西索斯coding13 小时前
gpt-image-2.5-sunburst 和 gemini-3.1-flash-image 差多少?60 组项目素材的成功成本评测框架
java·linux·gpt·ai
wflynn15 小时前
Claude Sonnet 5自主选药选坐标完成小鼠脑部手术,更强模型拒绝执行
ai·技术
这是谁的博客?16 小时前
AI 加速器 RTL 数字系统设计深度解析:从 MAC 阵列 Verilog 实现到时序收敛与时钟功耗的芯片落地全链路
人工智能·macos·ai·verilog·数字ic设计·ai芯片·rtl
信息安全失业大专人员17 小时前
每日了解一个智能体-WorkBuddy
开发语言·ai·c#·智能体
OpenAnolis小助手17 小时前
龙蜥社区亮相 GOSIM AI Vision Forum 共话“Agentic Native”开源新
人工智能·ai·开源·操作系统·龙蜥社区·agent
恩喜玛生物17 小时前
03_GROMACS分子动力学实战:从 Factor Xa 输入准备到轨迹分析
ai