用 tqdm+requests 打造支持进度条的PDF翻译CLI工具

前言

最近在做一个批量翻译任务:把一个 200 多页的英文论文 PDF 翻译成中文,然后做对照阅读。这个过程中,我希望能看到翻译进度——一个 200 页的 PDF,如果只是黑盒等 5 分钟,体验是糟糕的。

requests 适合发请求,tqdm 适合做进度条,把它们组合起来做一个 CLI 工具,既能给真实用户用,也能集成到后续自动化流水线里。

本文给出一个完整的、可直接运行的 Python CLI 脚本。代码我已经用了两周,生产场景够用。

环境准备

  • Python 3.10+
  • 依赖:pip install requests tqdm
pip install requests tqdm

思路概述

整体流程:

  1. requests 上传 PDF 到 PDFTranslator API
  2. 轮询任务状态(类似异步任务模式)
  3. tqdm 显示总体进度
  4. 下载翻译结果到本地

PDFTranslator 提供了创建任务 → 轮询 → 下载的标准异步任务 API,这正好适合进度条场景。

实现步骤

Step 1: 上传 PDF 并创建翻译任务

import requests
from pathlib import Path

API_BASE = "https://api.pdftranslator.org/v1"

def create_translation_task(pdf_path: str, target_lang: str = "zh-CN") -> str:
    """创建翻译任务,返回 task_id
    
    Args:
        pdf_path: 待翻译的 PDF 文件路径
        target_lang: 目标语言代码,如 zh-CN / en / es / fr
    
    Returns:
        task_id: 翻译任务 ID,后续用于轮询结果
    """
    url = f"{API_BASE}/translate/create"
    headers = {"Authorization": "Bearer YOUR_API_KEY"}  # 替换为你的 API Key
    
    with open(pdf_path, "rb") as f:
        files = {"file": (Path(pdf_path).name, f, "application/pdf")}
        data = {"target_lang": target_lang, "preserve_format": "true"}
        response = requests.post(url, headers=headers, files=files, data=data, timeout=60)
        response.raise_for_status()
        return response.json()["task_id"]

Step 2: 轮询任务状态

import time

def wait_for_task(task_id: str, poll_interval: float = 2.0) -> dict:
    """轮询任务状态直到完成
    
    Args:
        task_id: 翻译任务 ID
        poll_interval: 轮询间隔(秒)
    
    Returns:
        任务完成时的完整响应 JSON
    """
    url = f"{API_BASE}/translate/status/{task_id}"
    headers = {"Authorization": "Bearer YOUR_API_KEY"}
    
    while True:
        resp = requests.get(url, headers=headers, timeout=30).json()
        status = resp.get("status")
        
        if status == "completed":
            return resp
        if status == "failed":
            raise RuntimeError(f"翻译失败: {resp.get('error')}")
        
        # pending / processing 状态,继续轮询
        time.sleep(poll_interval)

Step 3: 整合 tqdm 显示进度条

from tqdm import tqdm
import sys

def translate_with_progress(pdf_path: str, output_path: str, target_lang: str = "zh-CN"):
    """带进度条的翻译流程
    
    进度条 0-30%:上传 + 创建任务
    进度条 30-90%:等待处理
    进度条 90-100%:下载结果
    """
    bar = tqdm(total=100, desc="翻译PDF", unit="%", file=sys.stdout)
    
    try:
        # 1. 创建任务 (0% -> 30%)
        bar.update(5)
        task_id = create_translation_task(pdf_path, target_lang)
        bar.update(25)
        
        # 2. 轮询直到完成 (30% -> 90%)
        # 假设根据页数估算"处理进度",这里是简化版
        page_count = _estimate_pages(pdf_path)
        last_progress = 30
        
        while True:
            result = wait_for_task(task_id, poll_interval=2.0)
            current_progress = result.get("progress", 50)
            # 将处理进度映射到 30-90 区间
            mapped = 30 + int(current_progress * 0.6)
            if mapped > last_progress:
                bar.update(mapped - last_progress)
                last_progress = mapped
            
            if result["status"] == "completed":
                break
        
        # 3. 下载结果 (90% -> 100%)
        download_url = result["download_url"]
        with requests.get(download_url, stream=True, timeout=120) as r:
            r.raise_for_status()
            with open(output_path, "wb") as f:
                for chunk in r.iter_content(chunk_size=8192):
                    f.write(chunk)
                    bar.update(0.5)  # 视觉补偿
        
        bar.update(100 - last_progress)  # 补满到 100%
        bar.set_description(f"✅ 完成: {output_path}")
    
    finally:
        bar.close()


def _estimate_pages(pdf_path: str) -> int:
    """快速估算PDF页数(通过文件大小粗估)"""
    size_mb = Path(pdf_path).stat().st_size / (1024 * 1024)
    return int(size_mb * 50)  # 粗估:每页约 20KB

Step 4: CLI 入口(用 argparse)

import argparse

