태그 보관물: python

한글 텍스트 추출을 위한 Python PDF module

이 내용은 2020년 3월에 작성된 것으로 참조하는 시점에 따라 변경된 사항들이 있을 수도 있습니다.

PyPDF2

PyPDF2는 PDF file의 metadata 정보를 가져오거나 페이지 단위로 나누거나 합치는 등의 여러가지 편리한 기능들을 제공한다. 하지만 한글을 제대로 추출하지 못하는 문제가 있어서(한글 뿐 아니라 CJK 모두 라고 함) 목적에는 적합하지 않았다.

PDFMiner

한글 처리는 문제 없다. 그런데 페이지 단위로 나누어서 처리하는 것을 따로 지원하지 않아서 원하는 페이지에 접근하려면 순차적으로 처음부터 해당 페이지를 찾아가는 trick을 사용해야 하는데, 이 코드로 순차적 접근을 하면 시간 복잡도가 O(N^2)가 되어 파일의 크기가 조금만 커도 성능이 매우 떨어진다.

Tika

많은 곳에서 쓰이는 꽤나 유명한 프로젝트인데 Python module로도 proting 되어 있다(tika-python). 한글 추출에는 문제가 없고, 이 모듈 자체에서는 페이지 단위의 텍스트 추출을 지원하지 않으나, 그대신 PDF를 XML로 추출한 다음에 BeautifulSoupe로 <div page=””> 태그를 찾아 페이지 단위로 접근하는 신박한 트릭이 있다(StackOverflow). 나는 BeautifulSoup의 paser로 lxml을 사용했다.

주의. 명시적으로 표시 되지는 않지만 JRE(Java Runtime Environment)에 의존하므로 동작시 오류가 발생하면 JRE가 제대로 설치 되어 있고 접근 가능한지 확인해 볼 것. Ubuntu 18.04 default-jre package (OpenJDK 11)로 동작 확인.

결론

PDF 문서 자체에 대한 합치기/나누기/정보 가져오기 등은 PyPDF2가 무척 편하다. 한글 텍스트 추출을 위해서는 Tika, 페이지 단위 접근이 필요하다면 Tika + BS를 고려해 볼 만하다. PDFMiner는 뭐랄까.. 쫌 별로..

[Tip] Emac에서 pdb 사용할 때 UnicodeEncodeError

*** UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position 168-169: ordinal not in range(128)

Emacs에서 shell을 열고 pdb를 실행 할 때 표시하고자 하는 문자열이 ASCII가 아니라면 발생 할 수 있는 문제인데 emacs의 초기화 파일에 unicode locale 설정을 해주는 것으로 해결할 수 있다. 인터넷 문서들 중에는 LANG, LC_LANG, LC_CTYPE 모두를 설정해 주어야 한다는 내용도 있었으나, 내 terminal에서 LC_CTYPE만 설정해서도 잘 동작되고 있으므로 이를 따라 LC_CTYPE만 UTF-8으로 다음과 같이 설정해 주었다.

Init file을 reload하거나 emac를 재 실행해서 ASCII외의 문자들이 잘 표시 되는지 확인해 본다.

그리고 GitHub repository에 업뎃. 🙂

[Tip] Mac version docker에서 띄운 (웹) 서버에 접속하기

Mac version Docker에 띄워둔 web server에 host에서 접속하려면 어떻게 해야 할까? 실행할 때 ‘–network=host’를 주면 된다는 얘기가 있어서 해봤는데, Mac에서는 통하지 않았다 이건 linux용이라고… Networking features in Docker Desktop for Mac에 따라 실행할 때 port를 매핑하는 것으로 이 문제를 해결할 수 있다.

Docker의 9090 port에 Mac의 9090 port로 접근하려면 다음과 같이 -p option으로 port를 매핑해서 docker를 띄운 후 서버를 실행한다.

이제 Mac의 web browser로 해당 포트에 접근할 수 있다.

App engine – 지정한 달의 google calendar event 가져오기

Google calendar API 설명문서에는 event list를 가져오는 방법이 설명과 함께 예제 code가 제시되어 있다.

이 코드를 수행하면 달력이 가지고 있는 모든 event들을 가져오는데, 이것을 조금 수정해서 지정된 달에 해당하는 event 목록을 가져오는 것으로 변경해 보자.

timeMax / timeMin

가져올 event의 범위는 lower bound를 나타내는 timeMin과 upper bound를 나타내는 timeMax값으로 제한할 수 있는데 이때 사용되는 날짜의 형식은 RFC3339을 따라야 한다.

calendar.monthrange() 

이 method는 년도와 월을 받아서 시작일의 주와 마지막 날짜를 tuple로 반환해 준다. 다음과 같이 특정 달의 마지막 날짜를 구할 수 있다.

Python에서 RFC3339 표시하기

이 형식은 간단히 말해 날짜를 나타내는 YYYY-MM-DD 형식과 시간을 나타내는 hh:mm:ss가 대문자 ‘T’로 연결되며 그 뒤에는 UTC로 부터의 time zone offset값이 붙는다.

datetime.isoformat()을 이용하면 비슷한 형식을 얻을 수는 있으나, 뒤에 붙는 time zone offset이 표시되지 않는다.

Python에서 time zone을 나타내는 tzinfo class의 설명문에 따르면 tzinfo는 abstract class여서 직접 사용할 수 없고, 이를 상속받는 class를 구현해서 사용해야 한다. 다음은 이 문서에 있는 예제를 조금 수정하여 time zone offset 값을 반환하는 tzinfo의 concrete class를 구현한 것이다.

Code
TimeZoneOffset class를 선언한 후에는 datetime()을 호출할때 tzinfo에 TimeZoneOffset의 instance를 넘겨서 time zone offset 정보를 포함하는 RFC3339 날짜 형식을 얻을 수 있다.

Jinja2의 UnicodeDecodeError

App engine을 이용하는 project 중에 Jinja2 template 부분에서 다음과 같이 UnicodeDecodeError가 발생하는 문제가 생겼다.

Jinja2 web page의 설명문서에 따르면, Jinja2는 내부적으로 Unicode를 사용하기 때문에 template에는 unicode object를 넘겨주어야 한다고 설명하고 있는데, 내가 project에서 사용하는 web page는  cp949 encoding을 사용하기 때문에 문제가 발생한다.

따라서 이것은 Jinja2 template으로 data를 넘기기 전에 unicode()를 이용해서 cpc949를 uinicode로 변환한 값을 넘겨주면 해결할 수 있다. 다음은 실제로 수정된 code로 cp949로 encoding된 문자열을  unicode로 변환해서 추가하는 것이다.