부록 1: 텍스트 추출에 대한 세부사항¶
이 장은 PyMuPDF 의 텍스트 추출 메서드에 대한 배경 정보를 제공합니다.
관심 있는 정보는 다음과 같습니다
그들이 무엇을 제공하는가?
그들이 무엇을 의미하는가(처리 시간 / 데이터 크기)?
TextPage의 일반적인 구조¶
TextPage 는 (Py-) MuPDF 의 클래스 중 하나입니다. 일반적으로 Page 텍스트 추출 메서드가 사용될 때 배후에서 생성(그리고 다시 파괴)되지만, 직접 사용할 수도 있으며 영구 객체로 사용할 수 있습니다. 이름이 시사하는 것과 달리, 이미지는 선택적으로 텍스트 페이지의 일부일 수도 있습니다:
<page>
<text block>
<line>
<span>
<char>
<image block>
<img>
텍스트 페이지 는 블록(= 대략 단락)으로 구성됩니다.
블록 은 행과 그 문자, 또는 이미지로 구성됩니다.
행 은 스팬으로 구성됩니다.
스팬 은 동일한 글꼴 속성(이름, 크기, 플래그 및 색상)을 가진 인접한 문자로 구성됩니다.
일반 텍스트¶
함수 TextPage.extractText() (또는 Page.get_text(“text”)) 는 문서 작성자가 지정한 대로 페이지의 일반 텍스트를 원래 순서대로 추출합니다.
예제 출력:
>>> print(page.get_text("text"))
Some text on first page.
참고
출력이 익숙한 “자연스러운” 읽기 순서와 같지 않을 수 있습니다. 그러나 page.get_text("text", sort=True) 를 실행하여 “왼쪽 위에서 오른쪽 아래로” 체계에 따라 재정렬을 요청할 수 있습니다.
BLOCKS¶
함수 TextPage.extractBLOCKS() (또는 Page.get_text(“blocks”)) 는 페이지의 텍스트 블록을 다음과 같은 항목 목록으로 추출합니다:
(x0, y0, x1, y1, "lines in block", block_no, block_type)
여기서 처음 4개 항목은 블록의 bbox의 부동 소수점 좌표입니다. 각 블록 내의 행은 줄바꿈 문자로 연결됩니다.
이것은 고속 메서드이며, 기본적으로 이미지 메타 정보도 추출합니다: 각 이미지는 메타 정보를 포함하는 하나의 텍스트 행이 있는 블록으로 나타납니다. 이미지 자체는 표시되지 않습니다.
위의 간단한 텍스트 출력과 마찬가지로 sort 인수를 사용하여 읽기 순서를 얻을 수 있습니다.
예제 출력:
>>> print(page.get_text("blocks", sort=False))
[(50.0, 88.17500305175781, 166.1709747314453, 103.28900146484375,
'Some text on first page.', 0, 0)]
WORDS¶
함수 TextPage.extractWORDS() (또는 Page.get_text(“words”)) 는 페이지의 텍스트 단어 를 다음과 같은 항목 목록으로 추출합니다:
(x0, y0, x1, y1, "word", block_no, line_no, word_no)
여기서 처음 4개 항목은 단어의 bbox의 부동 소수점 좌표입니다. 마지막 세 정수는 단어의 위치에 대한 추가 정보를 제공합니다.
이것은 고속 메서드입니다. 이전 메서드와 마찬가지로 인수 sort=True 는 단어를 재정렬합니다.
예제 출력:
>>> for word in page.get_text("words", sort=False):
print(word)
(50.0, 88.17500305175781, 78.73200225830078, 103.28900146484375,
'Some', 0, 0, 0)
(81.79000091552734, 88.17500305175781, 99.5219955444336, 103.28900146484375,
'text', 0, 0, 1)
(102.57999420166016, 88.17500305175781, 114.8119888305664, 103.28900146484375,
'on', 0, 0, 2)
(117.86998748779297, 88.17500305175781, 135.5909881591797, 103.28900146484375,
'first', 0, 0, 3)
(138.64898681640625, 88.17500305175781, 166.1709747314453, 103.28900146484375,
'page.', 0, 0, 4)
HTML¶
TextPage.extractHTML() (또는 Page.get_text(“html”) 출력은 페이지의 TextPage 구조를 완전히 반영합니다 – 아래의 DICT / JSON과 매우 유사합니다. 이것은 이미지, 글꼴 정보 및 텍스트 위치를 포함합니다. HTML 헤더 및 트레일러 코드로 감싸면 인터넷 브라우저에서 쉽게 표시할 수 있습니다. 위의 예제:
>>> for line in page.get_text("html").splitlines():
print(line)
<div id="page0" style="position:relative;width:300pt;height:350pt;
background-color:white">
<p style="position:absolute;white-space:pre;margin:0;padding:0;top:88pt;
left:50pt"><span style="font-family:Helvetica,sans-serif;
font-size:11pt">Some text on first page.</span></p>
</div>
HTML 출력 품질 제어¶
MuPDF v1.12.0에서 HTML 출력이 많이 개선되었지만, 아직 버그가 없지는 않습니다: 글꼴 지원 및 이미지 위치 지정 영역에서 문제를 발견했습니다.
HTML 텍스트에는 원본 문서에서 사용된 글꼴에 대한 참조가 포함되어 있습니다. 브라우저가 이를 알지 못하면(가능성은 낮지만!) 다른 것으로 대체할 수 있으며, 결과가 어색해 보일 수 있습니다. 이 문제는 브라우저에 따라 크게 다릅니다 – Windows 머신에서 MS Edge는 잘 작동했지만 Firefox는 형편없이 보였습니다.
복잡한 구조를 가진 PDF의 경우 이미지가 올바르게 위치 지정되거나 크기 조정되지 않을 수 있습니다. 이것은 회전된 페이지와 다양한 가능한 페이지 bbox 변형이 일치하지 않는 페이지(예: MediaBox != CropBox)의 경우인 것 같습니다. 이것을 해결하는 방법은 아직 알지 못합니다 – MuPDF 사이트에 버그를 제출했습니다.
글꼴 문제를 해결하기 위해 HTML 파일을 스캔하고 글꼴 참조를 교체하는 간단한 유틸리티 스크립트를 사용할 수 있습니다. 다음은 모든 글꼴을 PDF Base 14 글꼴 중 하나로 교체하는 작은 예제입니다: 세리프 글꼴은 “Times”가 되고, 비세리프 글꼴은 “Helvetica”가 되며, 고정폭 글꼴은 “Courier”가 됩니다. “bold”, “italic” 등에 대한 각각의 변형은 브라우저에서 올바르게 처리되기를 바랍니다:
import sys
filename = sys.argv[1]
otext = open(filename).read() # original html text string
pos1 = 0 # search start poition
font_serif = "font-family:Times" # enter ...
font_sans = "font-family:Helvetica" # ... your choices ...
font_mono = "font-family:Courier" # ... here
found_one = False # true if search successful
while True:
pos0 = otext.find("font-family:", pos1) # start of a font spec
if pos0 < 0: # none found - we are done
break
pos1 = otext.find(";", pos0) # end of font spec
test = otext[pos0 : pos1] # complete font spec string
testn = "" # the new font spec string
if test.endswith(",serif"): # font with serifs?
testn = font_serif # use Times instead
elif test.endswith(",sans-serif"): # sans serifs font?
testn = font_sans # use Helvetica
elif test.endswith(",monospace"): # monospaced font?
testn = font_mono # becomes Courier
if testn != "": # any of the above found?
otext = otext.replace(test, testn) # change the source
found_one = True
pos1 = 0 # start over
if found_one:
ofile = open(filename + ".html", "w")
ofile.write(otext)
ofile.close()
else:
print("Warning: could not find any font specs!")
DICT (또는 JSON)¶
TextPage.extractDICT() (또는 Page.get_text(“dict”, sort=False)) 출력은 TextPage 구조를 완전히 반영하며 모든 블록, 행 및 스팬에 대한 이미지 콘텐츠 및 위치 세부 정보(bbox – 픽셀 단위의 경계 상자)를 제공합니다. 이미지는 DICT 출력의 경우 bytes 로 저장되고 JSON 출력의 경우 base64 인코딩된 문자열로 저장됩니다.
딕셔너리 구조의 시각화는 딕셔너리 출력 구조 를 참조하세요.
다음은 이것이 어떻게 보이는지입니다:
{
"width": 300.0,
"height": 350.0,
"blocks": [{
"type": 0,
"bbox": (50.0, 88.17500305175781, 166.1709747314453, 103.28900146484375),
"lines": ({
"wmode": 0,
"dir": (1.0, 0.0),
"bbox": (50.0, 88.17500305175781, 166.1709747314453, 103.28900146484375),
"spans": ({
"size": 11.0,
"flags": 0,
"font": "Helvetica",
"color": 0,
"origin": (50.0, 100.0),
"text": "Some text on first page.",
"bbox": (50.0, 88.17500305175781, 166.1709747314453, 103.28900146484375)
})
}]
}]
}
RAWDICT (또는 RAWJSON)¶
TextPage.extractRAWDICT() (또는 Page.get_text(“rawdict”, sort=False)) 는 DICT의 정보 상위 집합 이며 세부 수준을 한 단계 더 깊게 가져갑니다. 위와 정확히 같아 보이지만, 스팬의 “text” 항목(string)이 “chars” 목록으로 대체됩니다. 각 “chars” 항목은 문자 dict 입니다. 예를 들어, 위의 “text”: “Text in black color.” 항목 대신 다음과 같은 것을 볼 수 있습니다:
"chars": [{
"origin": (50.0, 100.0),
"bbox": (50.0, 88.17500305175781, 57.336997985839844, 103.28900146484375),
"c": "S"
}, {
"origin": (57.33700180053711, 100.0),
"bbox": (57.33700180053711, 88.17500305175781, 63.4530029296875, 103.28900146484375),
"c": "o"
}, {
"origin": (63.4530029296875, 100.0),
"bbox": (63.4530029296875, 88.17500305175781, 72.61600494384766, 103.28900146484375),
"c": "m"
}, {
"origin": (72.61600494384766, 100.0),
"bbox": (72.61600494384766, 88.17500305175781, 78.73200225830078, 103.28900146484375),
"c": "e"
}, {
"origin": (78.73200225830078, 100.0),
"bbox": (78.73200225830078, 88.17500305175781, 81.79000091552734, 103.28900146484375),
"c": " "
< ... deleted ... >
}, {
"origin": (163.11297607421875, 100.0),
"bbox": (163.11297607421875, 88.17500305175781, 166.1709747314453, 103.28900146484375),
"c": "."
}],
XML¶
TextPage.extractXML() (또는 Page.get_text(“xml”)) 버전은 RAWDICT의 세부 수준으로 텍스트(이미지 없음)를 추출합니다:
>>> for line in page.get_text("xml").splitlines():
print(line)
<page id="page0" width="300" height="350">
<block bbox="50 88.175 166.17098 103.289">
<line bbox="50 88.175 166.17098 103.289" wmode="0" dir="1 0">
<font name="Helvetica" size="11">
<char quad="50 88.175 57.336999 88.175 50 103.289 57.336999 103.289" x="50"
y="100" color="#000000" c="S"/>
<char quad="57.337 88.175 63.453004 88.175 57.337 103.289 63.453004 103.289" x="57.337"
y="100" color="#000000" c="o"/>
<char quad="63.453004 88.175 72.616008 88.175 63.453004 103.289 72.616008 103.289" x="63.453004"
y="100" color="#000000" c="m"/>
<char quad="72.616008 88.175 78.732 88.175 72.616008 103.289 78.732 103.289" x="72.616008"
y="100" color="#000000" c="e"/>
<char quad="78.732 88.175 81.79 88.175 78.732 103.289 81.79 103.289" x="78.732"
y="100" color="#000000" c=" "/>
... deleted ...
<char quad="163.11298 88.175 166.17098 88.175 163.11298 103.289 166.17098 103.289" x="163.11298"
y="100" color="#000000" c="."/>
</font>
</line>
</block>
</page>
참고
이 출력을 해석하기 위해 lxml 를 성공적으로 테스트했습니다.
XHTML¶
TextPage.extractXHTML() (또는 Page.get_text(“xhtml”)) 는 TEXT의 변형이지만 HTML 형식이며, 일반 텍스트와 이미지를 포함합니다(“의미론적” 출력):
<div id="page0">
<p>Some text on first page.</p>
</div>
텍스트 추출 플래그 기본값¶
버전 1.16.2의 새로운 기능: 메서드
Page.get_text()는 추출된 데이터의 양과 품질을 제어하는 키워드 매개변수 flags (int) 를 지원합니다. 다음 표는 각 추출 변형에 대한 기본 설정(플래그 매개변수 생략 또는 None)을 보여줍니다.None이 아닌 값으로 플래그를 지정하는 경우 모든 원하는 옵션을 설정해야 합니다. 각 비트 설정에 대한 설명은 글꼴 속성 에서 찾을 수 있습니다.v1.19.6의 새로운 기능: 다음 표의 기본 조합은 이제 Python 상수로 사용할 수 있습니다:
TEXTFLAGS_TEXT,TEXTFLAGS_WORDS,TEXTFLAGS_BLOCKS,TEXTFLAGS_DICT,TEXTFLAGS_RAWDICT,TEXTFLAGS_HTML,TEXTFLAGS_XHTML,TEXTFLAGS_XML, 및TEXTFLAGS_SEARCH. 이제 기본 플래그를 쉽게 수정할 수 있습니다. 예를 들어:“blocks” 출력에 이미지를 포함:
flags = TEXTFLAGS_BLOCKS | TEXT_PRESERVE_IMAGES“dict” 출력에서 이미지를 제외:
flags = TEXTFLAGS_DICT & ~TEXT_PRESERVE_IMAGES텍스트 검색에서 하이픈 제거를 끔:
flags = TEXTFLAGS_SEARCH & ~TEXT_DEHYPHENATE
표시기 |
text |
html |
xhtml |
xml |
dict |
rawdict |
words |
blocks |
search |
|---|---|---|---|---|---|---|---|---|---|
합자 유지 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
0 |
공백 유지 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
이미지 유지 |
해당 없음 |
1 |
1 |
해당 없음 |
1 |
1 |
해당 없음 |
0 |
0 |
공백 억제 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
하이픈 제거 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
미디어박스로 자르기 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
U+FFFD 대신 CID 사용 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
0 |
search 는 텍스트 검색 함수를 나타냅니다.
“json” 은 “dict” 와 정확히 동일하게 처리되므로 생략되었습니다.
“rawjson” 은 “rawdict” 와 정확히 동일하게 처리되므로 생략되었습니다.
“n/a” 지정은 값이 0임을 의미하며 이 비트를 설정해도 출력에 영향을 주지 않습니다(하지만 성능에 부정적인 영향을 미칩니다).
기본적으로 이미지를 포함하는 출력 변형을 사용할 때 이미지에 관심이 없다면, 반드시 해당 비트를 끄세요: 더 나은 성능과 훨씬 낮은 공간 요구 사항을 경험할 수 있습니다.
TEXT_INHIBIT_SPACES 의 효과를 보려면 이 예제를 참조하세요:
>>> print(page.get_text("text"))
H a l l o !
Mo r e t e x t
i s f o l l o w i n g
i n E n g l i s h
. . . l e t ' s s e e
w h a t h a p p e n s .
>>> print(page.get_text("text", flags=pymupdf.TEXT_INHIBIT_SPACES))
Hallo!
More text
is following
in English
... let's see
what happens.
>>>
성능¶
텍스트 추출 메서드는 제공하는 정보와 리소스 요구 사항 및 실행 시간 측면에서 모두 크게 다릅니다. 일반적으로 더 많은 정보는 더 많은 처리가 필요하고 더 높은 데이터 볼륨이 생성됨을 의미합니다.
참고
특히 이미지는 매우 큰 영향을 미칩니다. 필요하지 않을 때는 반드시 제외하세요(flags 매개변수를 통해). 기본 플래그 설정으로 아래에 언급된 총 2,700페이지를 처리하는 데 모든 추출 메서드에서 160초가 필요했습니다. 모든 이미지가 제외되면 그 시간의 50% 미만(77초)이 필요했습니다.
우선, 모든 메서드는 시장의 다른 제품과 비교하여 매우 빠릅니다. 처리 속도 측면에서 더 빠른(무료) 도구는 알지 못합니다. 가장 상세한 메서드인 RAWDICT조차 Adobe PDF 참조 의 모든 1,310페이지를 5초 미만으로 처리합니다(여기서는 간단한 텍스트가 2초 미만이 필요합니다).
다음 표는 약 1400페이지의 텍스트 중심 페이지와 1300페이지의 이미지 중심 페이지에서 가져온 평균 상대 속도(“RSpeed”, 기준선 1.00은 TEXT)를 보여줍니다.
메서드 |
RSpeed |
설명 |
이미지 없음 |
|---|---|---|---|
TEXT |
1.00 |
이미지 없음, 일반 텍스트, 줄바꿈 |
1.00 |
BLOCKS |
1.00 |
이미지 bbox(만), 블록 수준 텍스트와 bbox, 줄바꿈 |
1.00 |
WORDS |
1.02 |
이미지 없음, 단어 수준 텍스트와 bbox |
1.02 |
XML |
2.72 |
이미지 없음, 문자 수준 텍스트, 레이아웃 및 글꼴 세부 정보 |
2.72 |
XHTML |
3.32 |
base64 이미지, 스팬 수준 텍스트, 레이아웃 정보 없음 |
1.00 |
HTML |
3.54 |
base64 이미지, 스팬 수준 텍스트, 레이아웃 및 글꼴 세부 정보 |
1.01 |
DICT |
3.93 |
바이너리 이미지, 스팬 수준 텍스트, 레이아웃 및 글꼴 세부 정보 |
1.04 |
RAWDICT |
4.50 |
바이너리 이미지, 문자 수준 텍스트, 레이아웃 및 글꼴 세부 정보 |
1.68 |
언급한 대로: 이미지 추출을 제외하면(마지막 열) 상대 속도가 크게 변합니다: RAWDICT와 XML을 제외하고 다른 메서드는 거의 동일하게 빠르며, RAWDICT는 이제 가장 느린 XML 보다 40% 적은 실행 시간이 필요합니다.
더 많은 성능 정보는 부록 1 장을 참조하세요.
