第三章:信息预处理——文本清洗、PDF解析与OCR识别

大家好,我是你们的老朋友。今天我们来聊聊研报Agent的“耳朵”和“眼睛”——信息预处理。

说实话,很多做量化的人,一上来就急着建模、调参数。但我个人习惯,花在数据清洗上的时间,至少占整个项目的40%。为什么?因为垃圾进,垃圾出。你喂给模型一堆乱码、重复文本、扫描歪了的PDF,它再聪明也白搭。

这一章,我们就来拆解三个核心环节:文本清洗(去噪、去重)PDF解析OCR识别。这三板斧抡好了,你的研报数据才算真正“能用”。

3.1 文本清洗:去噪与去重

先说说文本清洗。你以为从PDF里抽出来的文字就能直接用?太天真了。

我在项目中遇到过最离谱的情况:一份研报里,光“免责声明”就重复了8遍,中间还夹杂着“数据来源:Wind”、“分析师:张三”这种模板化噪音。如果不处理,模型会把“免责声明”当成核心内容去学习。

去噪,说白了就是干掉那些没用的东西。

  • 规则过滤:用正则表达式干掉页眉页脚、表格边框、特殊符号(比如“■”、“●”)。
  • 停用词移除:去掉“的”、“了”、“是”这类高频但无意义的词。但注意,金融领域要保留“风险”、“收益”这类关键词。
  • 空白字符压缩:把多个空格、换行符合并成一个,避免模型被格式干扰。

核心原则:去噪不是越狠越好。我曾经把“同比增长”里的“同比”当成噪音去掉了,结果模型死活学不会趋势判断。所以,保留业务语义是第一位的。

去重,则是另一个大坑。

你想想看,同一份研报,可能被不同券商转载,内容一模一样。或者同一家券商,在不同时间点发布了内容高度相似的周报。如果不做去重,模型会严重过拟合。

我常用的方法是SimHash算法。它能把一段文本压缩成一个64位的指纹,然后通过计算海明距离来判断相似度。距离小于3的,基本就是重复内容。

# 一个简单的SimHash示例(伪代码)
def simhash(text):
    # 1. 分词
    words = tokenize(text)
    # 2. 计算每个词的hash值
    v = [0] * 64
    for word in words:
        hash_val = hash(word)
        for i in range(64):
            if hash_val & (1 << i):
                v[i] += 1
            else:
                v[i] -= 1
    # 3. 生成指纹
    fingerprint = 0
    for i in range(64):
        if v[i] > 0:
            fingerprint |= (1 << i)
    return fingerprint

# 计算两个指纹的海明距离
def hamming_distance(fp1, fp2):
    return bin(fp1 ^ fp2).count('1')

小技巧:对于研报这种长文本,我建议先按段落去重,再按全文去重。因为有时候整篇不重复,但某一段被反复引用,也会污染数据。

3.2 PDF解析:PyMuPDF vs pdfplumber

PDF解析,是研报Agent的“眼睛”。市面上工具很多,但我最常用的就两个:PyMuPDF(fitz)pdfplumber

为什么?因为它们各有绝活。

特性 PyMuPDF pdfplumber
解析速度 极快(C语言底层) 中等(Python底层)
表格提取 一般(需手动处理) 优秀(自动识别表格结构)
文本布局 保留原始位置信息 保留字体、字号、位置
适用场景 纯文本、快速批量处理 复杂表格、带格式的研报

我个人习惯:如果研报主要是文字,比如宏观分析、策略点评,我用PyMuPDF,因为它快,一秒能解析几十页。但如果研报里有大量表格,比如财务数据、估值对比,我必用pdfplumber。

举个例子,用pdfplumber提取表格数据:

import pdfplumber

with pdfplumber.open("report.pdf") as pdf:
    for page in pdf.pages:
        tables = page.extract_tables()
        for table in tables:
            for row in table:
                print(row)  # 每一行是一个列表

避坑指南:我曾经用PyMuPDF解析一份带水印的研报,结果水印文字和正文混在一起,模型把“机密文件”当成了正文内容。后来我改用pdfplumber,通过设置page.filter(lambda obj: obj['object_type'] != 'image')过滤掉水印层,才解决问题。

3.3 OCR识别:PaddleOCR实战

最后说说OCR。很多研报是扫描件,或者图片格式的PDF。这时候,PyMuPDF和pdfplumber都无能为力,必须上OCR。

我推荐PaddleOCR。为什么?因为它开源、免费、中文识别率极高,而且支持GPU加速。

它的核心逻辑很简单:先检测出图片中的文字区域,再识别每个区域里的文字。

from paddleocr import PaddleOCR

# 初始化OCR模型
ocr = PaddleOCR(use_angle_cls=True, lang='ch')

# 识别图片
result = ocr.ocr('scanned_report.png', cls=True)

# 提取文字
for line in result:
    for word_info in line:
        text = word_info[1][0]
        confidence = word_info[1][1]
        print(f"文字: {text}, 置信度: {confidence:.2f}")

嗯,这里要注意:置信度阈值。我一般设置0.8以上才保留,低于这个值的,要么是模糊的,要么是识别错了。曾经有一次,PaddleOCR把“净利润”识别成了“净利闰”,就是因为置信度只有0.6,我没过滤掉。

实战建议:对于扫描件PDF,我通常先转成图片,再用PaddleOCR批量识别。如果图片分辨率太低,可以先用OpenCV做一次超分辨率处理,识别率能提升10%以上。

3.4 本章知识体系

为了让你更直观地理解这三者的关系,我画了一张流程图:

研报信息预处理流程 原始研报(PDF/图片) 是PDF? PyMuPDF/pdfplumber PaddleOCR识别 文本清洗(去噪、去重) 结构化文本数据

你看,整个流程其实不复杂。先判断输入是PDF还是图片,然后选择合适的工具解析,最后统一做清洗。但每一步都有坑,我踩过的,希望你别再踩。

好了,这一章就到这里。记住:数据预处理做得好,模型训练没烦恼。下一章我们聊聊如何把这些清洗好的文本,喂给大模型做摘要生成。


公众号:蓝海资料掘金营,微信deep3321