本文目录导读:

我来为您提供一个批量转换文档为电子书的Python脚本,支持常见的文档格式转换。
基础版:支持TXT、PDF、DOCX转EPUB
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
批量文档转电子书脚本
支持格式: TXT, PDF, DOCX → EPUB, MOBI
"""
import os
import sys
import argparse
from pathlib import Path
from typing import List, Optional
import logging
from concurrent.futures import ThreadPoolExecutor, as_completed
# 第三方库导入
try:
from ebooklib import epub
from bs4 import BeautifulSoup
import fitz # PyMuPDF
from docx import Document
from tqdm import tqdm
except ImportError as e:
print(f"缺少必要库: {e}")
print("请安装: pip install ebooklib beautifulsoup4 pymupdf python-docx tqdm")
sys.exit(1)
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class DocumentConverter:
"""文档转换器类"""
SUPPORTED_INPUT = {'.txt', '.pdf', '.docx'}
SUPPORTED_OUTPUT = {'.epub', '.mobi'}
def __init__(self, input_dir: str, output_dir: str, output_format: str = 'epub'):
"""
初始化转换器
Args:
input_dir: 输入目录
output_dir: 输出目录
output_format: 输出格式 (epub/mobi)
"""
self.input_dir = Path(input_dir)
self.output_dir = Path(output_dir)
self.output_format = output_format.lower()
if self.output_format not in self.SUPPORTED_OUTPUT:
raise ValueError(f"不支持的输出格式: {output_format}")
# 创建输出目录
self.output_dir.mkdir(parents=True, exist_ok=True)
def convert_document(self, file_path: Path) -> Optional[Path]:
"""
转换单个文档
Args:
file_path: 输入文件路径
Returns:
转换后的文件路径,失败返回None
"""
try:
# 根据文件类型选择转换方法
suffix = file_path.suffix.lower()
if suffix == '.txt':
content = self._read_txt(file_path)
elif suffix == '.pdf':
content = self._read_pdf(file_path)
elif suffix == '.docx':
content = self._read_docx(file_path)
else:
logger.warning(f"不支持的文件格式: {file_path}")
return None
# 生成电子书
output_file = self.output_dir / f"{file_path.stem}.{self.output_format}"
if self.output_format == 'epub':
self._create_epub(file_path.stem, content, output_file)
else: # mobi格式可能需要额外转换,这里简单处理
self._create_epub(file_path.stem, content, output_file.with_suffix('.epub'))
# 注意: 真正的MOBI转换需要Calibre的ebook-convert工具
logger.info(f"转换成功: {file_path.name} → {output_file.name}")
return output_file
except Exception as e:
logger.error(f"转换失败 {file_path.name}: {str(e)}")
return None
def _read_txt(self, file_path: Path) -> str:
"""读取TXT文件"""
try:
# 尝试多种编码
encodings = ['utf-8', 'gbk', 'gb2312', 'big5', 'latin-1']
for encoding in encodings:
try:
with open(file_path, 'r', encoding=encoding) as f:
return f.read()
except UnicodeDecodeError:
continue
raise ValueError(f"无法识别文件编码: {file_path}")
except Exception as e:
raise Exception(f"读取TXT失败: {str(e)}")
def _read_pdf(self, file_path: Path) -> str:
"""读取PDF文件"""
try:
doc = fitz.open(str(file_path))
text = ""
for page in doc:
text += page.get_text()
doc.close()
return text
except Exception as e:
raise Exception(f"读取PDF失败: {str(e)}")
def _read_docx(self, file_path: Path) -> str:
"""读取DOCX文件"""
try:
doc = Document(file_path)
paragraphs = [p.text for p in doc.paragraphs]
return '\n\n'.join(paragraphs)
except Exception as e:
raise Exception(f"读取DOCX失败: {str(e)}")
def _create_epub(self, title: str, content: str, output_file: Path):
"""创建EPUB文件"""
try:
# 创建EPUB对象
book = epub.EpubBook()
book.set_identifier(f'id_{hash(title)}')
book.set_title(title)
book.set_language('zh')
# 添加作者元数据
book.add_author('Converter')
# 创建章节
# 简单分章:每2000字分一章
chapter_size = 2000
chapters = [content[i:i+chapter_size]
for i in range(0, len(content), chapter_size)]
if not chapters:
chapters = [''] # 空内容时添加空章节
for i, chapter_content in enumerate(chapters):
chapter = epub.EpubHtml(
title=f'第{i+1}章',
file_name=f'chapter_{i+1}.xhtml',
lang='zh'
)
# 将纯文本转换为HTML
html_content = f'<h2>第{i+1}章</h2><p>{chapter_content}</p>'
chapter.content = html_content
book.add_item(chapter)
book.toc.append(chapter)
# 添加样式
style = '''
body { font-family: "Noto Serif CJK SC", serif; }
h2 { text-align: center; margin-bottom: 1em; }
p { text-indent: 2em; line-height: 1.8; }
'''
nav_css = epub.EpubItem(
uid='style',
file_name='style.css',
media_type='text/css',
content=style
)
book.add_item(nav_css)
# 设置spine
book.spine = ['nav'] + book.toc
# 添加默认导航
book.add_item(epub.EpubNcx())
book.add_item(epub.EpubNav())
# 保存EPUB文件
epub.write_epub(str(output_file), book)
except Exception as e:
raise Exception(f"创建EPUB失败: {str(e)}")
def batch_convert(self, max_workers: int = 4) -> List[Path]:
"""
批量转换所有支持的文件
Args:
max_workers: 最大线程数
Returns:
成功转换的文件列表
"""
# 收集所有支持的文件
files = []
for ext in self.SUPPORTED_INPUT:
files.extend(self.input_dir.glob(f"*{ext}"))
files.extend(self.input_dir.glob(f"*{ext.upper()}"))
if not files:
logger.warning(f"在 {self.input_dir} 中未找到支持的文档")
return []
logger.info(f"找到 {len(files)} 个文档,开始转换...")
# 多线程转换
converted_files = []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
# 使用tqdm显示进度
futures = {executor.submit(self.convert_document, f): f for f in files}
with tqdm(total=len(files), desc="转换进度", unit="个") as pbar:
for future in as_completed(futures):
result = future.result()
if result:
converted_files.append(result)
pbar.update(1)
pbar.set_postfix(success=len(converted_files))
logger.info(f"转换完成!成功: {len(converted_files)}/{len(files)} 个文件")
return converted_files
def main():
"""主函数"""
parser = argparse.ArgumentParser(
description='批量转换文档为电子书',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
示例:
python convert_ebook.py -i ./documents -o ./ebooks
python convert_ebook.py -i ./docs -o ./epubs --format epub --threads 8
python convert_ebook.py -i ./docs -o ./mobis --format mobi
'''
)
parser.add_argument('-i', '--input', required=True, help='输入文档目录')
parser.add_argument('-o', '--output', default='./output', help='输出电子书目录 (默认: ./output)')
parser.add_argument('-f', '--format', choices=['epub', 'mobi'], default='epub',
help='输出格式 (默认: epub)')
parser.add_argument('-t', '--threads', type=int, default=4,
help='并行线程数 (默认: 4)')
parser.add_argument('--log-level', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'],
default='INFO', help='日志级别')
args = parser.parse_args()
# 设置日志级别
logger.setLevel(getattr(logging, args.log_level))
try:
# 检查输入目录
input_dir = Path(args.input)
if not input_dir.exists():
raise ValueError(f"输入目录不存在: {args.input}")
if not input_dir.is_dir():
raise ValueError(f"输入路径不是目录: {args.input}")
# 创建转换器并执行转换
converter = DocumentConverter(
input_dir=input_dir,
output_dir=Path(args.output),
output_format=args.format
)
converted_files = converter.batch_convert(max_workers=args.threads)
# 输出总结
print("\n" + "="*50)
print(f"转换完成!成功处理 {len(converted_files)} 个文件")
for i, file in enumerate(converted_files, 1):
print(f"{i}. {file}")
print("="*50)
except Exception as e:
logger.error(f"转换过程出错: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()
高级版:加图形界面(可选)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
带GUI的批量转换器(需要安装 tkinter)
"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import threading
from pathlib import Path
import sys
import os
# 导入转换器
from convert_ebook import DocumentConverter # 假设上面的代码保存为 convert_ebook.py
class ConverterGUI:
"""图形界面转换器"""
def __init__(self):
self.root = tk.Tk()
self.root.title("批量文档转电子书工具")
self.root.geometry("600x400")
# 变量
self.input_dir = tk.StringVar()
self.output_dir = tk.StringVar(value="./output")
self.output_format = tk.StringVar(value="epub")
self.threads = tk.IntVar(value=4)
self.progress_var = tk.DoubleVar(value=0)
self.converter = None
self.setup_ui()
def setup_ui(self):
"""设置UI"""
# 输入目录
ttk.Label(self.root, text="输入目录:").grid(row=0, column=0, padx=5, pady=5, sticky='w')
ttk.Entry(self.root, textvariable=self.input_dir, width=40).grid(row=0, column=1, padx=5, pady=5)
ttk.Button(self.root, text="浏览...", command=self.select_input).grid(row=0, column=2, padx=5)
# 输出目录
ttk.Label(self.root, text="输出目录:").grid(row=1, column=0, padx=5, pady=5, sticky='w')
ttk.Entry(self.root, textvariable=self.output_dir, width=40).grid(row=1, column=1, padx=5, pady=5)
ttk.Button(self.root, text="浏览...", command=self.select_output).grid(row=1, column=2, padx=5)
# 输出格式
ttk.Label(self.root, text="输出格式:").grid(row=2, column=0, padx=5, pady=5, sticky='w')
format_frame = ttk.Frame(self.root)
format_frame.grid(row=2, column=1, padx=5, pady=5, sticky='w')
ttk.Radiobutton(format_frame, text="EPUB", variable=self.output_format, value="epub").pack(side='left')
ttk.Radiobutton(format_frame, text="MOBI", variable=self.output_format, value="mobi").pack(side='left')
# 线程数
ttk.Label(self.root, text="并行线程:").grid(row=3, column=0, padx=5, pady=5, sticky='w')
ttk.Spinbox(self.root, from_=1, to=16, textvariable=self.threads, width=5).grid(row=3, column=1, padx=5, pady=5, sticky='w')
# 进度条
self.progress = ttk.Progressbar(self.root, variable=self.progress_var,
maximum=100, length=400)
self.progress.grid(row=4, column=0, columnspan=3, padx=20, pady=20)
# 日志文本框
self.log_text = tk.Text(self.root, height=8, width=70)
self.log_text.grid(row=5, column=0, columnspan=3, padx=20, pady=10)
# 滚动条
scrollbar = ttk.Scrollbar(self.root, command=self.log_text.yview)
scrollbar.grid(row=5, column=3, sticky='ns')
self.log_text.config(yscrollcommand=scrollbar.set)
# 按钮
button_frame = ttk.Frame(self.root)
button_frame.grid(row=6, column=0, columnspan=3, pady=10)
ttk.Button(button_frame, text="开始转换", command=self.start_convert).pack(side='left', padx=10)
ttk.Button(button_frame, text="清空日志", command=self.clear_log).pack(side='left', padx=10)
ttk.Button(button_frame, text="退出", command=self.root.quit).pack(side='left', padx=10)
def select_input(self):
"""选择输入目录"""
directory = filedialog.askdirectory(title="选择文档目录")
if directory:
self.input_dir.set(directory)
def select_output(self):
"""选择输出目录"""
directory = filedialog.askdirectory(title="选择输出目录")
if directory:
self.output_dir.set(directory)
def start_convert(self):
"""开始转换"""
if not self.input_dir.get():
messagebox.showerror("错误", "请选择输入目录")
return
# 禁用转换按钮
self.log("开始转换...")
# 在新线程中执行
thread = threading.Thread(target=self.do_convert)
thread.daemon = True
thread.start()
def do_convert(self):
"""执行转换"""
try:
converter = DocumentConverter(
input_dir=self.input_dir.get(),
output_dir=self.output_dir.get(),
output_format=self.output_format.get()
)
# 覆盖日志方法
def log_callback(msg):
self.root.after(0, self.log, msg)
# 转换文件
files = converter.batch_convert(max_workers=self.threads.get())
self.root.after(0, lambda: self.log(f"转换完成!共处理 {len(files)} 个文件"))
self.root.after(0, lambda: messagebox.showinfo("完成", "转换完成!"))
except Exception as e:
self.root.after(0, lambda: messagebox.showerror("错误", str(e)))
self.root.after(0, lambda: self.log(f"转换失败: {str(e)}"))
def log(self, message):
"""输出日志"""
self.log_text.insert(tk.END, message + "\n")
self.log_text.see(tk.END)
# 更新进度(简单模拟)
self.progress_var.set(min(self.progress_var.get() + 10, 100))
def clear_log(self):
"""清空日志"""
self.log_text.delete(1.0, tk.END)
self.progress_var.set(0)
def run(self):
"""运行主循环"""
self.root.mainloop()
if __name__ == "__main__":
app = ConverterGUI()
app.run()
使用说明
安装依赖
pip install ebooklib beautifulsoup4 pymupdf python-docx tqdm # 如果下载慢,可以使用国内镜像 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple ebooklib beautifulsoup4 pymupdf python-docx tqdm
命令行使用
# 基本用法 python convert_ebook.py -i ./documents -o ./ebooks # 指定输出格式 python convert_ebook.py -i ./docs -o ./epubs --format epub # 增加并行线程 python convert_ebook.py -i ./docs -o ./ebooks --threads 8 # 查看帮助 python convert_ebook.py --help
目录结构示例
documents/ ├── 小说1.txt ├── 说明书.pdf ├── 报告.docx └── 文章.txt # 转换后 output/ ├── 小说1.epub ├── 说明书.epub ├── 报告.epub └── 文章.epub
这个脚本提供了以下功能:
- 多格式支持:TXT、PDF、DOCX
- 多线程处理:并行转换提高效率
- 进度显示:转换过程清晰可见
- 错误处理:单个文件失败不影响其他文件
- 编码识别:自动识别TXT文件编码
- 图形界面(可选)**:方便不熟悉命令行的用户
如果需要支持更多格式或转换到Kindle格式(MOBI),建议安装Calibre并使用其命令行工具配合此脚本使用。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播