Skill: jin-liquor/arxiv-paper-resolver (version 1.0.1)

--- name: arxiv-paper-resolver description: 接收 arXiv 论文链接，自动提取章节结构、下载 PDF、解析 HTML 全文，生成按论文自身章节结构编排的中文文档 tags: [Research, Papers, Documentation, Automation] --- # arXiv 论文解析与归档技能 ## 触发条件 - 用户提供 arXiv 论文链接（如 `https://arxiv.org/abs/2604.25836`） - 用户要求查找、解析并生成某篇论文的中文文档 ## 核心功能 1. **章节提取**：从 arXiv HTML 实验版解析论文的完整章节结构（h2 主节 + h3 子节） 2. **自动下载**：从 abs 页面获取 Access Paper 三个链接，下载 PDF 3. **目录归档**：按论文标题创建子目录，保存 PDF、章节文件、元数据 4. **中文翻译**：将各章节翻译为中文，生成按论文自身章节结构编排的 MD 文档（不包含参考文献/引用内容） ## 工作流程 ### 完整流程概览 ``` 用户提供 arXiv 链接 ↓ Step 1: 运行提取脚本（自动完成） ├── 获取 abs 页面 → 提取标题 + Access Paper 三链接 ├── 创建论文标题命名的子目录 ├── 下载 PDF ├── 获取 HTML 实验版全文 ├── 解析 h2/h3 章节结构 └── 保存章节文件 + 元数据 JSON ↓ Step 2: 读取章节文件（人工/LLM 完成） ├── 读取章节结构总览 └── 逐章读取原始内容 ↓ Step 3: 翻译并组装中文文档（LLM 完成） ├── 按章节依次翻译为中文 ├── 保持数学符号和 LaTeX 公式不变 └── 生成完整 MD 文档 ↓ 完成：PDF + 中文文档 已归档到目录 ``` ### 安装与依赖 ```bash pip install -r scripts/requirements.txt ``` ### 步骤 1: 运行提取脚本 调用 `scripts/arxiv_section_extractor.py`，传入 arXiv ID（支持 URL 或裸 ID）： ```bash python3 scripts/arxiv_section_extractor.py 2604.25836 # 或指定输出目录 python3 scripts/arxiv_section_extractor.py 2604.25836 -o ~/papers # 或通过环境变量 export ARXIV_PAPERS_DIR=~/papers python3 scripts/arxiv_section_extractor.py 2604.25836 ``` 脚本自动完成： - 从 abs 页面获取论文标题和三个 Access Paper 链接 - 创建 `{output_dir}/{论文标题slug}/` 目录 - 下载 PDF - 获取 HTML 实验版全文 - 解析所有 h2 章节（含 h3 子节），跳过 References / Appendix 等附录 - 保存元数据 JSON、章节结构总览 ### 步骤 2: 读取章节结构 先读章节结构总览了解论文组织： ```bash cat {output_dir}/{slug}/{arxiv_id}_section_structure.txt ``` ### 步骤 3: 依次读取各章节原文 ```bash ls {output_dir}/{slug}/{arxiv_id}_raw_sections/ ``` 按编号顺序读取 `01_*.txt`、`02_*.txt`……每个文件包含该章的完整原文。 ### 步骤 4: 翻译并生成中文 MD 文档 对每个章节： 1. 读取原始英文内容 2. 翻译为中文（保持数学符号/LaTeX 不变） 3. 按论文自身章节结构组装 **⚠️ 格式要求（严格执行）：** **A. 行内公式必须使用 `$...$`（Typora 兼容）** - ✅ 正确：`$S_{\text{TC}} = \frac{1}{N_{\text{valid}}}$` - ❌ 错误：`\(S_{\text{TC}} = \frac{1}{N_{\text{valid}}}\)`（不渲染） - ❌ 错误：``$`S_{\text{TC}} = \frac{1}{N_{\text{valid}}}`$``（带反引号乱码） - 生成后必须验证：文件中所有公式定界符都是纯 `$`，不能有反引号 `` ` `` 或 `\(`/`\)` **B. 禁止使用 HTML 锚点标签** - ❌ 错误：`<a id="section-1"></a>`（在 Typora 中显示为多余字段） - 目录链接（如 `[1. 章节名](#section-1)`）也应一并省略，Typora 不支持此类锚点跳转 **C. 文档结构（最终 MD 格式）：** ```markdown # 论文标题（原文+中文译名） ## 基本信息 - **论文标题**：... - **arXiv ID**：... - **链接**：... - **PDF**：`{output_dir}/{slug}/{arxiv_id}.pdf` --- ## 摘要 （翻译后的中文摘要，无公式则直接写文字） --- ## 目录 - [1. 章节名](#) - [2. 章节名](#) - ... --- ## 1. 章节名（翻译后的中文名） （翻译后的完整内容，含 h3 子节。公式必须用 `$...$` 包裹。） --- ## 2. 章节名 ... ``` ### 步骤 5: 保存 保存为 `{output_dir}/{slug}/{arxiv_id}_中文文档.md` ### 步骤 6（可选）：基于文档生成技术演示 PPT 当用户要求"做成 PPT"、"生成演示文稿"、"做 slides"时，在完成中文文档后，使用 HTML 幻灯片技能生成演示文稿。 ## 脚本说明 ### `scripts/arxiv_section_extractor.py` 唯一的核心脚本，封装了所有自动化操作。 **用法：** ```bash python3 arxiv_section_extractor.py <arxiv_id> # 或 python3 arxiv_section_extractor.py https://arxiv.org/abs/2604.25836 # 指定输出目录 python3 arxiv_section_extractor.py 2604.25836 -o ~/papers ``` **依赖：** `requests`, `beautifulsoup4` **功能：** 1. `extract_arxiv_id()` — 从 URL 或裸 ID 中提取 arxiv_id 2. `get_paper_info_from_abs()` — 从 abs 页面获取标题和三个 Access Paper 链接 3. `fetch_html_content()` — 获取 HTML 实验版全文（自动尝试 v1 和无后缀） 4. `extract_sections_from_html()` — 解析 h2 章节结构和 h3 子节 5. `save_section_files()` — 将各章节保存为独立文件，生成章节结构总览 6. `sanitize_title()` — 将标题转为安全目录名 **输出目录结构：** ``` {output_dir}/{title-slug}/ ├── {arxiv_id}.pdf ← 原始 PDF ├── {arxiv_id}_中文文档.md ← 最终中文文档（由 LLM 生成） ├── {arxiv_id}_metadata.json ← 论文元数据 ├── {arxiv_id}_section_structure.txt ← 章节结构总览 └── {arxiv_id}_raw_sections/ ← 各章节原文 ├── 00_abstract.txt ├── 01_Introduction.txt ├── 02_Section_Name.txt └── ... ``` ## 输出示例 ``` {output_dir}/strongly-quasi-pseudometric-aggregation-functions/ ├── 2604.25836.pdf ← PDF ├── 2604.25836_中文文档.md ← 中文文档 ├── 2604.25836_metadata.json ← 元数据 ├── 2604.25836_section_structure.txt ← 章节结构 └── 2604.25836_raw_sections/ ← 原文章节 ├── 00_abstract.txt ├── 01_Introduction.txt ├── 02_Quasi-pseudometric_aggregation_functions.txt ├── 03_Strongly...on_products.txt └── 04_Strongly...on_sets.txt ``` ## 注意事项 1. **公式定界符（关键！）**：翻译生成的中文文档中，所有 LaTeX 行内公式必须使用 **`$...$`** 作为定界符。**禁止使用** `\(...\)`（Typora 不渲染），也**禁止使用** ``$`...`$``（带反引号导致乱码）。生成后需用 `grep '$' 文档.md | head` 验证定界符正确。 2. **禁止 HTML 标签**：文档中**不得出现**任何 HTML 标签，包括 `<a id="...">` 锚点、`<br>` 换行等。目录无需带锚点链接，直接写 `- [章节名](#)` 或纯文本列表即可。 3. **翻译约定**：数学符号和 LaTeX 命令（如 `\mathcal`, `\text`, `\sum`）保留原样不翻译。技术术语（如 LLM, MLLM, DV, Agent 等）首字母缩写保留原文，可在其后括号加中文注释。不包含参考文献/引用内容（如 [1], [2] 等引用标记应删除）。 4. **章节跳过**：脚本自动跳过 References、Appendix、Supplementary 等附录章节。 5. **HTML 地址**：脚本自动尝试 `{id}v1` 和 `{id}` 两种地址格式。 6. **输出目录**：默认 `~/papers/`，可通过 `-o` 参数或 `ARXIV_PAPERS_DIR` 环境变量自定义。 7. **错误处理**：PDF 下载失败不影响章节提取；HTML 获取失败则终止流程。

