cleaner.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. """
  2. 文本清洗与纠错
  3. 处理 PDF 提取文本中的格式问题
  4. """
  5. import re
  6. import logging
  7. from typing import Optional
  8. logger = logging.getLogger(__name__)
  9. class TextCleaner:
  10. def __init__(self):
  11. self.replacements = [
  12. # 先处理断行连接(在移除不当换行之前),否则这些规则永远无法匹配
  13. (r"([a-z])\n([a-z])", r"\1\2"), # 英文单词断行连接
  14. (r"(\d)\n(\d)", r"\1\2"), # 数字断行连接
  15. (r"(?<!\n)\n(?!\n)", ""), # 移除不当换行(段落内的单换行)
  16. (r"\x00", ""), # 移除空字符
  17. (r"\n{3,}", "\n\n"), # 多个连续换行缩为两个
  18. (r"\t", " "), # 制表符替换
  19. (r" {2,}", " "), # 多个空格缩为一个
  20. ]
  21. def clean(self, text: str) -> str:
  22. result = self._normalize_width(text)
  23. result = self._normalize_units(result)
  24. for pattern, replacement in self.replacements:
  25. result = re.sub(pattern, replacement, result)
  26. return result.strip()
  27. def clean_drug_entry(self, text: str) -> str:
  28. cleaned = self.clean(text)
  29. cleaned = self._fix_common_ocr_errors(cleaned)
  30. return cleaned
  31. def _fix_common_ocr_errors(self, text: str) -> str:
  32. """扩展的药学 OCR 纠错字典,覆盖常见字形混淆和编码问题"""
  33. ocr_fixes = {
  34. # ---- 已有 ----
  35. "咤": "啶",
  36. "唾": "唑",
  37. "茶碱": "茶碱",
  38. # ---- 化学药常见 OCR 错误 ----
  39. "甘": "苷", # 苦杏仁甘→苦杏仁苷
  40. "甙": "苷", # 异体字规范
  41. "溜": "馏", # 蒸溜→蒸馏
  42. "腊": "蜡", # 腊→蜡
  43. "酿": "酶", # 酿→酶 (OCR)
  44. "容": "密", # 容封→密封
  45. "陚": "酯", # 化学式
  46. "肔": "肽",
  47. "唼": "哌",
  48. "噻昉": "噻吩",
  49. "涣": "溴",
  50. "氮卓": "氮䓬",
  51. "畄": "留",
  52. "貭": "质",
  53. "千燥": "干燥",
  54. "堿": "碱",
  55. }
  56. for wrong, correct in ocr_fixes.items():
  57. text = text.replace(wrong, correct)
  58. return text
  59. def _normalize_width(self, text: str) -> str:
  60. """全角/半角字符规范化,确保标点和数字一致性"""
  61. # 全角 ASCII 标点 → 半角(保留中文逗号句号)
  62. width_map = {
  63. "!": "!", "?": "?", """: '"', "'": "'",
  64. ":": ":", ";": ";", "(": "(", ")": ")",
  65. "【": "[", "】": "]", "~": "~",
  66. # 全角数字 → ASCII
  67. "0": "0", "1": "1", "2": "2", "3": "3", "4": "4",
  68. "5": "5", "6": "6", "7": "7", "8": "8", "9": "9",
  69. # 全角大写字母 → ASCII
  70. "A": "A", "B": "B", "C": "C", "D": "D", "E": "E",
  71. "F": "F", "G": "G", "H": "H", "I": "I", "J": "J",
  72. "K": "K", "L": "L", "M": "M", "N": "N", "O": "O",
  73. "P": "P", "Q": "Q", "R": "R", "S": "S", "T": "T",
  74. "U": "U", "V": "V", "W": "W", "X": "X", "Y": "Y", "Z": "Z",
  75. "a": "a", "b": "b", "c": "c", "d": "d", "e": "e",
  76. "f": "f", "g": "g", "h": "h", "i": "i", "j": "j",
  77. "k": "k", "l": "l", "m": "m", "n": "n", "o": "o",
  78. "p": "p", "q": "q", "r": "r", "s": "s", "t": "t",
  79. "u": "u", "v": "v", "w": "w", "x": "x", "y": "y", "z": "z",
  80. }
  81. for full, half in width_map.items():
  82. text = text.replace(full, half)
  83. return text
  84. def _normalize_units(self, text: str) -> str:
  85. """药品计量单位规范化"""
  86. unit_fixes = [
  87. (r'\bug\b', 'μg'), # ug → μg
  88. (r'\buL\b', 'μL'), # uL → μL
  89. (r'\bum\b', 'μm'), # um → μm
  90. (r'(?i)\bml\b', 'mL'), # ML/ml/Ml → mL
  91. (r'(?i)\bul\b', 'μL'), # ul → μL
  92. (r'(\d+)h\b', r'\1小时'), # 24h → 24小时
  93. (r'(\d+)min\b', r'\1分钟'), # 30min → 30分钟
  94. ]
  95. for pattern, replacement in unit_fixes:
  96. text = re.sub(pattern, replacement, text)
  97. return text