网页表格抓取( 还在用收费的工具处理PDF?用Python助力冲破会员牢笼)
优采云 发布时间: 2021-11-09 23:21网页表格抓取(
还在用收费的工具处理PDF?用Python助力冲破会员牢笼)
作者:Seon,链接:还在使用付费工具处理 PDF?用Python帮助突破会员牢笼
大家好,我是@无欢不散,资深互联网玩家,Python技术爱好者,喜欢分享硬核技术。
欢迎关注我的专栏:
前言:害的,又要处理PDF文件了。需要的是提取表格,还得请有一定软件会员的同事来操作。没想到会员早就过期了。毕竟不常用,也没有必要给PDF文件充值。以我的码字速度,我可以在输入支付密码之前输入所有内容。但转念一想,PDF自动化的需求还有很多种,比如增删改页面,提取文字图片等等,万一哪天满足了*敏*感*词*操作的需要,可能就麻烦了。自动化之王(不是)Python 有办法!让我们来看看。
我们先来看看作者遇到的问题。我要处理的是这样一个PDF文件。前几页为正文,文末附有几张表格。其中一张表格跨页分布。如何提取表格?
核心代码只需要两行。打开PDF文件和阅读表格非常方便。
import pdfplumber
pdf = pdfplumber.open("test.pdf")
for i in range(2, 4):
tables = pdf.pages[i].extract_tables()
for t in tables:
print(t)
从输出结果可以看出,它是一种嵌套列表形式,一张表展开后的部分将被识别为另一张表。
[['序列号','标题1','标题2','标题3'], ['1','内容','内容','内容'], ['2','内容', '内容','内容'], ['3','内容','内容','内容']] [['序列号','字段1','字段2','字段3','字段 4 ','字段 5'], ['1','Content','Content','Content','Content','Content'], ['2','Content','Content',' Content' ,'Content','Content'], ['3','Content','Content','Content','Content','Content'], ['4','Content','Content' , '内容','内容','内容'], ['5','content','content','content','content','content']] [['6','content','content','content','content','content'], ['7 ','Content','Content','Content','Content','Content'], ['8','Content','Content','Content','Content','Content'], [ '9','Content','Content','Content','Content','Content'], ['10','Content','Content','Content','Content','Content'] ] [ ['序列号','表头1','表头2','表头3','表头4'], ['1','content','content','content', '内容'], ['2','Content','Content','Content','Content'], ['3','Content','Content','Content','Content'], ['4','Content','content' ,'content','content'], ['5','content','content','content','content']]'Content','Content','Content','Content'], [ '9','Content','Content','Content','Content','Content'], ['10','Content','Content','Content','Content','Content'] ] [['序列号','Header 1','Header 2','Header 3','Header 4'], ['1','Content','Content','Content','Content'] , ['2','内容','内容','内容','内容'], ['3', '内容','内容','内容','内容'], ['4','内容','内容','内容','内容'], ['5','content','content','Content','content']]'Content','Content','Content','Content'], ['9','Content' ,'Content','Content','Content','Content'], ['10','Content','Content','Content','Content','Content']] [['序列号' ,'Header 1','Header 2','Header 3','Header 4'], ['1','Content','Content','Content','Content'], ['2','内容','内容','内容','内容'], ['3', '内容','内容','内容','内容'], ['4','内容','内容','内容','内容'],[' 5','content','content','Content','content']]Content','content','content']] [['序列号','表头1','表头2' ,'table header 3','table header 4'], ['1','content','content','content','content'], ['2','content','content',' content','content'], ['3','content','content', 'Content','Content'], ['4','Content','Content','Content','Content' ], ['5','内容','内容','内容','内容']]内容','内容','内容']] [['序列号','表头1','表头2','表头3','表头4 '], ['1','content','content','content','content'], ['2','content','content','content','content'], ['3 ','content','content', 'Content','Content'], ['4','Content','Content','Content','Content'], ['5','Content', '内容','内容','内容']]内容'], ['5','内容','内容','内容','内容']]内容'],['5','内容','内容','内容','内容']]Content','Content']]Content','content','content']] [['序列号','表头1','表头2','表头3','表头4' ], ['1','content','content','content','content'], ['2','content','content','content','content'], ['3' ,'content','content', 'Content','Content'], ['4','Content','Content','Content','Content'], ['5','Content',' Content','Content','Content']]Content'], ['5','content','content','content','content']]Content'], ['5','content' ,'内容','内容','内容']]Content','Content']]Content','content','content']] [['序列号','表头1','表头2','表头3','表头4' ], ['1','content','content','content','content'], ['2','content','content','content','content'], ['3' ,'content','content', 'Content','Content'], ['4','Content','Content','Content','Content'], ['5','Content',' Content','Content','Content']]Content'], ['5','content','content','content','content']]Content'], ['5','content' ,'内容','内容','内容']],'内容']]内容','内容','内容']] [['序列号','表头1','表头2','表头3','表头4'], ['1','content','content','content','content'],['2','content','content','content','content'],['3',' content','content', 'Content','Content'], ['4','Content','Content','Content','Content'], ['5','Content','Content' ,'Content','Content ']]Content'], ['5','content','content','content','content']]Content'], ['5','content','内容','内容','内容']],'内容']]内容','内容','内容']] [['序列号','表头1','表头2','表头3','表头4'], ['1','content','content','content','content'],['2','content','content','content','content'],['3',' content','content', 'Content','Content'], ['4','Content','Content','Content','Content'], ['5','Content','Content' ,'Content','Content ']]Content'], ['5','content','content','content','content']]Content'], ['5','content','内容','内容','内容']]]]内容','内容','内容']] [['序列号','表头1','表头2','表头3','表头4'],['1' ,'content','content','content','content'], ['2','content','content','content','content'], ['3','content',' content', 'Content','Content'], ['4','Content','Content','Content','Content'], ['5','Content','Content','Content' ,'Content ']]Content'], ['5','content','content','content','content']]Content'], ['5','content','content','内容','内容']]]]内容','内容','内容']] [['序列号','表头1','表头2','表头3','表头4'],['1' ,'content','content','content','content'], ['2','content','content','content','content'], ['3','content',' content', 'Content','Content'], ['4','Content','Content','Content','Content'], ['5','Content','Content','Content' ,'Content ']]Content'], ['5','content','content','content','content']]Content'], ['5','content','content','内容','内容']]content']] [['序列号','表头1','表头2','表头3','表头4'],['1','内容','内容',' content','content'], ['2','content','content','content','content'], ['3','content','content', 'Content','Content' ], ['4','Content','Content','Content','Content'], ['5','Content','Content','Content','Content']]Content'], ['5','content','content','content','content']]Content'], ['5','content','content','content','content']]content']] [['序列号','表头1','表头2','表头3','表头4'],['1','内容','内容',' content','content'], ['2','content','content','content','content'], ['3','content','content', 'Content','Content' ], ['4','Content','Content','Content','Content'], ['5','Content','Content','Content','Content']]Content'], ['5','content','content','content','content']]Content'], ['5','content','content','content','content']]表头 2','表头 3','表头 4'], ['1','content','content','content','content'], ['2','content',' content','content','content'], ['3','content','content', 'Content','Content'], ['4','Content','Content','Content' ,'Content'], ['5','Content','Content','Content','Content ']]Content'], ['5','content','content','content',' content']]Content'], ['5','content','content','content','content']]表头 2','表头 3','表头 4'], ['1','content','content','content','content'], ['2','content',' content','content','content'], ['3','content','content', 'Content','Content'], ['4','Content','Content','Content' ,'Content'], ['5','Content','Content','Content','Content ']]Content'], ['5','content','content','content',' content']]Content'], ['5','content','content','content','content']]['2','content','content','content','content'],['3','content','content','Content','Content'],['4','内容','内容','内容','内容'], ['5','内容','内容','内容','内容']]内容'],['5','内容' ,'content','content','content']]Content'], ['5','content','content','content','content']]['2','content','content','content','content'],['3','content','content','Content','Content'],['4','内容','内容','内容','内容'], ['5','内容','内容','内容','内容']]内容'],['5','内容' ,'content','content','content']]Content'], ['5','content','content','content','content']]Content','Content','Content']]Content'], ['5','content','content','content','content']]Content'], ['5','content' ,'内容','内容','内容']]Content','Content','Content']]Content'], ['5','content','content','content','content']]Content'], ['5','content' ,'内容','内容','内容']]
那就用legend可以让EXCEL飞起来的xlwings库来写数据吧!对代码稍作修改,详情见注释。
import pdfplumber
import xlwings as xw
wb = xw.books.active # 连接到活动的工作簿
sht = wb.sheets['Sheet1'] # 连接子表1
n = 1
pdf = pdfplumber.open("test.pdf") # 打开PDF
for i in range(2, 4):
tables = pdf.pages[i].extract_tables() # 获取第3、4页的表格
for t in tables:
for row in t:
if '序号' in row and n>1:
n += 1 # 不同表格之间隔一行
sht.range(f'A{n}').value = row # 将表格的一行从A1单元格开始写入
n += 1 # 换行
果然,这种肉眼可见的效果,比其他保存表格前在内存中执行的EXCEL处理库更让人享受。您还可以使用 xlwings 为每个表格添加边框、粗体标题等。我不会在这里详细介绍。有兴趣的同学可以自行探索。本文的主角是PDF处理库。
pdfplumber 是一款基于pdfminer.six 的PDF 内容提取工具。和之前的体验一样,操作很简单。遗憾的是,它专门用于识别,不能直接修改和保存PDF。如果有比较复杂的需求,可以学习pdfminer,但是需要了解PDF文件模型,学习难度大。
官网:/jsvine/pdfplumber
PyPDF2也是著名的PDF页面级处理工具。它更擅长分割、合并、裁剪和转换页面,但缺乏提取内容的能力。
官网:/PyPDF2
让我们从常见的 PDF 操作中了解这两个!
1、插入/添加页面
让我们准备另一个 PDF 文件用于插入实验。
PyPDF2 使用阅读器来操作 PDF 文件。如果想在第一页之后插入一个页面,需要通过Reader对象的getPage方法获取该页面,然后通过Writer对象的addPage方法添加该页面,最后写入文件流。
from PyPDF2 import PdfFileWriter, PdfFileReader
output = PdfFileWriter()
input = PdfFileReader(open("test.pdf", "rb"))
insert = PdfFileReader(open("insert.pdf", "rb"))
for i in range(0, 4):
output.addPage(input.getPage(i))
if i ==0:
output.addPage(insert.getPage(0))
output.write(open("output.pdf", "wb"))
2、选择/删除页面
通过插入页面,相信大家已经可以想到一种选择/删除页面的方法,即选择需要的页面索引来添加页面。例如,在上一步中只为 output.pdf 保留了第 1 页和第 3 页。
from PyPDF2 import PdfFileWriter, PdfFileReader
new_output = PdfFileWriter()
input = PdfFileReader(open("output.pdf", "rb"))
for i in range(0, 4):
if i==0 or i==2:
new_output.addPage(input.getPage(i))
new_output.write(open("new_output.pdf", "wb"))
3、提取文本信息
不得不说pdfplumber非常精炼,用非常直观的句子就可以实现。可以通过页面对象的extract_text 方法提取文本。需要注意的是pdfplumber只能提取原生内容。如果是图片,则需要使用OCR工具。
import pdfplumber
pdf = pdfplumber.open("test.pdf")
pages = pdf.pages
for p in pages:
print(p.extract_text())
1 1 1 2 2 2 3 3 3 表1 序号标题1 标题2 标题3 1 内容内容内容2 内容内容内容3 内容内容内容表二......4、提取表单信息
之前我使用extract_tables()批量提取表。我们也可以通过extract_table()来提取指定页面上的表,然后加载到DataFrame中进行后续的分析操作。
import pdfplumber
import pandas as pd
pdf = pdfplumber.open("test.pdf")
table = pdf.pages[2].extract_table()
df = pd.DataFrame(table[1:], columns=table[0])
print(df)
但事实证明它提取了页面的第二个表。不是默认提取页面中的第一个表吗?
查阅官方资料得知extract_table()返回的是页面最大的表。如果多个表格具有相同的大小(以单元格数衡量),则返回最靠近页面顶部的表格。
5、提取图片信息
接下来,我们将为提取实验准备一个带有图片的PDF文件。
根据官方提示,如果要进行可视化操作,需要安装两个依赖工具。(另外作者发现还有其他库可以提取PDF的图像元素或者将PDF页面转换成图像,比如PyMuPDF、pdf2image等,可以扩展学习)
安装地址如下,注意选择python对应的版本。
/script/download.php#windows /download/gsdnld.html
图片参数列表可以通过页面对象的images属性获取。这里我们只有一张图片,获取其图片流数据写入文件,成功提取图片。
import pdfplumber
pdf_pic = pdfplumber.open("picture.pdf")
page = pdf_pic.pages[0]
img = page.images
data = img[0]['stream'].get_data()
with open('pic.png', 'wb') as f:
f.write(data)
同时,我们也可以验证提取图片中的文字和表格,发现是不可行的。
print(page.extract_text())
print(page.extract_table())
我是文字无6、 页面转图片
除了提取页面中的图片元素外,还可以将整个页面转换成图片输出。让我们换回原创 PDF 进行实验。page对象有to_image方法,比单个元素的操作简单。
import pdfplumber
pdf = pdfplumber.open("test.pdf")
pages = pdf.pages
for i, p in enumerate(pages):
p.to_image().save(f"第{i+1}页.png", format="PNG")
之后,我们还可以将多张图片连接到增长图表,适合移动查看。
from os import listdir
from PIL import Image
imgs = [Image.open(f) for f in listdir('.') if f.endswith('.png')] # 获取当前目录下的图像
width, height = imgs[0].size # 单幅图像尺寸
result = Image.new(imgs[0].mode, (width, height * len(imgs))) # 创建空白长图
for i, im in enumerate(imgs): # 拼接图片
result.paste(im, box=(0, i * height))
result.save('长条图.png')
本文通过一个实战案例和6个小操作演示了两个PDF库的基本功能,还有很多隐藏属性等着我们去探索,比如加水印、加解密等,还不快来自己开发一个PDF工具来收费?致富指日可待!
本文版权归原作者所有。如果您对内容的版权有任何疑问,请与我联系。本文仅供交流学习之用。
我的热门文章,也许你会感兴趣:
学好Python,这篇文章就够了(入门|基础|进阶|实战)-知乎()
神器pypandoc-实现电子书的自由-知乎()
半夜用Python爬取严选的文胸数据,发现一个惊天秘密-知乎()
为了打击不公,我用Python爬取了选中的男性内衣数据,结果...-知乎 ()
我的热门回答,也许你可以看看:
你用 Python 写过哪些有趣的脚本?-知乎 ()
哪些 Python 库让你们迟到了彼此讨厌?-知乎 ()
你们都用python做什么(全职程序员除外)?-知乎 ()
Python 新手如何快速入门?-知乎 ()