1 目录解析

虽然许多年报具有书签,不过一些页码并不准确。每一份年报,内容里都包含目录页,但还是有一些页码不准。所以,有必要写一些代码,来识别出正确的包含页码的目录信息。

1.2 代码

import re
import fitz

# pdf = '下载的年报.pdf'
pdf = '2021-03-18-002352.SZ-顺丰控股_2020年年度报告.pdf' # 须和代码文件在同一文件夹
doc = fitz.open(pdf)
toc_bookmarks = doc.get_toc()

def getText(pdf):
    text = ''
    doc = fitz.open(pdf)
    for page in doc:
        text += page.get_text()
    doc.close()
    return(text)

def getTOC_level1(headings,pdf):
    doc = fitz.open(pdf)
    toc = []
    for page in doc:
        text = page.get_text()
        pageNo = page.number
        if len(text) > 100:
            continue
        text = re.sub('\s', '', text)
        for heading in headings:
            title = re.sub('\s', '', heading[1])
            if re.search(title, text):
                toc.append([1,heading[0],title,pageNo])
                break
    return(toc)

def getTOC(pdf,level=2):
    # text = getText(pdf)
    if level not in [1,2,3]:
        raise ValueError("level 应该为 1、2、3")
    toc = []
    doc = fitz.open(pdf)
    # 需要排除目录页的干扰。
    p_tocpage = re.compile('(?<=\\n)\s*目录\s*(?=\\n)')
    for page in doc:
        text = page.get_text()
        if p_tocpage.search(text):
            pageNo_toc = page.number        
            break
    # 需要剔除审计报告影响
    # 三级目录
    p_level_1 = re.compile('(第\w{1,2}节)\s+([\w、,]*)')
    p_level_2 = re.compile('([一二三四五六七八九十]{1,2})、\s*(.*)')
    p_level_3 = re.compile('(\d{1,2})、\s*(.*)')
    for page in doc:
        text = page.get_text()
        pageNo = page.number # 注意从0开始
        if pageNo == pageNo_toc:
            # 解析目录页
            headings = p_level_1.findall(text)
            continue
        lines = text.splitlines()
        lines = [l.strip() for l in lines if l.strip() != '']
        for line in lines:
            match1 = p_level_1.match(line)
            if match1:
                toc.append([1,match1.group(1),match1.group(2),pageNo])
            if level >= 2:
                match2 = p_level_2.match(line)
                if match2:
                    toc.append([2,match2.group(1),match2.group(2),pageNo])
            if level == 3:
                match3 = p_level_3.match(line)
                if match3:
                    toc.append([3,match3.group(1),match3.group(2),pageNo])
    # 以上对顺丰控股2020年报不适用
    toc_1 = [c for c in toc if c[0] == 1]
    if len(toc_1) < 10:
        # above is failed
        toc = getTOC_level1(headings, pdf)
    return((toc,headings))

toc,headings = getTOC(pdf,level=2)
text = getText(pdf)

1.3 结果分析

年报准备的再仔细都可能有错!

顺丰控股的年报,已经是制作的非常精美了,不过并没有书签信息,而且也出了一个错误,即第一节节标题错用了“及”,而不是正确的“和”

  • 正文页截图

  • 目录页截图

  • 正文页中识别出的

  • 目录页中提取出的目录

怎么办?

2 年报发布与命名

有一些公司,发布年报之后,发现错误,会发布更新版年报。我们可能觉得年报是一件严肃的事情,但是,还是有不少企业会犯错。

2.1 花样命名:更新后|修订稿|。。。

就算是更新,也有各种叫法:

这样就算了,各家有各自的偏好。

但是,下面这样的就有点“难以原谅”!

2.2 更新前后不一致:年报|年度报告

2.3 解决方案

如何处理?

import re
import pandas as pd
import os

df = pd.read_csv('证券行业.csv')
# df = pd.read_csv('新能源行业.csv')
# 标准化年报文件名
p = re.compile('(?<=\d{4})(年报)|(年年报)')
f_names = [p.sub('年年度报告',f) for f in df.f_name]
df['f_name'] = f_names; del p, f_names