本文へ移動
cccskills
無料GitHub で公開

download-fulltext-pdf

当用户明确要求"下载文献全文"或"获取论文PDF"时使用。通过 DOI 号下载学术论文全文 PDF,支持 arXiv、Sci-Hub、Unpaywall、期刊官网等多源策略。

⚠️ 不适用:用户只是想解析或处理已有的 PDF 文件(应使用 pdf skill)、只是想搜索论文信息而无需下载全文、没有提供 DOI/标题/BibTeX 任何标识符。

インストール方法を見る

含まれるファイル(7)

  • SKILL.md15.3 KB
  • CHANGELOG.md1.5 KB
  • config.yaml1.9 KB
  • README.md3.5 KB
  • scripts/download_pdf.py16.6 KB
  • scripts/validate_input.py2.2 KB
  • scripts/verify_pdf.py2.3 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

下载文献全文 PDF

目标

当用户明确要求"下载文献全文"或"获取论文PDF"时使用。通过 DOI 号下载学术论文全文 PDF,支持 arXiv、Sci-Hub、Unpaywall、期刊官网等多源策略。⚠️ 不适用:用户只是想解析或处理已有的 PDF 文件(应使用 pdf skill)、只是想搜索论文信息而无需下载全文、没有提供 DOI/标题/BibTeX 任何标识符。

流程

输入

输入至少包含 DOI、标题或 BibTeX 之一,并指定输出目录/文件路径;可选输入包括来源开关、Unpaywall 邮箱、验证阈值和覆盖策略。输出路径必须存在或可创建且可写,触发边界以本 Skill 的 ## 目标 为准。

执行步骤

核心工作流

1. 输入验证与规范化

必需参数:

  • doi: DOI 号(如 10.1038/nature09492)
  • output_path: 输出目录或完整 PDF 文件路径

可选参数:

  • title: 论文标题(作为 DOI 的备选)
  • bibtex: BibTeX 条目(包含 DOI 或标题信息)

验证逻辑:

# 由 scripts/validate_input.py 处理
- DOI 格式规范化(移除多余空格、补全前缀)
- 输出路径检查(目录存在性、写入权限)
- 至少提供一种标识符(DOI/title/bibtex)
2. 多源下载策略

优先级顺序(按成功率和速度排序):

优先级数据源优势局限触发条件
1arXiv稳定可靠仅限预印本论文检测到 arXiv ID(如 10.48550/arXiv.*)
2Sci-Hub覆盖较广、速度快可能有 CAPTCHA非 arXiv 或 arXiv 失败
3Unpaywall合法开放获取(OA)取决于论文是否 OASci-Hub 失败时兜底
4期刊官网合法渠道常需订阅/登录最后兜底

策略执行:

# 由 scripts/download_pdf.py 实现
1. 如 DOI 可解析出 arXiv ID → 优先 arXiv
2. 尝试 Sci-Hub(通过 scihub 库)
3. 尝试 Unpaywall(合法 OA 获取)
4. 尝试 DOI 落地页并猜测常见 PDF 路径(期刊官网兜底)
3. 下载后验证

必需检查(由 scripts/verify_pdf.py 处理):

  • 文件大小 > 0 且非 HTML 页面
  • PDF 文件头正确(%PDF-)
  • 文件可被 PyPDF2 解析

失败处理:

  • 验证失败 → 记录错误 → 尝试下一个数据源
  • 所有源失败 → 返回详细错误报告
4. 输出格式

成功时:

✅ 成功下载论文全文

**来源**: Sci-Hub
**DOI**: 10.1038/nature09492
**文件**: /path/to/paper.pdf
**大小**: 2.3 MB

失败时:

❌ 无法下载论文全文

**尝试过的源**:
- Sci-Hub: CAPTCHA 验证失败
- arXiv: 非 arXiv 论文
- Unpaywall: 无开放获取版本
- 期刊官网: 未找到直接 PDF 链接

**建议**:
1. 手动访问 Sci-Hub 并完成 CAPTCHA 验证
2. 检查 DOI 是否正确
3. 尝试通过期刊官网获取

AI 动态判断

AI 需要动态处理的场景:

场景处理方式
CAPTCHA 检测识别错误消息中的 "captcha" 关键词 → 切换数据源
网络超时增加重试次数(最多 3 次)或切换源
404/未找到直接切换下一个源,不重试
文件损坏删除已下载文件,尝试下一个源
arXiv 检测DOI 中包含 arXiv ID → 优先使用 arXiv 源

硬编码操作(scripts/)

scripts/validate_input.py

输入验证与规范化脚本:

import sys
import re
from pathlib import Path