path: README.md
content: # arXiv Paper Resolver

> 自动提取 arXiv 论文章节结构、下载 PDF、解析 HTML 全文，生成按论文自身章节结构编排的中文文档。

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/)

## 📌 功能

- **自动解析**：从 arXiv HTML 实验版（`arxiv.org/html/`）提取论文全文
- **章节拆分**：按 h2 主节 + h3 子节结构拆分为独立文件，跳过 References / Appendix
- **PDF 下载**：自动下载论文 PDF
- **元数据提取**：标题、作者、摘要、arXiv 信息
- **中文文档生成**：支持 LLM 逐章翻译为中文 MD 文档（`$...$` 公式、兼容 Typora）
- **目录归档**：按论文标题自动创建 slug 目录，文件结构清晰

## 🚀 快速开始

### 安装

```bash
git clone https://github.com/jin-liquor/arxiv-paper-resolver.git
cd arxiv-paper-resolver
pip install -r scripts/requirements.txt
```

### 使用

```bash
# 通过 arXiv ID
python3 scripts/arxiv_section_extractor.py 2604.25836

# 通过 URL
python3 scripts/arxiv_section_extractor.py https://arxiv.org/abs/2604.25836

# 指定输出目录
python3 scripts/arxiv_section_extractor.py 2604.25836 -o ~/my_papers

# 通过环境变量配置输出目录
export ARXIV_PAPERS_DIR=~/my_papers
python3 scripts/arxiv_section_extractor.py 2604.25836
```

