37

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

 4 years ago
source link: https://www.tuicool.com/articles/meMFrei
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Vj6R3qb.jpg!web

标星★公众号      爱你们

量化投资与机器学习编辑部报道

近期原创文章:

♥  5种机器学习算法在预测股价的应用(代码+数据)

♥  Two Sigma用新闻来预测股价走势,带你吊打Kaggle

 2万字干货: 利用深度学习最新前沿预测股价走势

♥  机器学习在量化金融领域的误用!

♥  基于RNN和LSTM的股市预测方法

♥  如何鉴别那些用深度学习预测股价的花哨模型?

♥  优化强化学习Q-learning算法进行股市

♥  WorldQuant 101 Alpha、国泰君安 191 Alpha

♥  基于回声状态网络预测股票价格(附代码)

♥  计量经济学应用投资失败的7个原因

♥  配对交易千千万,强化学习最NB!(文档+代码)

♥  关于高盛在Github开源背后的真相!

♥  新一代量化带货王诞生!Oh My God!

♥  独家!关于定量/交易求职分享(附真实试题)

♥  Quant们的身份危机!

♥  AQR最新研究 | 机器能“学习”金融吗?

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

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

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

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

PDFPlumbe

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

M7zIzij.jpg!web

获取全部代码,见文末

关于 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 ),说明如下:

zE77Z3E.jpg!web

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

vUneeaz.jpg!web

使用 .extract_table 获取数据

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

bAJ3mqv.png!web

使用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(" ", "")

uaYRfyi.jpg!web

大功告成!

具体是如何产生的呢?

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

ZJNJV3A.jpg!web

案例二:从PDF中提取图形数据

import pdfplumber
report = pdfplumber.open("../pdfs/ag-energy-round-up-2017-02-24.pdf").pages[0]
im = report.to_image()
im

AbeiA3a.jpg!web

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

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

QB77jay.jpg!web

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

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

FNrU73m.jpg!web

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

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

zUJBz2n.jpg!web

import pdfplumber

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

FvaUZjM.jpg!web

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

im.reset().debug_tablefinder()

A7Vbmur.jpg!web

默认设置正确地标识了表的垂直边界,但是没有捕获每组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)

eEZ73qR.jpg!web

table = p0.extract_table(table_settings)

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

AVFZ73U.jpg!web

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

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

IJzqeeF.png!web

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]

vaArIf7.jpg!web

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

JzMF3qq.jpg!web

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

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

YnaUBfJ.jpg!web

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

text = p0.extract_text()
print(text)

iY7r6bj.jpg!web

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

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

EBrYZ3V.jpg!web

在这份报告中,每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]

uAfMfmq.jpg!web

通过 DataFrame进行展示:

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

nQZ3IbB.jpg!web

获取代码

后台 输入 (严格大小写)

Pdfplumber_文件

—End—

量化投资与机器学习微信公众号,是业内垂直于 Quant MFE CST、AI 等专业的 流量化自媒体 。公众号拥有来自 公募、私募、券商、银行、海外 等众多圈内 18W+ 关注者。每日发布行业前沿研究成果和最新量化资讯。

你点的每个“在看”,都是对我们最大的鼓励


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK