[英]How to extract table data from PDF?
我的目標是將.pdf
文件處理為 memory。 問題是 output 忽略表,結果是串聯字符串。
使用的庫: https://github.com/ledongthuc/pdf
編碼
package main
import (
"bytes"
"fmt"
"github.com/ledongthuc/pdf"
)
func main() {
pdf.DebugOn = true
content, err := readPdf("accountnumberJul2022.pdf") // Read local pdf file
if err != nil {
panic(err)
}
fmt.Println(content)
return
}
func readPdf(path string) (string, error) {
f, r, err := pdf.Open(path)
// remember close file
defer f.Close()
if err != nil {
return "", err
}
var buf bytes.Buffer
b, err := r.GetPlainText()
if err != nil {
return "", err
}
buf.ReadFrom(b)
return buf.String(), nil
}
PDF 文件: https://drive.google.com/file/d/14RFll7pZ8_J8ua-NDrw31QHe-4N16IJL/view?usp=sharing
output
DATEDESCRIPTIONBRENTRYBALANCE01/07Beginning Balance1,000.0002/07TRSFE-BANKINGDB0207/DBXOA/SB24313/Q0321XXXXX56LAWSON1 DB999.0003/07TRSFE-BANKINGDB0307/DBXOA/SB24313/Q0321XXXXX56LAWSON2 DB997.0004/07TRSFE-BANKINGDB0407/DBXOA/SB24313/Q0321XXXXX56LAWSON3 DB994.0005/07TRSFE-BANKINGDB0507/DBXOA/SB24313/Q0321XXXXX56LAWSON4 DB990.0006/07TRSFE-BANKINGDB0607/DBXOA/SB24313/Q0321XXXXX56LAWSON5 DB985.0007/07TRSFE-BANKINGDB0707/DBXOA/SB24313/Q0321XXXXX56LAWSON6 DB979.0008/07TRSFE-BANKINGDB0807/DBXOA/SB24313/Q0321XXXXX56LAWSON7 DB972.0009/07TRSFE-BANKINGDB0907/DBXOA/SB24313/Q0321XXXXX56LAWSON8 DB964.00Continued on next pageDATEDESCRIPTIONBRENTRYBALANCE10/07TRSFE-BANKINGDB1007/DBXOA/SB24313/Q0321XXXXX56LAWSON9 DB955.0011/07TRSFE-BANKINGDB1107/DBXOA/SB24313/Q0321XXXXX56LAWSON10 DB945.0012/07TRSFE-BANKINGDB1207/DBXOA/SB24313/Q0321XXXXX56LAWSON11 DB934.0013/07TRSFE-BANKINGDB1307/DBXOA/SB24313/Q0321XXXXX56LAWSON12 DB922.0014/07TRSFE-BANKINGDB1407/DBXOA/SB24313/Q0321XXXXX56LAWSON13 DB909.0015/07TRSFE-BANKINGDB1507/DBXOA/SB24313/Q0321XXXXX56LAWSON14 DB895.0016/07INTEREST1517/07INTERESTTAX1909.00
我試過的:
fmt.Println(word.S)
更改為fmt.Print(word.S)
但是,output 更不可讀
>>>> row: 0
ATEDESCRIPTIONBRENTRYBALANCEBe00.ginning Balance1,000.00469NOSWTRSFE-BANKINGDB0207/DBXOA/SB24313/Q0321XXXXX56LAWSONAL659X99.00XXXXXTRSFE-BANKINGDB0307/DBXOA/SB24313/Q0321XXXXX56LAWSON1230997.00Q/313TRSFE-BANKINGDB0407/DBXOA/SB24313/Q0321XXXXX56LAWSON42BS994.00/AOXBTRSFE-BANKINGDB0507/DBXOA/SB24313/Q0321XXXXD56LAWSOND/70990.0090BDGTRSFE-BANKINGDB0607/DBXOA/SB24313/Q0321XXXXX56LAWSONNIKN985.00AB-EFTRSFE-BANKINGDB0707/DBXOA/SB24313/Q0321XXXXX56LAWSONSRT0979.000.279TRSFE-BANKINGDB0807/DBXOA/SB24313/Q0321XXXXX56LAWSON701/70080009/0770270/70/0770/6003/070/507/4/03 DBBD 2B4 DB5D 1 DB68 DB DBBD 7oontinued Cn next page>>>> row: 0
TATEDESCRIPTIONBRENTRYBALANCE00.90TRSFE-BANKINGDB1007/DBXOA/SB24313/Q0321XXXXX56LAWSON9XAT955.00TSERETRSFE-BANKINGDB1107/DBXOA/SB24313/Q0321XXXXX56LAWSONTNITS945.00ERETNTRSFE-BANKINGDB1207/DBXOA/SB24313/Q0321XXXXX56LAWSONI00.5934.0098NO/DSRSFE-BANKINGDB1307/DBXOA/SB24313/Q0321XXXXX56LAWSONWAL65922.00XXXXXTRSFE-BANKINGDB1407DBXOA/SB24313/Q0321XXXXX56LAWSON1230Q909.00/3134TRSFE-BANKINGDB1507/DBXOA/SB20/5110/0770/410707/60170//7137712/107/1141B3 BDBDDBD 1 0112 BD 11D9B 511
PDF 文件在設計上並不意味着機器可讀。 PDF 以及它們的結構可能會因人而異。 所以我懷疑是否會有任何“可靠的解決方案”來解析任意 PDF 文件。 PDF 文件不需要像原始電子表格文件那樣“結構化”,PDF 可能來自該文件。 它更像矢量圖形,因為它只包含對 plot 字符的位置和命令,而不是包含文本本身。
在您的情況下,您的特定 PDF 文件似乎結構良好。 使用qpdf提取內容表明:
# part of the pdf content extracted, comments (#) added by me.
BT
/F4 14.666667 Tf
1 0 0 -1 0 .47981739 Tm
0 -13.2773438 Td <0027> Tj # D
10.5842743 0 Td <0024> Tj # A
8.6870575 0 Td <0037> Tj # T
8.9526215 0 Td <0028> Tj # E
ET
Q
Q
q
147.75 87.296265 149.25 23.148926 re
W* n
q
.75 0 0 .75 152.25 92.546265 cm
/G3 gs
BT
/F4 14.666667 Tf
1 0 0 -1 0 .47981739 Tm
0 -13.2773438 Td <0027> Tj # D
10.5842743 0 Td <0028> Tj # E
9.7756042 0 Td <0036> Tj # S
9.7756042 0 Td <0026> Tj # C
10.5842743 0 Td <0035> Tj # R
10.5842743 0 Td <002C> Tj # I
4.0719757 0 Td <0033> Tj # P
9.7756042 0 Td <0037> Tj # T
8.9526215 0 Td <002C> Tj # I
4.0719757 0 Td <0032> Tj # O
11.4001007 0 Td <0031> Tj # N
ET
# some part skipped......
BT
/F4 14.666667 Tf
1 0 0 -1 0 .47981739 Tm
0 -13.2773438 Td <0037> Tj # T
8.9526215 0 Td <0035> Tj # R
10.5842743 0 Td <0036> Tj # S
9.7756042 0 Td <0029> Tj # F
ET
Q
q
.75 0 0 .75 152.25 152.993042 cm
/G3 gs
BT
/F4 14.666667 Tf
1 0 0 -1 0 .47981739 Tm
0 -13.2773438 Td <0028> Tj # E
9.7756042 0 Td <0010> Tj # -
4.8806458 0 Td <0025> Tj # B
9.7756042 0 Td <0024> Tj # A
9.7756042 0 Td <0031> Tj # N
10.5842743 0 Td <002E> Tj # K
9.7756042 0 Td <002C> Tj # I
4.0719757 0 Td <0031> Tj # N
10.5842743 0 Td <002A> Tj # G
ET
Q
q
.75 0 0 .75 152.25 165.641968 cm
/G3 gs
BT
/F4 14.666667 Tf
1 0 0 -1 0 .47981739 Tm
0 -13.2773438 Td <0027> Tj # D
10.5842743 0 Td <0025> Tj # B
ET
BT = 開始文本
ET = 結束文本
編寫一個類似於您使用的庫的程序https://github.com/ledongthuc/pdf或直接修改庫以將一對 BT 和 ET 之間的任何內容解析為單個文本應該是微不足道的。 困難的部分是恢復電子表格的列和行信息(即哪個文本屬於哪個字段)。 因為在 PDF 閱讀器的眼中,電子表格行只是一堆隨機行,也可能是隨機矩形。
這是我編寫的一個演示程序,它首先找到所有矩形,然后將所有文本放入相應的矩形中,然后對每個字段中的文本進行排序和連接以形成最終結果。
func readPdf(path string) {
r, err := pdf.Open(path)
panic(err)
// extract all rectangles
var fieldRects []FieldRect
p := r.Page(1)
c := p.Content()
// font := p.Font(p.Fonts()[0])
// fmt.Printf("font.Widths(): %v\n", font.Widths())
for _, r := range c.Rect {
fieldRects = append(fieldRects, FieldRect{
rect: r,
texts: nil,
})
}
// put text(glyph) into their corresponding rectangles
for _, t := range c.Text {
for i := range fieldRects {
fr := &fieldRects[i]
if fr.rect.Min.X < t.X && fr.rect.Min.Y < t.Y &&
fr.rect.Max.X > t.X && fr.rect.Max.Y > t.Y {
fr.texts = append(fr.texts, t)
}
}
}
// these values can also be derived from font size to gain
// even more robustness
const NEWLINE_TOLERATION = 2
// unfortunately the pdf you sent does not have proper font
// width information, so this is the best we can get without
// inferring width information from the glyph shape itself.
const SPACE_TOLERATION = 11
// sort text(glyph) by position within rectangles, then concat
for i := range fieldRects {
fr := &fieldRects[i]
sort.Slice(fr.texts, func(i, j int) bool {
deltaY := fr.texts[i].Y - fr.texts[j].Y
if math.Abs(deltaY) < NEWLINE_TOLERATION { // tolerate some vertical deviation
return fr.texts[i].X < fr.texts[j].X // on the same line
}
return deltaY > 0 // not on the same line
})
for _, f := range fr.texts {
if fr.lastPos != nil {
if fr.lastPos.Y-f.Y > NEWLINE_TOLERATION { // new line
fr.resultText += "\n"
}
if f.X-fr.lastPos.X > SPACE_TOLERATION { // space
fr.resultText += " "
}
}
fr.resultText += f.S
fr.lastPos = &pdf.Point{X: f.X, Y: f.Y}
}
if fr.resultText == "" {
continue
}
fmt.Printf("====== pos: %v, %v; text: \n%s\n", fr.rect.Min, fr.rect.Max, fr.resultText)
}
}
由於您發送的 PDF 文件中缺少字體寬度信息,因此沒有簡單的方法來實現可靠的空間檢測。 這個程序產生了一個有點可讀但不是很好的結果:
====== pos: {0 0}, {794 1123}; text:
DATE DESCRIPTION BR ENTRY BALANCE
01/07 Beginning Balance 1,000.00
02/07 TRSF 0207/DBXO 1 DB 999.00
E-BANKING A/SB24313/
DB Q0321XXXX
X56
LAWSON
03/07 TRSF 0307/DBXO 2 DB 997.00
E-BANKING A/SB24313/
DB Q0321XXXX
X56
LAWSON
04/07 TRSF 0407/DBXO 3 DB 994.00
E-BANKING A/SB24313/
DB Q0321XXXX
X56
LAWSON
05/07 TRSF 0507/DBXO 4 DB 990.00
E-BANKING A/SB24313/
DB Q0321XXXX
X56
LAWSON
06/07 TRSF 0607/DBXO 5 DB 985.00
E-BANKING A/SB24313/
DB Q0321XXXX
X56
LAWSON
07/07 TRSF 0707/DBXO 6 DB 979.00
E-BANKING A/SB24313/
DB Q0321XXXX
X56
LAWSON
08/07 TRSF 0807/DBXO 7 DB 972.00
E-BANKING A/SB24313/
DB Q0321XXXX
X56
LAWSON
09/07 TRSF 0907/DBXO 8 DB 964.00
E-BANKING A/SB24313/
DB Q0321XXXX
X56
LAWSON
Continued on next page
====== pos: {372.75 87.296265}, {447 110.44519100000001}; text:
Continue
====== pos: {447.75 87.296265}, {522 110.44519100000001}; text:
d on next page
====== pos: {147.75 111.19519}, {297 134.34411599999999}; text:
X56
LAWSON
====== pos: {147.75 135.094116}, {222 183.540893}; text:
TRSF
E-BANKING
DB
====== pos: {222.75 135.094116}, {297 208.83874500000002}; text:
X56
LAWSON
0907/DBXO
A/SB24313/
Q0321XXXX
====== pos: {147.75 209.58875}, {222 258.035527}; text:
TRSF
E-BANKING
DB
====== pos: {222.75 209.58875}, {297 283.33337900000004}; text:
X56
LAWSON
0807/DBXO
A/SB24313/
Q0321XXXX
====== pos: {147.75 284.08337}, {222 332.530147}; text:
TRSF
E-BANKING
DB
====== pos: {222.75 284.08337}, {297 357.827999}; text:
X56
LAWSON
0707/DBXO
A/SB24313/
Q0321XXXX
====== pos: {147.75 358.578}, {222 407.024777}; text:
TRSF
E-BANKING
DB
====== pos: {222.75 358.578}, {297 432.322629}; text:
X56
LAWSON
0607/DBXO
A/SB24313/
Q0321XXXX
====== pos: {147.75 433.07263}, {222 481.519407}; text:
TRSF
E-BANKING
DB
====== pos: {222.75 433.07263}, {297 506.81725900000004}; text:
X56
LAWSON
0507/DBXO
A/SB24313/
Q0321XXXX
====== pos: {147.75 507.56726}, {222 556.0140369999999}; text:
TRSF
E-BANKING
DB
====== pos: {222.75 507.56726}, {297 581.311889}; text:
X56
LAWSON
0407/DBXO
A/SB24313/
Q0321XXXX
====== pos: {147.75 582.06189}, {222 630.508667}; text:
TRSF
E-BANKING
DB
====== pos: {222.75 582.06189}, {297 655.806519}; text:
X56
LAWSON
0307/DBXO
A/SB24313/
Q0321XXXX
====== pos: {147.75 656.55652}, {222 705.003297}; text:
TRSF
E-BANKING
DB
====== pos: {222.75 656.55652}, {297 730.301149}; text:
nce
0207/DBXO
A/SB24313/
Q0321XXXX
另一個不能正常工作的原因是從文檔中提取的矩形與 PDF 閱讀器中單元格的外觀不匹配。
事實上,我相信如果你能找到一種方法來找到所有單元格並將文檔頁面划分為正確定位和大小的單元格,這幾乎可以完美地工作。 (手動或自動)。 並且也許使用將一對 BT 和 ET 之間的任何內容視為單個文本的技巧,而不是依賴字形之間的相對位置來做到這一點。
但請記住,即使您設法做到了,這仍然只適用於這種特定格式和(因為沒有更好的詞)由這個特定軟件創建的 PDF 的“風味”,並且不太可能在還要別的嗎。
還有可用的商業解決方案,例如https://www.ilovepdf.com/pdf_to_excel背后的框架,我發現它非常適合您的特定文檔。 這些解決方案往往更加強大和可靠,但確實要花錢購買。 其中一些可以在線使用,所以也許想辦法使用他們的 api 可能是一個可行的選擇。 (如果他們的服務條款允許您這樣做,那就是。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.