## 📁 输出结构

```
{output_dir}/{paper-title-slug}/
├── {arxiv_id}.pdf                    ← 原始 PDF
├── {arxiv_id}_metadata.json          ← 元数据（标题、作者、分类等）
├── {arxiv_id}_section_structure.txt  ← 章节结构总览
├── {arxiv_id}_中文文档.md            ← 中文翻译文档（由 LLM 生成）
└── {arxiv_id}_raw_sections/          ← 各章节原文
    ├── 00_abstract.txt
    ├── 01_Introduction.txt
    ├── 02_Method.txt
    └── ...
```

## 🛠️ 与 LLM / AI Agent 搭配使用

本工具设计为 AI Agent 的论文处理前段。典型工作流：

```
用户: "帮我读这篇论文 https://arxiv.org/abs/2604.25836"

Agent:
  1. 运行 extractor 脚本 → 提取章节 + 下载 PDF
  2. 读取各章节原文
  3. 逐章翻译为中文，保持 LaTeX 公式不变
  4. 生成中文 MD 文档

用户: "做成 PPT"
Agent: 基于中文文档生成 HTML 演示文稿
```

可作为 Hermes Agent、Claude Code、Codex 等 AI Agent 的 skill 集成。

## 📋 依赖

- Python 3.8+
- `requests` — HTTP 请求
- `beautifulsoup4` — HTML 解析
- `urllib3` — HTTP 底层库（requests 依赖）

## ⚠️ 注意事项

- PDF 下载失败不影响章节提取流程
- HTML 获取失败会终止流程（自动尝试 `v1` 和裸 ID 两种地址）
- 脚本自动跳过 References、Appendix、Supplementary 等附录章节
- arXiv HTML 实验版（`arxiv.org/html/`）格式可能随 arXiv 更新而变化

## 🤝 贡献

欢迎提交 Issue 和 PR！可贡献方向：

- 多语言翻译支持
- BibTeX 自动生成
- 公式 / 图表提取
- 批量解析
- 与 Obsidian / Notion 等笔记工具集成

## 📄 许可

MIT License