def normalize_doi(doi: str) -> str:
    """规范化 DOI 格式"""
    doi = doi.strip()
    if not doi.startswith("10."):
        doi = f"10.{doi}"  # 尝试补全
    return doi

def validate_output_path(path: str) -> Path:
    """验证输出路径"""
    path = Path(path).expanduser().resolve()
    if path.exists() and path.is_dir():
        # 目录:自动生成文件名
        return path / "paper.pdf"
    if path.parent.exists():
        # 完整路径:确保父目录可写
        return path
    raise ValueError(f"输出路径无效: {path}")

def main():
    if len(sys.argv) < 3:
        print("用法: python validate_input.py <doi> <output_path>", file=sys.stderr)
        sys.exit(1)

    doi = normalize_doi(sys.argv[1])
    output_path = validate_output_path(sys.argv[2])

    print(f"DOI:{doi}")
    print(f"OUTPUT:{output_path}")

if __name__ == "__main__":
    main()
scripts/download_pdf.py

核心下载逻辑(多源策略):

import sys
import requests
from pathlib import Path
from typing import Optional, Tuple

# 尝试导入 scihub 库(可选依赖)
try:
    from scihub import SciHub
    HAS_SCIHUB = True
except ImportError:
    HAS_SCIHUB = False

class PDFDownloader:
    """多源 PDF 下载器"""

    def __init__(self, doi: str, output_path: Path):
        self.doi = doi
        self.output_path = output_path
        self.sources_tried = []

    def download_from_scihub(self) -> Tuple[bool, str]:
        """从 Sci-Hub 下载"""
        if not HAS_SCIHUB:
            return False, "scihub 库未安装"

        try:
            sh = SciHub()
            result = sh.download(self.doi, path=str(self.output_path))
            if result and self.output_path.exists():
                return True, "Sci-Hub"
            return False, "下载失败"
        except Exception as e:
            error_msg = str(e).lower()
            if "captcha" in error_msg:
                return False, "CAPTCHA 验证失败"
            return False, str(e)

    def download_from_arxiv(self) -> Tuple[bool, str]:
        """从 arXiv 下载(检测 arXiv ID)"""
        # 简化实现:arXiv 直接下载 URL
        arxiv_id = self._extract_arxiv_id()
        if not arxiv_id:
            return False, "非 arXiv 论文"

        url = f"https://arxiv.org/pdf/{arxiv_id}.pdf"
        try:
            response = requests.get(url, timeout=30)
            if response.status_code == 200:
                self.output_path.write_bytes(response.content)
                return True, "arXiv"
            return False, f"HTTP {response.status_code}"
        except Exception as e:
            return False, str(e)

    def download_from_unpaywall(self) -> Tuple[bool, str]:
        """从 Unpaywall 下载(合法 OA)"""
        # Unpaywall API 端点
        # email 参数建议从 config.yaml 读取;可配置 emails 列表做负载均衡
        url = f"https://api.unpaywall.org/v2/{self.doi}"
        try:
            response = requests.get(url, timeout=30)
            if response.status_code == 200:
                data = response.json()
                if data.get("is_oa"):
                    pdf_url = data.get("best_oa_location", {}).get("url_for_pdf")
                    if pdf_url:
                        pdf_resp = requests.get(pdf_url, timeout=30)
                        if pdf_resp.status_code == 200:
                            self.output_path.write_bytes(pdf_resp.content)
                            return True, "Unpaywall"
            return False, "无开放获取版本"
        except Exception as e:
            return False, str(e)

    def _extract_arxiv_id(self) -> Optional[str]:
        """从 DOI 或上下文中提取 arXiv ID"""
        # 简化实现:检测 DOI 中是否包含 arXiv 信息
        # 实际应解析 BibTeX 或标题
        return None

    def download(self) -> Tuple[bool, str, str]:
        """执行多源下载策略"""
        # 策略 1: Sci-Hub
        self.sources_tried.append("Sci-Hub")
        success, source = self.download_from_scihub()
        if success:
            return True, source, "成功"

        # 策略 2: arXiv(如果是 arXiv 论文)
        self.sources_tried.append("arXiv")
        success, source = self.download_from_arxiv()
        if success:
            return True, source, "成功"

        # 策略 3: Unpaywall
        self.sources_tried.append("Unpaywall")
        success, source = self.download_from_unpaywall()
        if success:
            return True, source, "成功"

        # 所有源失败
        return False, "", f"尝试过的源: {', '.join(self.sources_tried)}"

