查看原文
其他

手把手教学:提取PDF各种表格文本数据(附代码)

The following article is from 量化投资与机器学习 Author QIML编辑部

点击上方“Python数据科学”,星标公众号

重磅干货,第一时间送达


☞500g+超全学习资源免费领取,干货来袭!


来源:量化投资与机器学习(ID:Lhtz_Jqxx)

本文首发于量化投资与机器学习


还在为抓取各种PDF格式的财务、数据报表而烦恼吗?


还在为自己手工操作导致的效率低下而烦恼吗?


还在担心没有趁手的兵器吗?


今天,公众号为大家介绍一款神器:


PDFPlumbe


轻松玩转PDF,痛快抓数据!助你一臂之力!




获取全部代码,见文末



关于PDFPlumbe

PDFPlumb最适合提取电脑生成的PDF,而不是扫描的PDF。 它是在pdfminer和pdfmine.six基础上设计的。


适用版本: Python2.7、3.1、3.4、3.5和3.6。


安装PDFPlumbe
pip install pdfplumber


要使用pdfplumber的可视化调试工具,还需要在计算机上安装ImageMagick(https://imagemagick.org/index.php),说明如下:


http://docs.wand-py.org/en/latest/guide/install.html#install-imagemagick-debian


具体参数、提取流程与可视化我们将以案例进行展示,更详细的内容,请大家在文末下载安装包自行查看。


案例一
import pdfplumber

pdf = pdfplumber.open("../pdfs/ca-warn-report.pdf")
p0 = pdf.pages[0]
im = p0.to_image()
im



使用 .extract_table 获取数据


table = p0.extract_table()
table[:3]


使用pandas将列表呈现为一个DataFrame,并在某些日期内删除多余的空格。


import pandas as pd
df = pd.DataFrame(table[1:], columns=table[0])
for column in ["Effective", "Received"]:
    df[column] = df[column].str.replace(" ", "")


大功告成!


具体是如何产生的呢?


红线代表pdfplumber在页面上找到的线,蓝色圆圈表示这些线的交叉点,淡蓝色底纹表示从这些交叉点派生的单元格。



案例二:从PDF中提取图形数据
import pdfplumber
report = pdfplumber.open("../pdfs/ag-energy-round-up-2017-02-24.pdf").pages[0]
im = report.to_image()
im



页面对象具有 .curves 属性,该属性包含在页面上找到的一个curve对象列表。本报告包含12条曲线,每图4条:


len(report.curves)
12
report.curves[0]


将它们传递 .draw_lines 确定曲线的位置:


im.draw_lines(report.curves, stroke="red", stroke_width=2)



我们通过循环使用四种颜色的调色板来获得更好的显示感:


im.reset()
colors = [ "gray", "red", "blue", "green" ]
for i, curve in enumerate(report.curves):
    stroke = colors[i%len(colors)]
    im.draw_circles(curve["points"], radius=3, stroke=stroke, fill="white")
    im.draw_line(curve["points"], stroke=stroke, stroke_width=2)
im



案例三
import pdfplumber

pdf = pdfplumber.open("../pdfs/background-checks.pd")
p0 = pdf.pages[0]
im = p0.to_image()
im



使用 PageImage.debug_tablefinder() 来检查表格:


im.reset().debug_tablefinder()

默认设置正确地标识了表的垂直边界,但是没有捕获每组5个states/territories之间的水平边界。所以:


使用自定义 .extract_table :


  • 因为列由行分隔,所以我们使用 vertical_strategy="lines"

  • 因为行主要由文本之间的沟槽分隔,所以我们使用 horizontal_strategy="text"

  • 由于文本的左、右端与竖线不是很齐平,所以我们使用 intersection_tolerance: 15


table_settings = {
    "vertical_strategy": "lines",
    "horizontal_strategy": "text",
    "intersection_x_tolerance": 15
}

im.reset().debug_tablefinder(table_settings)



table = p0.extract_table(table_settings)

for row in table[:5]:
    print(row)




清理数据(页眉页脚等):


core_table = table[3:3+56]
" • ".join(core_table[0])



" • ".join(core_table[-1])



COLUMNS = [
    "state",
    "permit",
    "handgun",
    "long_gun",
    "other",
    "multiple",
    "admin",
    "prepawn_handgun",
    "prepawn_long_gun",
    "prepawn_other",
    "redemption_handgun",
    "redemption_long_gun",
    "redemption_other",
    "returned_handgun",
    "returned_long_gun",
    "returned_other",
    "rentals_handgun",
    "rentals_long_gun",
    "private_sale_handgun",
    "private_sale_long_gun",
    "private_sale_other",
    "return_to_seller_handgun",
    "return_to_seller_long_gun",
    "return_to_seller_other",
    "totals"
]


def parse_value(i, x):
    if i == 0: return x
    if x == "": return None
    return int(x.replace(",", ""))

from collections import OrderedDict
def parse_row(row):
    return OrderedDict((COLUMNS[i], parse_value(i, cell))
        for i, cell in enumerate(row))

data = [ parse_row(row) for row in core_table ]
Now here's the first row, parsed:

data[0]


案例四
import pdfplumber
import re
from collections import OrderedDict

pdf = pdfplumber.open("../pdfs/san-jose-pd-firearm-sample.pdf")
p0 = pdf.pages[0]
im = p0.to_image()
im



我们在pdfplumber检测到的每个 char 对象周围绘制矩形。通过这样做,我们可以看到报表主体的的每一行都有相同的宽度,并且每个字段都填充了空格(“”)字符。这意味着我们可以像解析标准的固定宽度数据文件一样解析这些行。


im.reset().draw_rects(p0.chars)



使用 page .extract_text(…) 方法,逐行抓取页面上的每个字符(文本):


text = p0.extract_text()
print(text)



清理数据(页眉页脚等):


core_pat = re.compile(r"LOCATION[\-\s]+(.*)\n\s+Flags = e", re.DOTALL)
core = re.search(core_pat, text).group(1)
print(core)



在这份报告中,每f一个irearm占了两行。下面的代码将表拆分为two-line,然后根据每个字段中的字符数解析出字段:


lines = core.split("\n")
line_groups = list(zip(lines[::2], lines[1::2]))
print(line_groups[0])



def parse_row(first_line, second_line):
    return OrderedDict([
        ("type", first_line[:20].strip()),
        ("item", first_line[21:41].strip()),
        ("make", first_line[44:89].strip()),
        ("model", first_line[90:105].strip()),
        ("calibre", first_line[106:111].strip()),
        ("status", first_line[112:120].strip()),
        ("flags", first_line[124:129].strip()),
        ("serial_number", second_line[0:13].strip()),
        ("report_tag_number", second_line[21:41].strip()),
        ("case_file_number", second_line[44:64].strip()),
        ("storage_location", second_line[68:91].strip())
    ])

parsed = [ parse_row(first_line, second_line)
    for first_line, second_line in line_groups ]


parsed[:2]


通过DataFrame进行展示:


mport pandas as pd
columns = list(parsed[0].keys())
pd.DataFrame(parsed)[columns]



获取代码

后台输入

20190923



/ 今日留言赠送书籍 /

☞ 混脸熟赠书规则

《机器学习线性代数基础-Python语言描述》

👆扫描上方二维码购买


内容介绍:数学是机器学习绕不开的基础知识,传统教材的风格偏重理论定义和运算技巧,想以此高效地打下机器学习的数学基础,针对性和可读性并不佳。本书以机器学习涉及的线性代数核心知识为重点,进行新的尝试和突破:从坐标与变换、空间与映射、近似与拟合、相似与特征、降维与压缩这5个维度,环环相扣地展开线性代数与机器学习算法紧密结合的核心内容,并分析推荐系统和图像压缩两个实践案例,在介绍完核心概念后,还将线性代数的应用领域向函数空间和复数域中进行拓展与延伸;同时极力避免数学的晦涩枯燥,充分挖掘线性代数的几何内涵,并以Python语言为工具进行数学思想和解决方案的有效实践。 

  《机器学习线性代数基础:Python语言描述》适合实践于数据分析、信号处理等工程领域的读者,也适合在人工智能、机器学习领域进行理论学习和实践,希望筑牢数学基础的读者,以及正在进行线性代数课程学习的读者阅读。



/ 上期中奖者公布 /
恭喜Savages同学上期走心留言中奖,
获取《机器学习线性代数基础-Python语言描述》
书籍一本,请联系小编获取,
微信:lucici1990



推荐阅读

1、80页笔记看遍机器学习基本概念、算法、模型,帮新手少走弯路

2、接私活必备的 10 个开源项目!

3、Python 最强编辑器详细使用指南!

4、技术面试时该反问面试官什么问题?小伙整理了灵魂50问,GitHub日入2500星
5、2019互联网月饼哪家强?阿里走情怀;百度最土豪


👆关注“数据挖掘工程师”

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存