网页表格抓取( 还在用收费的工具处理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 新手如何快速入门?-知乎 ()

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线