path: scripts/arxiv_section_extractor.py
content: #!/usr/bin/env python3
"""
arXiv 论文章节提取器

可复用脚本：接收 arXiv ID，自动提取论文信息、下载 PDF、解析 HTML 全文章节结构

用法:
  python arxiv_section_extractor.py <arxiv_id>
  python arxiv_section_extractor.py <arxiv_id> -o ~/papers

输出目录:
  {output_dir}/{paper-title-slug}/
    ├── {arxiv_id}.pdf                  ← 原始 PDF
    ├── {arxiv_id}_raw_sections/        ← 原始章节文件（供翻译用）
    │   ├── 00_abstract.txt
    │   ├── 01_introduction.txt
    │   └── ...
    ├── {arxiv_id}_metadata.json        ← 论文元数据
    └── {arxiv_id}_section_structure.txt ← 章节层级结构

依赖: requests, beautifulsoup4
"""

import requests
import re
import os
import sys
import json
import argparse
from bs4 import BeautifulSoup


def sanitize_title(title):
    """将论文标题转为安全的目录名"""
    name = title.lower()
    name = re.sub(r'[^a-z0-9\s-]', '', name)
    name = re.sub(r'\s+', '-', name.strip())
    name = re.sub(r'-+', '-', name)
    return name[:80].rstrip('-')


def fetch_page(url, timeout=30):
    """安全地获取页面内容"""
    headers = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'}
    resp = requests.get(url, headers=headers, timeout=timeout)
    resp.raise_for_status()
    return resp.text


def extract_arxiv_id(raw_input):
    """从 URL 或裸 ID 中提取 arxiv_id"""
    raw_input = raw_input.strip()
    m = re.search(r'arxiv\.org/abs/(\d+\.\d+)', raw_input)
    if m:
        return m.group(1)
    m = re.search(r'arxiv\.org/html/(\d+\.\d+)', raw_input)
    if m:
        return m.group(1)
    m = re.match(r'(\d+\.\d+)', raw_input)
    if m:
        return m.group(1)
    return None


def get_paper_info_from_abs(arxiv_id):
    """从 abs 页面获取论文标题和 Access Paper 链接"""
    abs_url = f"https://arxiv.org/abs/{arxiv_id}"
    html = fetch_page(abs_url)
    soup = BeautifulSoup(html, 'html.parser')

    # 提取标题
    title_el = soup.find('h1', class_='title')
    paper_title = ""
    if title_el:
        paper_title = title_el.get_text().strip()
        paper_title = re.sub(r'^Title:\s*', '', paper_title, flags=re.IGNORECASE)

    # 提取 Access Paper 的三个链接
    links = {'pdf': '', 'html': '', 'tex': ''}

    pdf_link = soup.find('a', {'class': 'download-pdf'})
    if pdf_link and pdf_link.get('href'):
        href = pdf_link['href']
        links['pdf'] = f"https://arxiv.org{href}" if href.startswith('/') else href

    html_link = soup.find('a', {'id': 'latexml-download-link'})
    if html_link and html_link.get('href'):
        links['html'] = html_link['href']

    tex_link = soup.find('a', {'class': 'download-eprint'})
    if tex_link and tex_link.get('href'):
        href = tex_link['href']
        links['tex'] = f"https://arxiv.org{href}" if href.startswith('/') else href

    return paper_title, links


def fetch_html_content(arxiv_id):
    """获取 HTML 实验版全文，自动尝试 v1 和无后缀"""
    urls_to_try = [
        f"https://arxiv.org/html/{arxiv_id}v1",
        f"https://arxiv.org/html/{arxiv_id}"
    ]
    for url in urls_to_try:
        try:
            html = fetch_page(url)
            print(f"  HTML 获取成功: {url} ({len(html)} 字符)")
            return html, url
        except Exception as e:
            print(f"  尝试 {url} 失败: {e}")
    return None, None


