一个简单的羊毛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)
相关推荐
腾渊信息科技公司10 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
Smoothcloud润云11 小时前
国内GPU算力租赁平台横向测评:资源、成本、稳定性三维对比
人工智能·ai·云计算·gpu算力·gpu
赵康13 小时前
AI 写代码之后,Code Review 会议怎么开
ai·llm·skill
神奇霸王龙14 小时前
Claude Code屠榜:MiMo与Grok紧追Codex
服务器·网络·人工智能·gpt·ai·ai编程
TsingtaoAI14 小时前
3D高斯泼溅技术发展及其在具身智能领域的应用综述
人工智能·算法·ai·具身智能·高斯泼溅
汤姆yu15 小时前
CodeGeeX 4完整安装与实操使用全指南
人工智能·windows·ai·智能体·视频模型
人间凡尔赛16 小时前
2026 AI Agent 多智能体协同编程实战指南:从 Kimi K3 到 GPT-5.6 深度解析
ai·agent·多智能体·编程实战·waic·gpt-5.6·kimik3
ApacheSeaTunnel16 小时前
Apache SeaTunnel AI CLI Benchmark:7 款大模型、100 个 ETL 任务实测,谁真正能跑起来?
大数据·ai·开源·大模型·数据集成·cli·seatunnel·技术分享·数据同步
大卫小东(Sheldon)17 小时前
小鹤音形词典:一个用 Rust 写的离线编码查询桌面工具
ai·rust
前端Baymax18 小时前
Memory Search索引模型不匹配故障
ai·agent·infra