def main():
    if len(sys.argv) != 3:
        print("用法: python download_pdf.py <doi> <output_path>", file=sys.stderr)
        sys.exit(1)

    doi = sys.argv[1]
    output_path = Path(sys.argv[2]).expanduser().resolve()

    downloader = PDFDownloader(doi, output_path)
    success, source, message = downloader.download()

    if success:
        print(f"SUCCESS:{source}")
        print(f"FILE:{output_path}")
        print(f"SIZE:{output_path.stat().st_size}")
    else:
        print(f"FAILURE:{message}", file=sys.stderr)
        sys.exit(1)

if __name__ == "__main__":
    main()
scripts/verify_pdf.py

PDF 验证脚本:

import sys
from pathlib import Path

def verify_pdf(path: Path) -> Tuple[bool, str]:
    """验证 PDF 文件完整性"""
    if not path.exists():
        return False, "文件不存在"

    if path.stat().st_size == 0:
        return False, "文件大小为 0"

    # 检查 PDF 文件头
    with open(path, "rb") as f:
        header = f.read(5)
        if header != b"%PDF-":
            return False, "无效的 PDF 文件头"

    # 尝试用 PyPDF2 解析
    try:
        import PyPDF2
        with open(path, "rb") as f:
            PyPDF2.PdfReader(f)
        return True, "有效 PDF"
    except ImportError:
        # PyPDF2 未安装,跳过深度验证
        return True, "未安装 PyPDF2,跳过深度验证"
    except Exception as e:
        return False, f"PDF 解析失败: {str(e)}"

def main():
    if len(sys.argv) != 2:
        print("用法: python verify_pdf.py <pdf_path>", file=sys.stderr)
        sys.exit(1)

    path = Path(sys.argv[1]).expanduser().resolve()
    is_valid, message = verify_pdf(path)

    if is_valid:
        print(f"VALID:{message}")
        sys.exit(0)
    else:
        print(f"INVALID:{message}", file=sys.stderr)
        sys.exit(1)

if __name__ == "__main__":
    main()

配置参数(config.yaml)

# 下载策略配置
download:
  # Sci-Hub 配置
  scihub:
    enabled: true
    timeout: 30  # 秒
    retries: 3

  # arXiv 配置
  arxiv:
    enabled: true
    timeout: 30

  # Unpaywall 配置
  unpaywall:
    enabled: true
    emails:
      - "hwb2012@qq.com"
      - "huangwb886@gmail.com"
    email_strategy: "round_robin"  # round_robin | hash_doi | random
    state_file: ".bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/download-fulltext-pdf/state/unpaywall_email_state.json"
    email: ""  # 兼容旧字段(可选)

# 验证配置
verification:
  check_pdf_header: true
  require_pypdf2: false  # 是否强制要求 PyPDF2
  max_file_size: 100_000_000  # 100 MB

# 输出配置
output:
  default_filename: "paper.pdf"
  overwrite: false  # 是否覆盖已存在文件

依赖说明

必需依赖:

  • requests(网络请求)

可选依赖(自动安装):

  • scihub(Sci-Hub 支持,首次使用时自动安装)
  • PyPDF2(PDF 验证,推荐安装)
自动安装机制

当 AI 执行下载任务时,scripts/download_pdf.py 会:

  1. 检测缺失的可选依赖
  2. 提醒用户将要安装的包(如 📦 检测到缺失依赖: scihub)
  3. 自动安装(使用 pip install)
  4. 安装失败时自动降级到其他数据源

用户无需手动安装,skill 会"开箱即用"。

手动安装(可选)

如果自动安装失败,用户可以手动安装:

pip install requests scihub PyPDF2

使用示例

示例 1:使用 DOI 下载

/skill download-fulltext-pdf "10.1038/nature09492" ./downloads/

示例 2:使用标题下载

/skill download-fulltext-pdf --title "Deep Learning" ./downloads/

示例 3:批量下载(从 BibTeX 文件)

/skill download-fulltext-pdf --bibtex references.bib ./downloads/

输出

成功时交付经 %PDF- 文件头和可选 PyPDF2 解析验证的 PDF,并报告来源、规范化 DOI、文件路径和大小;失败时列出尝试过的来源、具体原因和可操作建议。状态文件、日志和调试材料留在任务工作区。

输出管理

BenszAPI 任务工作区

校验

质量检查清单

  • DOI 格式正确(以 10. 开头)
  • 输出路径可写
  • 至少一种数据源可用
  • 下载后 PDF 验证通过
  • 错误消息清晰可操作

失败与恢复

错误处理

错误类型表现处理方式
CAPTCHASci-Hub 返回验证码页面切换到 Unpaywall 或期刊官网
未找到404 或 "not found"切换下一个数据源
超时请求超时重试(最多 3 次)
文件损坏PDF 头无效删除并重新下载

