TL;DR
아래 내용을 통해 개발한 pypapago
는 현재 pypi에 올라가 있어 아래 명령어로 설치해 바로 사용할 수 있습니다.
1 | pip install -U pypapago |
2019.07.09일자 기준 최신버전은
0.1.1.1
입니다.
Github Repo: https://github.com/Beomi/pypapago
파파고 번역?
네이버에서는 Papago 라는 이름으로 ML 기반과 사전 기반 두가지 방식의 번역과 언어 감지 등 여러가지 서비스를 제공한다. 한편 해당 서비스는 파파고 공식 사이트에서 실제로 사용할 수 있지만 API를 이용해서 사용할 수도 있기 때문에, 개발자들이 API Key만 신청하면 한 번에 5천자, 그리고 하루에 1만자 이내로만 요청할 수 있다는 제한이 있다. (무료로 사용하는 경우)
한편 papago.naver.com에서 제공하는 웹 페이지 상에서의 번역은 추가적인 제한이 없기 때문에, 해당 웹 페이지를 파싱해서 어떤 API Call을 하고 있는지 뜯어보면 보다 많은 요청을 자유롭게 할 수 있지 않을까 싶었다.
AJAX(XHR) Request 뜯어보기
파파고에 번역할 문장을 집어넣고 ‘번역’ 버튼을 누를 때 이뤄지는 HTTP 요청을 크롬 개발자 도구로 살펴보면 다음과 같다.
실제로 translatedText
라는 키에 결과값이 잘 들어오는 것을 볼 수 있다.
한편 그렇다면 HTTP 요청을 보내는 방식이 어떻게 이뤄지는지 확인이 필요하다.
가장 먼저 살펴보는 것은 API Call이 어떤 URL(HOST)와 어떤 Method로 요청이 이뤄지는지 보는 것.
위 스샷을 살펴보면 https://papago.naver.com/apis/n2mt/translate
주소에 POST
방식으로 HTTP 요청을 보내고 있다는 것을 볼 수 있다.
POST
방식으로 보내는 HTTP 요청은 주로 <form>
내부의 FormData와 함께 보내는 경우가 많다. 따라서 아래쪽의 Form Data 항목을 살펴보면 다음과 같다.
하지만 우리가 입력한 “I am GROOT”라는 문장은 위 FormData 어디에서도 살펴볼 수가 없다. 어떻게 된 것일까?
Base64 Encode & Decode
위와 같이 암호화된 것 처럼 보이는 데이터를 base64
로 디코딩을 해 보았다.
결과는 위와 같이 “I am GROOT”라는 부분이 잘 나오는 것을 볼 수 있다. 한편 앞쪽에 나와있는 이상한 문자열은 정체를 알 수 없었다.
따라서 우리가 실제로 조정하는 것이 필요한 source
, target
, text
를 남기고 앞쪽은 base64로 인코딩 된 값을 그대로 가져왔다. (패키지 소스코드의 SECRET_KEY
부분. 사실은 secret이 아니다.)
코드로 만들어보자
Basic setup
가장 기초적인 방법은 크롬에서 실제로 요청하는 HTTP를 그대로 따라하는 방법이다.
Translator
Class를 생성할 때 기초적인 헤더들을 설정해 주고, 앞서 살펴보았던 요청에 필요한 base64로 인코딩 된 값들을 넣어준다.
Github: https://github.com/Beomi/pypapago/blob/0.1.1.1/pypapago/translator.py#L14-L33
1 | class Translator: |
String Type의 base64 값 만들기
앞서 사용한 self.QUERY_KEY
의 경우는 아직 UTF-8이기 때문에 base64로 인코딩 된 str
결과물이 필요하다.
이때 단순히 encode를 하면 type이 str
이 아니라 byte
타입이기 때문에 문제가 생긴다. (String와 Byte를 합칠 수 없다고 TypeError가 발생한다.)
Github: https://github.com/Beomi/pypapago/blob/0.1.1.1/pypapago/translator.py#L35-L42
1 |
|
따라서 위와 같이 .decode('utf-8')
으로 다시한번 바꾸어주는 과정이 필요하다.
HTTP 요청 보내기
위와 같이 준비가 끝나면 실제 HTTP 요청으로 보내는 것이 필요하다.
API Host도 알고, Method와 내용물도 알고 있으니 간단하게 보내기만 하면 된다.
Github: https://github.com/Beomi/pypapago/blob/0.1.1.1/pypapago/translator.py#L44-L61
1 | def translate(self, query, source='en', target='ko', verbose=False): |
실제로 query
, source
, target
을 우리가 바꾸어 사용하기 때문에 해당하는 값들을 넣어주고, API 요청이 이뤄질때 실제로 넘어오는 결과값은 굉장히 길고 디테일한 정보를 담고있다. 이런 정보도 사용할 수 있도록 verbose
옵션을 통해 Raw json을 받을 수 있는 옵션도 넣어준다.
Bulk/Parallel로 실행하기
한편 번역기를 사용할 때 한번에 하나가 아니라 여러개를 실행해야 하는 경우도 있다.
이런 경우를 위해 multiprocessing
을 사용해 기본적으로는 cpu코어 수(하이퍼스레딩은 2배)만큼 Worker를 띄워 사용하도록 설정해두고, 커스텀으로 worker 수를 지정할 수 있도록 옵션을 넣어준다.
실제로 worker를 30개를 넣으면 25배+로 빨라진다. WOW.
Github: https://github.com/Beomi/pypapago/blob/0.1.1.1/pypapago/translator.py#L63-L80
1 | def bulk_translate(self, queries, source='en', target='ko', workers=None, verbose=False): |
그리고 Bulk로 작업을 하는 경우에는 보통의 경우 얼마나 진행되었는지 알고싶은 것이 당연하기 때문에 imap
와 tqdm
을 사용해 Progress bar를 화면(Jupyter Notebook도 지원함!)에 나타나도록 만들어주었다.
(아래는 Google colab에서 pypapago를 이용해 몇천개 번역을 worker 30개로 돌릴때 화면에 나타나는 모습)
맺으며
간단하게 써보려고 하다가 파파고 API의 요청건수가 가볍게 넘어버려서 (ㅠㅠ) + Google번역기 패키지가 자꾸 에러를 뿜어서 라는 두가지 이유로 인해 만들어보았다.
오랫만에 작업한 pypi 패키징이라 살짝 헷갈리기도 해서 0.1.0
배포 후 0.1.1
로 긴급 패치(의존성을 setup.py
에 넣는 것 잊음)를 했지만 정작 오타가 있어서 0.1.1.1
이라는 이상한 버전으로 올리게 되었다는 이야기.
pypi 패키징 하는 부분도 간단하게 정리가 필요할 것 같다.