document_processor.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. """
  2. 文档处理工具
  3. 支持 Excel, Word, PDF 的文本提取和智能切片
  4. """
  5. import re
  6. from typing import List, Dict, Optional
  7. import docx
  8. import openpyxl
  9. import PyPDF2
  10. from io import BytesIO
  11. class DocumentProcessor:
  12. def __init__(self):
  13. self.chunk_size = 800 # 每个切片的字符数
  14. self.chunk_overlap = 100 # 切片之间的重叠字符数
  15. def extract_text(self, file_content: bytes, file_type: str) -> Optional[str]:
  16. """
  17. 从文件中提取文本
  18. :param file_content: 文件内容(字节)
  19. :param file_type: 文件类型(.xlsx, .docx, .pdf等)
  20. :return: 提取的文本内容
  21. """
  22. try:
  23. if file_type in ['.xlsx', '.xls']:
  24. return self._extract_excel(file_content)
  25. elif file_type in ['.docx', '.doc']:
  26. return self._extract_word(file_content)
  27. elif file_type == '.pdf':
  28. return self._extract_pdf(file_content)
  29. else:
  30. return None
  31. except Exception as e:
  32. print(f"文本提取失败: {str(e)}")
  33. return None
  34. def _extract_excel(self, file_content: bytes) -> str:
  35. """提取 Excel 文本"""
  36. wb = openpyxl.load_workbook(BytesIO(file_content), data_only=True)
  37. text_parts = []
  38. for sheet_name in wb.sheetnames:
  39. sheet = wb[sheet_name]
  40. text_parts.append(f"\n=== 工作表: {sheet_name} ===\n")
  41. for row in sheet.iter_rows(values_only=True):
  42. row_text = '\t'.join([str(cell) if cell is not None else '' for cell in row])
  43. if row_text.strip():
  44. text_parts.append(row_text)
  45. return '\n'.join(text_parts)
  46. def _extract_word(self, file_content: bytes) -> str:
  47. """提取 Word 文本"""
  48. doc = docx.Document(BytesIO(file_content))
  49. text_parts = []
  50. for para in doc.paragraphs:
  51. if para.text.strip():
  52. text_parts.append(para.text)
  53. # 提取表格
  54. for table in doc.tables:
  55. for row in table.rows:
  56. row_text = '\t'.join([cell.text.strip() for cell in row.cells])
  57. if row_text.strip():
  58. text_parts.append(row_text)
  59. return '\n'.join(text_parts)
  60. def _extract_pdf(self, file_content: bytes) -> str:
  61. """提取 PDF 文本"""
  62. pdf_reader = PyPDF2.PdfReader(BytesIO(file_content))
  63. text_parts = []
  64. for page_num in range(len(pdf_reader.pages)):
  65. page = pdf_reader.pages[page_num]
  66. text = page.extract_text()
  67. if text.strip():
  68. text_parts.append(f"\n=== 第 {page_num + 1} 页 ===\n")
  69. text_parts.append(text)
  70. return '\n'.join(text_parts)
  71. def split_text_into_chunks(self, text: str) -> List[Dict]:
  72. """
  73. 将文本智能切片
  74. :param text: 原始文本
  75. :return: 切片列表 [{"content": "...", "metadata": {...}}, ...]
  76. """
  77. # 按段落分割
  78. paragraphs = [p.strip() for p in text.split('\n') if p.strip()]
  79. chunks = []
  80. current_chunk = ""
  81. current_length = 0
  82. for para in paragraphs:
  83. para_length = len(para)
  84. # 如果当前段落加入后超过chunk_size,则保存当前chunk
  85. if current_length + para_length > self.chunk_size and current_chunk:
  86. chunks.append({
  87. "content": current_chunk.strip(),
  88. "metadata": {"char_count": current_length}
  89. })
  90. # 保留overlap部分
  91. if current_length > self.chunk_overlap:
  92. overlap_text = current_chunk[-self.chunk_overlap:]
  93. current_chunk = overlap_text + "\n" + para
  94. current_length = len(overlap_text) + para_length
  95. else:
  96. current_chunk = para
  97. current_length = para_length
  98. else:
  99. # 继续累积
  100. if current_chunk:
  101. current_chunk += "\n" + para
  102. else:
  103. current_chunk = para
  104. current_length += para_length
  105. # 保存最后一个chunk
  106. if current_chunk.strip():
  107. chunks.append({
  108. "content": current_chunk.strip(),
  109. "metadata": {"char_count": current_length}
  110. })
  111. return chunks
  112. def clean_text(self, text: str) -> str:
  113. """
  114. 清理文本:去除多余空格、特殊字符等
  115. """
  116. # 去除多余空白
  117. text = re.sub(r'\s+', ' ', text)
  118. # 去除特殊控制字符
  119. text = re.sub(r'[\x00-\x08\x0b-\x0c\x0e-\x1f\x7f]', '', text)
  120. return text.strip()
  121. # 全局单例
  122. _document_processor = None
  123. def get_document_processor() -> DocumentProcessor:
  124. """获取文档处理器单例"""
  125. global _document_processor
  126. if _document_processor is None:
  127. _document_processor = DocumentProcessor()
  128. return _document_processor