其他
数据治理 | 省下一个亿!一文读懂如何用python读取并处理PDF中的表格(赠送本文所用的PDF文件)
目录
一、前言
二、背景案例
三、数据清洗流程
理解数据,明确要求
清洗数据流程
(1)PDF转化为EXCEL
(2)清洗&合并所有表
(3)按需求处理合并后的表
四、总结
本文共6039个字,阅读大约需要16分钟,欢迎指正!
如需获取文中所用到的PDF文件,可关注我们,对话框内发送关键词“20220708”~
Part1前言
Part2案例背景
从 PDF 中读取表格,并将所有表格合并为一张表 将 表名称
字段分割为表格名称_英文
,表格名称_中文
两个字段将一个表名下的所有中英文变量名分别合并在一起,使用顿号“、”连接在一起
Part3数据清理流程
1理解数据,明确需求
pdfplumber
即可读取所需表,使用第三方库 xlwt
可以将读取到的表格写入 Excel 中。2数据清洗流程
(1) PDF 转化为 Excel
一共 9 个文件需要处理,如果每处理一份 PDF 都需要手动修改文件路径,显然是效率低下的。所以我们可以使用 Python 标准库 os 一次性获取所有 PDF 的文件路径。我们将所有待处理 PDF 全部放入 Python 工作目录下的一个名为转换前的PDF
文件夹中,使用下面的方法就可以获取该文件夹下所有 PDF 的路径:# 获取某文件夹下所有指定后缀名文件的路径
import os
def get_file_list(path, ex):
"""
获取指定路径下的符合后缀名条件的文件路径
path: 监测目录的路径
ex: 条件,文件后缀名
return: 指定后缀名文件路径列表
"""
file_path_list = []
for dir, folder, file in os.walk(path):
for i in file:
if os.path.splitext(i)[1] in ex:
file_path_list.append(os.path.join(dir, i))
return file_path_list
# 获取路径,文件所在目录(文件全部存放在一个文件夹下,最好不要设置子文件夹,不要存放其他文件)
# 获取 文件夹 PDF下所有后缀名是 .pdf 的文件路径
pdf_path_list = get_file_list('./转换前的PDF', ['.pdf'])
pdf_path_list
输出结果如下图所示:
pdfplumber
和 xlwt
定义一个可以将一个 PDF 中所有表格信息转换为 Excel 表的方法,并将所有转换后的 Excel 表存放在名为 转换后的Excel
的文件夹下,文件名称不变。代码如下:import pdfplumber
import xlwt
def pdf_to_excel(pdf_path, excel_path):
'''
参数说明:
pdf_path: pdf 文档的路径
excel_path: 保存 excel 表的路径
'''
workbook = xlwt.Workbook() # 定义一个空的工作表,名为workbook
sheet = workbook.add_sheet('Sheet1') # 为工作表添加一个 sheet,sheet名称为 'Sheet1',变量名为 sheet
# 使用 pdfplumber.open 方法读取pdf 中所有信息
pdf = pdfplumber.open(pdf_path)
i = 0 # Excel 行序号起始位置,Python 中往往从 0 开始是计算
# 以 PDF 的一页为单位,循环读取所有 PDF 页
for page in pdf.pages:
# page.extract_text() 表示获取 PDF 每一页的所有文字信息,包括表格、页眉页脚页码。
# 而 page.extract_tables() 则表示获取 PDF 每一页的所有表格,不含页眉页脚页码,
# 根据我们的需求,使用 page.extract_tables() 最合适。
for table in page.extract_tables():
# table 表示这一页的所有表格,格式为多维列表
for row in table:
# row 表示表 table 中的一行,格式为列表
for j in range(len(row)):
# j 表示一行数据 row 中数据单元格的序号,从 0 开始计算
# 将一行数据 row 的第 j 个数据(row[j]) 写入sheet 的第 i 行,j 列
sheet.write(i, j, row[j])
# 一次写入一行数据,写操作完成后行序号 i 就会加1,表示稍后在下一行写入数据
# 以这样的方式将 pdf 表中的每一个单元格,写入 Excel 中对应的单元格
i += 1
# 关闭读取的 pdf
pdf.close()
# 将写好的 Excel 表保存到指定位置
workbook.save(excel_path)
# 输出一个转换成功的信号
print('写入excel成功!')
# 调用上面的方法转换 PDF 为 Excel 表
for pdf_path in pdf_path_list:
# 从 pdf 路径中提取文件名称,并生成对应的 excel 保存路径
excel_name = pdf_path.split('\\')[-1].replace('.pdf', '.xlsx')
excel_path = os.path.join('./转换后的Excel', excel_name)
# 开始转换
pdf_to_excel(pdf_path, excel_path)
(2) 清洗&合并所有表
表名称
字段中的分布在不同单元格的表名称写入一个单元格中,合并表名称。清洗以及合并所有表的代码如下:# 使用前面定义的获取指定后缀名文件路径的方法,来获取转换好的 Excel 路径
excel_path_list = get_file_list('./转换后的Excel', ['.xlsx'])
# 建立一个空的表,随后将所有处理后的表都并入这张表
DATA = pd.DataFrame()
# 遍历所有 Excel 表路径
for path in excel_path_list:
# 读取一张表
data = pd.read_excel(path)
# 将这张表中重复出现的表头删除
# 将‘表名称’字段设置为数据索引后,删除索引为‘表名称’的行,原来的表头不受影响
try:
data = data.set_index('表名称').drop('表名称').reset_index()
data.reset_index(drop=True, inplace=True) # 重置数据索引,使索引恢复连续状态
except:
pass
# 合并位于不同单元格的表名称,主要思路是:
# 若出现连续两行数据不为空,且后面一行中不含汉字,则将下面一行的字符加入到上面那一行中
# 检测是否为空使用 pd.isna() 方法,检测是否含有汉字使用正则表达式
for i in data.index:
if pd.isna(data['表名称'][i]) == False and \
pd.isna(data['表名称'][i+1]) == False and\
bool(re.search('[\u4e00-\u9fa5]', data['表名称'][i+1])) == False:
# 修改数据
data['表名称'][i] += data['表名称'][i+1]
data['表名称'][i+1] = np.nan
# 清洗表名称字段,去除其中的换行符
data['表名称'] = data['表名称'].fillna('')
data['表名称'] = data['表名称'].apply(lambda x: x.replace('\n', ''))
# 两处理后的单张表添加到大数据表 DATA 中
DATA = pd.concat([DATA, data])
print(DATA.shape)
DATA
(3) 按需求处理合并后的表
# 先将空字符替换为空值,下面使用其他填充方式重新填充
DATA['表名称'] = DATA['表名称'].replace('', np.nan)
# 向下填充, 一列中的空值使用上一个非空值来填充
DATA['表名称'] = DATA['表名称'].ffill()
# 防止报错,填充其他字段中的缺失值
DATA['字段名称_英文'] = DATA['字段名称_英文'].fillna('无')
DATA['字段名称_中文'] = DATA['字段名称_中文'].fillna('无')
# 分组合并的自定义方法
def concat_function(x):
return pd.Series(
{
'字段名称_英文':'、'.join(x['字段名称_英文']),
'字段名称_中文':'、'.join(x['字段名称_中文'])
}
)
# 关键步骤,先根据'表名称'进行分组,分组后使用'、'将一组下面的所有元素拼接在一起,最后重置索引
DATA = DATA.groupby(DATA['表名称']).apply(concat_function).reset_index()
DATA
表名称
字段拆分为表名称_英文
和表名称_中文
字段即可,我们注意到,所有的表名称格式都是一样的,即中文名称在前,英文名称在最后,放在括号里面。我们正好可以使用正则表达式来处理。将表名称字段拆分为两个字段的代码如下:# 标准化括号,将所有英文括号替换为中文括号,防止出错
DATA['表名称'] = DATA['表名称'].apply(lambda x: x.replace('(', '(').replace(')', ')'))
# 定义拆分方法
def split_function(df):
Table_name = df['表名称']
# 从完整的表明中提取中英文表名称
# 提取英文名,使用正则表达式获取所有括号内的内容,取最后一个即可
name_eng = re.findall('(.*?)', Table_name)[-1]
name_eng = name_eng.strip('(').strip(')')
# 提取中文名,从原来的表名称中删除英文名即可
name_cn = Table_name.replace(f'({name_eng})', '')
# 返回提取结果
return name_eng, name_cn
# 事先在指定位置插入两列
DATA.insert(1, '表名称_中文', '')
DATA.insert(1, '表名称_英文', '')
DATA[['表名称_英文', '表名称_中文']] = DATA.apply(split_function, axis=1, result_type='expand')
# 删除原来的表名称
DATA.drop(['表名称'], axis=1, inplace=True)
DATA
DATA.to_excel('变量清单_合并.xlsx', index=False)
Part4总结
另外,如需获取文中所用到的PDF文件,可关注我们,对话框内发送关键词“20220708”~用于帮助大家测试代码以及了解我们的数据产品,希望对大家有所帮助。
我们将在数据治理板块中推出一系列原创推文,帮助读者搭建一个完整的社科研究数据治理软硬件体系。该板块将涉及以下几个模块(点击标题即可跳转至相应合集):
星标⭐我们不迷路!想要文章及时到,文末“在看”少不了!
点击搜索你感兴趣的内容吧
往期推荐
数据Seminar
这里是大数据、分析技术与学术研究的三叉路口
文 | 《社科领域大数据治理实务手册》
欢迎扫描👇二维码添加关注