def extract_sections_from_html(html_content, html_url):
    """从 HTML 实验版中提取所有章节及内容"""
    soup = BeautifulSoup(html_content, 'html.parser')

    result = {
        'title': '',
        'authors': '',
        'abstract': '',
        'arxiv_info': '',
        'sections': []
    }

    # 获取标题
    title_el = soup.find('h1', class_='ltx_title_document')
    if not title_el:
        title_el = soup.find('h1')
    if title_el:
        result['title'] = title_el.get_text().strip()

    # 获取作者
    authors_el = soup.find('div', class_='ltx_authors')
    if authors_el:
        authors_text = authors_el.get_text().strip()
        authors_text = re.sub(r'[\u2070-\u2079\u00b2\u00b3\u00b9]+', '', authors_text)
        authors_text = re.sub(r'\s+', ' ', authors_text).strip()
        result['authors'] = authors_text

    # 获取摘要
    abstract_el = soup.find('div', id='abstract1')
    if not abstract_el:
        abstract_el = soup.find('blockquote', class_='abstract')
    if abstract_el:
        abstract_text = abstract_el.get_text().strip()
        abstract_text = re.sub(r'^Abstract[:\\s]*', '', abstract_text, flags=re.IGNORECASE)
        result['abstract'] = abstract_text

    # 从页脚获取 arXiv 信息
    body_text = soup.get_text()
    arxiv_id_match = re.search(
        r'arXiv:\s*[\d\.]+v?\d*\s*\[[^\]]+\]\s*\d+\s+\w+\s+\d{4}',
        body_text
    )
    if arxiv_id_match:
        result['arxiv_info'] = arxiv_id_match.group(0).strip()

    # 提取各 h2 章节
    h2_tags = soup.find_all('h2')

    for h2 in h2_tags:
        h2_text = h2.get_text(strip=True)

        # 跳过 References 和附录
        skip_keywords = ['References', 'references', 'Instructions for reporting',
                         'Acknowledgments', 'acknowledgements', 'Appendix', 'appendix',
                         'Supplementary', 'supplementary']
        if any(kw in h2_text for kw in skip_keywords):
            break

        # 提取 h2 后的所有内容
        content_parts = []
        sub_sections = []

        for sibling in h2.find_next_siblings():
            if sibling.name == 'h2':
                break
            if sibling.name == 'h3':
                sub_title = sibling.get_text(strip=True)
                sub_content = []
                for sub_sib in sibling.find_next_siblings():
                    if sub_sib.name in ['h2', 'h3']:
                        break
                    txt = sub_sib.get_text(strip=True)
                    if txt:
                        sub_content.append(txt)
                sub_sections.append({
                    'title': sub_title,
                    'content': '\n\n'.join(sub_content)
                })
                continue

            txt = sibling.get_text(strip=True)
            if txt:
                content_parts.append(txt)

        full_content = '\n\n'.join(content_parts)
        if len(full_content) < 50:
            continue

        # 确定章节编号
        section_num = ""
        section_name = h2_text
        num_match = re.match(r'(\d+)\.?\s*(.*)', h2_text)
        if num_match:
            section_num = num_match.group(1)
            section_name = num_match.group(2).strip() or section_name

        result['sections'].append({
            'heading': h2_text,
            'content': full_content,
            'number': section_num,
            'subsections': sub_sections
        })

    return result


def save_section_files(paper_dir, arxiv_id, parsed):
    """将提取的章节保存为独立文件"""
    sections_dir = os.path.join(paper_dir, f"{arxiv_id}_raw_sections")
    os.makedirs(sections_dir, exist_ok=True)

    saved_files = []

    # 保存摘要
    if parsed['abstract']:
        meta_file = os.path.join(sections_dir, "00_abstract.txt")
        with open(meta_file, 'w', encoding='utf-8') as f:
            f.write(parsed['abstract'])
        saved_files.append(meta_file)

    # 保存每个章节
    for sec in parsed['sections']:
        idx = parsed['sections'].index(sec)
        heading = sec['heading']
        safe_name = re.sub(r'[^\w\s-]', '', heading).strip()
        safe_name = re.sub(r'\s+', '_', safe_name)[:50]
        filename = f"{idx+1:02d}_{safe_name}.txt"

        filepath = os.path.join(sections_dir, filename)

        content = f"# {heading}\n\n{sec['content']}"

        for sub in sec.get('subsections', []):
            content += f"\n\n### {sub['title']}\n\n{sub['content']}"

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

        saved_files.append(filepath)
        print(f"  章节已保存: {filename} ({len(content)} 字符)")

    # 保存章节结构总览
    struct_lines = [f"论文标题: {parsed['title']}"]
    struct_lines.append(f"章节数: {len(parsed['sections'])}")
    struct_lines.append("")
    for i, sec in enumerate(parsed['sections'], 1):
        struct_lines.append(f"  {i}. {sec['heading']}")
        for sub in sec.get('subsections', []):
            struct_lines.append(f"     - {sub['title']}")

    struct_path = os.path.join(paper_dir, f"{arxiv_id}_section_structure.txt")
    with open(struct_path, 'w', encoding='utf-8') as f:
        f.write('\n'.join(struct_lines))

    saved_files.append(struct_path)
    return saved_files