注意事项

  1. 版权合规:仅用于学术研究,遵守当地版权法律
  2. Sci-Hub 可用性:Sci-Hub 域名可能变化,需要定期更新
  3. 网络环境:某些地区可能需要代理才能访问 Sci-Hub
  4. CAPTCHA 处理:遇到 CAPTCHA 时,建议手动完成验证

约束

<!-- BEGIN COMMON CONSTRAINTS --> <!-- Source-Hash: sha256:15120201e9e0c7569517261d57ecefb63ac279c26ed13876f8e95b6dc35854d3 --> <!-- Template-ID: skill-common-constraints; Template-Version: 1; Sync-Policy: exact-block -->

公共硬约束

本块由 docs/templates/skill-common-constraints.md 统一维护;每个 SKILL.md 的 ## 约束 必须逐字同步本块,不得在副本中改写公共规则。

  • 任务需要落盘时,使用唯一的 ./.bensz-api/task-{yyyymmdd-hhmm}-{简短描述}/ 根目录;共享材料放入 shared/,Skill 专属材料放入该 Skill 的 input/、output/、log/。
  • 正式交付物、源代码和正式计划按项目约定保存,不写入任务工作区;未经授权不覆盖、删除、迁移或远程写入。
  • 项目维护变更检查 BAC 可用性并记录需求、AI 产出、工具结果、文件改动和验证摘要;BAC 只做过程审计,不替代署名、责任或合规判断。
  • 不记录 API Key、访问令牌、密码、Cookie、环境/凭据文件、私有 Prompt、身份信息、本地用户名、主机名或不必要的大体积原始数据。
  • 文件路径必须规范化并限制在授权项目范围内;外部 URL、子进程和网络访问遵循最小权限,防止路径遍历、SSRF 和命令注入。
  • Skill 版本唯一记录在自身 config.yaml:skill_info.version;公开 API、协议、目录或配置变更同步文档与 CHANGELOG.md。
  • bensz-collect-bugs 是一个 Agent Skill;仅将 Bensz Agent Skill 或 Bensz 基础设施本身的设计缺陷交给它。先脱敏写入 ~/.bensz-skills/bugs/,当前任务不中断,只有用户明确要求才公开上报,禁止直接修改用户已安装的 Skill 源码。
<!-- End of canonical common constraints. --> <!-- END COMMON CONSTRAINTS -->

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

当用户明确要求转换、修改或批量转换图片格式时使用。支持常见本地或网络图片输入与常见目标格式输出。⚠️ 不适用:仅调整尺寸/裁剪、查看图片信息,或没有明确格式转换意图。

日本語の概要は準備中です。原文の説明を表示しています。

huangwb8/skills502026年9月25日 更新

根据用户描述生成高质量绘图 prompt,并按通用、roadmap、schematic 模式通过 BenszAPI 直接完成 gpt-image-2.5-flare、gpt-image-2.5-sunburst、gpt-image-2 或 Nano Banana/Gemini 出图、编辑和多轮迭代;这是自包含的图片生成工作流,选中后不得调用或依赖 imagegen,除非用户明确要求同时使用 imagegen。

日本語の概要は準備中です。原文の説明を表示しています。

huangwb8/skills502026年9月25日 更新

当用户明确要求测试代码、代码审查或代码自检时使用。系统化发现并验证代码问题。⚠️ 不适用:用户只是想实现或优化功能、询问代码问题,或没有明确测试意图。

日本語の概要は準備中です。原文の説明を表示しています。

huangwb8/skills502026年9月25日 更新

当用户明确要求“测试项目”、 “运行 auto-test-project”或“进行项目级测试”时使用。对完整项目执行多轮 A 轮批判性测试与 B 轮质量检查,发现、记录、修复并验证问题。⚠️ 不适用:用户只是想优化功能、询问项目问题,或没有明确测试意图。

日本語の概要は準備中です。原文の説明を表示しています。

huangwb8/skills502026年9月25日 更新

当用户明确要求测试 Skill、运行 auto-test 或对 Skill 进行批判性测试时使用。系统化发现、记录并验证 Skill 问题。⚠️ 不适用:用户只是想开发或优化 Skill,或没有明确测试意图。

日本語の概要は準備中です。原文の説明を表示しています。

huangwb8/skills502026年9月25日 更新

当用户明确要求使用 awesome-code、进行多代理协作或并行协调开发时使用。根据任务选择合适的协作方式并协调专业 Agent。⚠️ 不适用:用户只需单一角色完成简单修改或咨询,或未表达多代理协作意图。

日本語の概要は準備中です。原文の説明を表示しています。

huangwb8/skills502026年9月25日 更新

huangwb8 のスキルをすべて見る

このスキルの問題を報告する