虽然许多年报具有书签,不过一些页码并不准确。每一份年报,内容里都包含目录页,但还是有一些页码不准。所以,有必要写一些代码,来识别出正确的包含页码的目录信息。
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)
年报准备的再仔细都可能有错!
顺丰控股的年报,已经是制作的非常精美了,不过并没有书签信息,而且也出了一个错误,即第一节节标题错用了“及”,而不是正确的“和”。