def main():
    parser = argparse.ArgumentParser(description="带进度条的 PDF 翻译 CLI")
    parser.add_argument("pdf", help="待翻译的 PDF 文件路径")
    parser.add_argument("-o", "--output", help="输出路径,默认与输入同名 _translated.pdf")
    parser.add_argument("-l", "--lang", default="zh-CN", 
                        help="目标语言代码,默认 zh-CN。可选:en, es, fr, de, ja, ko")
    args = parser.parse_args()
    
    output = args.output or str(Path(args.pdf).with_name(
        Path(args.pdf).stem + "_translated.pdf"
    ))
    
    translate_with_progress(args.pdf, output, args.lang)
    print(f"\n✅ 翻译完成,已保存到: {output}")


if __name__ == "__main__":
    main()

完整脚本

把上面所有步骤合并到一个文件 translate_pdf.py:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
translate_pdf.py - 带进度条的 PDF 翻译 CLI 工具

用法:
    python translate_pdf.py report.pdf -l en -o report_en.pdf
"""

import argparse
import sys
import time
from pathlib import Path

import requests
from tqdm import tqdm

API_BASE = "https://api.pdftranslator.org/v1"
API_KEY = "YOUR_API_KEY"  # 替换为你的 API Key


def create_translation_task(pdf_path: str, target_lang: str) -> str:
    """创建翻译任务"""
    url = f"{API_BASE}/translate/create"
    headers = {"Authorization": f"Bearer {API_KEY}"}
    with open(pdf_path, "rb") as f:
        files = {"file": (Path(pdf_path).name, f, "application/pdf")}
        data = {"target_lang": target_lang, "preserve_format": "true"}
        resp = requests.post(url, headers=headers, files=files, data=data, timeout=60)
        resp.raise_for_status()
        return resp.json()["task_id"]


def wait_for_task(task_id: str, poll_interval: float = 2.0) -> dict:
    """轮询任务状态"""
    url = f"{API_BASE}/translate/status/{task_id}"
    headers = {"Authorization": f"Bearer {API_KEY}"}
    while True:
        resp = requests.get(url, headers=headers, timeout=30).json()
        status = resp.get("status")
        if status == "completed":
            return resp
        if status == "failed":
            raise RuntimeError(f"翻译失败: {resp.get('error')}")
        time.sleep(poll_interval)


def download_result(download_url: str, output_path: str, bar: tqdm):
    """流式下载翻译结果"""
    with requests.get(download_url, stream=True, timeout=120) as r:
        r.raise_for_status()
        with open(output_path, "wb") as f:
            for chunk in r.iter_content(chunk_size=8192):
                if chunk:
                    f.write(chunk)
                    bar.update(0.3)


def translate_with_progress(pdf_path: str, output_path: str, target_lang: str):
    """主流程"""
    bar = tqdm(total=100, desc="翻译中", unit="%")
    task_id = create_translation_task(pdf_path, target_lang)
    bar.update(30)  # 上传完成
    
    try:
        while True:
            result = wait_for_task(task_id, poll_interval=2.0)
            progress = result.get("progress", 50)
            current = 30 + int(progress * 0.6)
            bar.n = min(90, current)
            bar.refresh()
            if result["status"] == "completed":
                break
        
        download_result(result["download_url"], output_path, bar)
        bar.n = 100
        bar.refresh()
    finally:
        bar.close()


def main():
    parser = argparse.ArgumentParser(description="带进度条的 PDF 翻译 CLI")
    parser.add_argument("pdf", help="待翻译 PDF")
    parser.add_argument("-o", "--output")
    parser.add_argument("-l", "--lang", default="zh-CN")
    args = parser.parse_args()
    
    output = args.output or str(Path(args.pdf).with_name(
        Path(args.pdf).stem + "_translated.pdf"
    ))
    
    translate_with_progress(args.pdf, output, args.lang)
    print(f"\n✅ 已保存: {output}")


if __name__ == "__main__":
    main()

运行效果

$ python translate_pdf.py research.pdf -l zh-CN
翻译中: 100%|████████████████████████| 100/100 [04:47<00:00,  2.87s/%]
✅ 已保存: research_translated.pdf

进度条下方会显示当前进度,翻译总耗时 5 分钟以内。

工程化建议

如果你打算把这个工具放到生产环境,有几点建议:

  1. 重试机制:网络中断时增加 tenacity 重试
  2. 日志:用 logging 替代 print,方便排查
  3. 并发批量:concurrent.futures.ThreadPoolExecutor 一次处理多个 PDF
  4. 配置文件:API Key 用环境变量,不写死在代码里
  5. 错误处理:对 PDF 损坏、文件过大等情况做用户友好提示

总结

进度条看似是"小事",但对长时间任务的用户体验至关重要。tqdm 几行代码就解决问题,值得纳入工具脚本的标配。

完整脚本可以扩展为:

  • 批量翻译整个目录
  • 集成到 Celery 异步任务队列
  • 提供 Web UI(Tornado / FastAPI + 后台任务)

下一步建议:如果你也有批量翻译需求,可以扩展为 Web 界面,让非技术同事也能用。


标签:Python、PDF翻译、tqdm、CLI工具、AI翻译

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值