def main():
    parser = argparse.ArgumentParser(description='arXiv 论文章节提取器')
    parser.add_argument('input', help='arXiv ID 或 URL（如 2604.25836 或 https://arxiv.org/abs/2604.25836）')
    parser.add_argument('-o', '--output-dir',
                        default=os.environ.get('ARXIV_PAPERS_DIR', os.path.join(os.path.expanduser('~'), 'papers')),
                        help='论文输出根目录（默认: ~/papers/，可设置 ARXIV_PAPERS_DIR 环境变量）')
    args = parser.parse_args()

    raw_input = args.input
    arxiv_id = extract_arxiv_id(raw_input)
    if not arxiv_id:
        print(f"错误: 无法从 '{raw_input}' 中提取 arXiv ID")
        sys.exit(1)

    print("=" * 60)
    print(f"arXiv 论文提取器")
    print(f"论文 ID: {arxiv_id}")
    print("=" * 60)

    # 步骤 1: 获取 abs 页面信息
    print("\n[1/5] 获取论文信息...")
    paper_title, access_links = get_paper_info_from_abs(arxiv_id)
    if not paper_title:
        print("错误: 无法获取论文标题")
        sys.exit(1)
    print(f"  标题: {paper_title}")
    print(f"  PDF:  {access_links['pdf']}")
    print(f"  HTML: {access_links['html']}")
    print(f"  TeX:  {access_links['tex']}")

    # 步骤 2: 创建目录
    print("\n[2/5] 创建目录...")
    slug = sanitize_title(paper_title)
    paper_dir = os.path.join(args.output_dir, slug)
    os.makedirs(paper_dir, exist_ok=True)
    print(f"  目录: {paper_dir}")

    # 步骤 3: 下载 PDF
    print("\n[3/5] 下载 PDF...")
    pdf_path = os.path.join(paper_dir, f"{arxiv_id}.pdf")
    try:
        pdf_resp = requests.get(access_links['pdf'], stream=True, timeout=60)
        pdf_resp.raise_for_status()
        with open(pdf_path, 'wb') as f:
            for chunk in pdf_resp.iter_content(8192):
                f.write(chunk)
        pdf_size = os.path.getsize(pdf_path)
        print(f"  PDF 已下载: {pdf_path} ({pdf_size/1024:.0f} KB)")
    except Exception as e:
        print(f"  PDF 下载失败: {e}")

    # 步骤 4: 获取 HTML 实验版
    print("\n[4/5] 获取 HTML 实验版全文...")
    html_content, html_url = fetch_html_content(arxiv_id)
    if not html_content:
        print("错误: 无法获取 HTML 内容")
        sys.exit(1)

    # 步骤 5: 解析章节并保存
    print("\n[5/5] 解析章节结构...")
    parsed = extract_sections_from_html(html_content, html_url)
    print(f"  共解析出 {len(parsed['sections'])} 个章节")

    saved_files = save_section_files(paper_dir, arxiv_id, parsed)

    # 保存元数据 JSON
    metadata = {
        'arxiv_id': arxiv_id,
        'title': paper_title,
        'title_slug': slug,
        'authors': parsed['authors'],
        'abstract': parsed['abstract'],
        'arxiv_info': parsed['arxiv_info'],
        'links': access_links,
        'section_count': len(parsed['sections']),
        'sections': [
            {'number': s.get('number', ''), 'heading': s['heading'],
             'subsections': [sub['title'] for sub in s.get('subsections', [])]}
            for s in parsed['sections']
        ],
        'paper_dir': paper_dir,
        'pdf_path': pdf_path,
        'sections_dir': os.path.join(paper_dir, f"{arxiv_id}_raw_sections"),
        'structure_file': os.path.join(paper_dir, f"{arxiv_id}_section_structure.txt")
    }

    meta_path = os.path.join(paper_dir, f"{arxiv_id}_metadata.json")
    with open(meta_path, 'w', encoding='utf-8') as f:
        json.dump(metadata, f, ensure_ascii=False, indent=2)

    print(f"  元数据: {meta_path}")

    # 输出摘要
    print("\n" + "=" * 60)
    print("提取完成！")
    print("=" * 60)
    print(f"论文: {paper_title}")
    print(f"目录: {paper_dir}")
    print(f"PDF:  {pdf_path}")
    print(f"章节数: {len(parsed['sections'])}")
    print()
    print("章节结构:")
    for i, sec in enumerate(parsed['sections'], 1):
        print(f"  {i}. {sec['heading']}")
        for sub in sec.get('subsections', []):
            print(f"      - {sub['title']}")
    print()
    print(f"章节文件目录: {os.path.join(paper_dir, arxiv_id + '_raw_sections')}/")
    print("下一步: 读取各章节文件 -> 翻译为中文 -> 组装中文文档")


