wechat-robot-skills/skills/pdf/scripts/extract_text.py

146 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
from __future__ import annotations
import sys
from typing import Any, Optional
from _pdf_common import (
SkillArgumentParser,
input_pdf,
run_cli,
selected_page_window,
)
DEFAULT_MAX_PAGES = 8
DEFAULT_MAX_CHARS = 24000
def _parse_args(argv: list[str]):
parser = SkillArgumentParser(description="按页分段提取 PDF 文本")
parser.add_argument("--input", required=True, help="本地 PDF 路径")
parser.add_argument("--start-page", type=int, default=1, help="起始页1-based")
parser.add_argument("--end-page", type=int, help="结束页1-based默认到末页")
parser.add_argument(
"--start-offset",
type=int,
default=0,
help="起始页文本字符偏移量,用于继续读取被截断的单页",
)
parser.add_argument(
"--max-pages",
type=int,
default=DEFAULT_MAX_PAGES,
help=f"单次最多处理页数,默认 {DEFAULT_MAX_PAGES}",
)
parser.add_argument(
"--max-chars",
type=int,
default=DEFAULT_MAX_CHARS,
help=f"单次最多返回文本字符数,默认 {DEFAULT_MAX_CHARS}",
)
parser.add_argument(
"--layout",
action="store_true",
help="尽量保留版面空格;普通总结通常不要启用",
)
args = parser.parse_args(argv)
if args.start_offset < 0:
raise ValueError("start-offset 不能小于 0")
if args.max_chars < 1:
raise ValueError("max-chars 必须大于 0")
return args
def _extract(args) -> dict[str, Any]:
import pdfplumber
path = input_pdf(args.input)
pages_output: list[dict[str, Any]] = []
used_chars = 0
next_page: Optional[int] = None
next_offset = 0
with pdfplumber.open(path) as pdf:
page_count = len(pdf.pages)
start_page, actual_end, window_next = selected_page_window(
page_count,
args.start_page,
args.end_page,
args.max_pages,
)
for page_number in range(start_page, actual_end + 1):
page = pdf.pages[page_number - 1]
text = page.extract_text(layout=args.layout) or ""
text = text.replace("\x00", "")
offset = args.start_offset if page_number == start_page else 0
if offset > len(text):
raise ValueError(
f"start-offset 超过第 {page_number} 页文本长度 {len(text)}"
)
remaining = text[offset:]
budget = args.max_chars - used_chars
if budget <= 0:
next_page = page_number
next_offset = offset
break
if len(remaining) > budget:
if pages_output and budget < min(500, len(remaining)):
next_page = page_number
next_offset = offset
break
excerpt = remaining[:budget]
pages_output.append(
{
"page": page_number,
"text": excerpt,
"char_count": len(text),
"offset_start": offset,
"offset_end": offset + len(excerpt),
"complete": False,
}
)
used_chars += len(excerpt)
next_page = page_number
next_offset = offset + len(excerpt)
break
pages_output.append(
{
"page": page_number,
"text": remaining,
"char_count": len(text),
"offset_start": offset,
"offset_end": len(text),
"complete": True,
}
)
used_chars += len(remaining)
if next_page is None:
next_page = window_next
return {
"path": str(path),
"page_count": page_count,
"start_page": start_page,
"end_page": pages_output[-1]["page"] if pages_output else None,
"window_end_page": actual_end,
"returned_chars": used_chars,
"pages": pages_output,
"has_more": next_page is not None,
"next_page": next_page,
"next_offset": next_offset,
}
def main(argv: list[str] | None = None) -> int:
arguments = sys.argv[1:] if argv is None else argv
return run_cli(lambda: _extract(_parse_args(arguments)))
if __name__ == "__main__":
raise SystemExit(main())