2. 金融文本命名实体识别:公司名与股票代码

任务与数据

从公告文本中抽取三类实体:公司全称(以“股份有限公司/有限公司”结尾)、公司简称(“证券简称:XXX”)、股票代码(6位数字,常见前缀 600/601/603/000/002/300)。输出一张映射表:简称 → (全称, 代码)。

依赖:Python 3.8+,仅用标准库 re、json,无需第三方包。

抽取流程

公告文本 正则:全称 正则:简称/代码 实体映射表 JSON 输出

完整实现

import re, json

FULL_RE = re.compile(r'[\u4e00-\u9fa5]{2,20}(?:股份有限公司|有限公司)')
CODE_RE = re.compile(r'(?<!\d)((?:600|601|603|000|002|300)\d{3})(?!\d)')
ABBR_RE = re.compile(r'证券简称[::]\s*([\u4e00-\u9fa5A-Za-z0-9]{2,10})')

def extract(text):
    fulls = FULL_RE.findall(text)
    codes = CODE_RE.findall(text)
    abbrs = ABBR_RE.findall(text)
    # 取出现次数最多的全称作为主全称
    full = max(set(fulls), key=fulls.count) if fulls else None
    code = codes[0] if codes else None
    abbr = abbrs[0] if abbrs else None
    return {"简称": abbr, "全称": full, "代码": code}

def build_map(texts):
    table = {}
    for t in texts:
        e = extract(t)
        if e["简称"] and e["代码"]:
            table[e["简称"]] = {"全称": e["全称"], "代码": e["代码"]}
    return table

if __name__ == "__main__":
    docs = [
        "贵州茅台酒股份有限公司公告:证券简称:贵州茅台,证券代码:600519。",
        "比亚迪股份有限公司 证券简称:比亚迪 股票代码:002594 关于年度报告。",
        "宁德时代新能源科技股份有限公司,证券简称:宁德时代,代码 300750。",
    ]
    table = build_map(docs)
    print(json.dumps(table, ensure_ascii=False, indent=2))

运行与验证

运行:python extract.py。预期输出(由代码逻辑唯一确定):

{
  "贵州茅台": {"全称": "贵州茅台酒股份有限公司", "代码": "600519"},
  "比亚迪": {"全称": "比亚迪股份有限公司", "代码": "002594"},
  "宁德时代": {"全称": "宁德时代新能源科技股份有限公司", "代码": "300750"}
}

通过条件:三个简称均被识别,全称以“股份有限公司/有限公司”结尾,代码为6位且前缀合法。若某条公告缺少简称或代码,该条不进入映射表(如代码中 if e["简称"] and e["代码"] 所保证)。

常见问题

  • 全称误匹配:正则限定 2–20 个汉字,避免跨句吞并;若公告含多个全称,取出现次数最多者。
  • 代码误匹配:用 (?<!\d) 与 (?!\d) 边界,防止从更长数字串中截取。
  • 简称缺失:部分公告用“证券简称”以外的表述,可扩展 ABBR_RE 的别名列表。