if __name__ == "__main__":
    main()


path: scripts/requirements.txt
content: requests>=2.28.0
beautifulsoup4>=4.11.0


path: scripts/usage_example.md
content: # arXiv Paper Resolver - 使用示例

## 快速使用

### 方式一：命令行直接运行

```bash
# 通过 arXiv ID
python3 arxiv_section_extractor.py 2604.25405

# 通过完整 URL
python3 arxiv_section_extractor.py https://arxiv.org/abs/2604.25405

# 自定义输出目录
python3 arxiv_section_extractor.py 2604.25405 -o ./my_papers
```

### 方式二：通过 Python 导入使用（TODO）

```python
from arxiv_section_extractor import extract_arxiv_id, get_paper_info_from_abs

arxiv_id = extract_arxiv_id("https://arxiv.org/abs/2604.25836")
title, links = get_paper_info_from_abs(arxiv_id)
print(f"论文: {title}")
```

## 输出示例

```text
============================================================
arXiv 论文提取器
论文 ID: 2604.25405
============================================================

[1/5] 获取论文信息...
  标题: Leveraging Previous-Traversal Point Cloud Map Priors for Camera-Based 3D Object Detection and Tracking
  PDF:  https://arxiv.org/pdf/2604.25405
  HTML: https://arxiv.org/html/2604.25405v1
  TeX:  https://arxiv.org/src/2604.25405

[2/5] 创建目录...
  目录: /home/user/papers/leveraging-previous-traversal-point-cloud-map-priors/

[3/5] 下载 PDF...
  PDF 已下载: .../2604.25405.pdf (1824 KB)

[4/5] 获取 HTML 实验版全文...
  HTML 获取成功: https://arxiv.org/html/2604.25405v1 (50219 字符)

[5/5] 解析章节结构...
  共解析出 6 个章节
  章节已保存: 01_Introduction.txt (1240 字符)
  章节已保存: 02_Related_Work.txt (980 字符)
  ...

============================================================
提取完成！
============================================================
论文: Leveraging Previous-Traversal Point Cloud Map Priors ...
目录: /home/user/papers/leveraging-previous-traversal-.../
PDF:  /home/user/papers/leveraging-previous-traversal-.../2604.25405.pdf
章节数: 6

章节结构:
  1. I Introduction
  2. II Related Work
  3. III Technical Approach
      - A. Multi-Model Motion Estimation
      - B. Motion-State-Driven Cascade Association
  4. IV Experimental Evaluation
  5. V Discussion
  6. VI Conclusion
```

## 常见问题

**Q: 为什么 PDF 下载失败？**
A: 检查网络连接，确认 arXiv 服务器可用。PDF 下载失败不影响章节提取。

**Q: 为什么 HTML 解析失败？**
A: arXiv 可能更新了 HTML 格式，需要更新脚本中的解析逻辑。可提交 Issue。

**Q: 如何修改输出目录？**
A: 使用 `-o` 参数或设置 `ARXIV_PAPERS_DIR` 环境变量。

**Q: 如何批量解析多篇论文？**
A: 可以写一个循环脚本调用 extractor，或提交 PR 添加批量支持。


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)