본문 바로가기
ETC/파파고 글자수 제한 없이 사용하기

파파고 글자수 제한 없이 사용하기

by 손너잘 2018. 2. 12.
인공지능이 발달함에 따라 번역기의 성능이 나날이 높아져가고 있다.

이 프로젝트는 대학교 웹서비스설계 과목의 팀 프로젝트로 진행했던 것 이다.
학기 프로젝트였지만 귀차니즘에 팅가팅가 놀다가 발표 하루전날에 모든걸 만들어버리는 기염을 토해내느라 나도 토할뻔했지만..
프로젝트 진행을 함께해준 박XX, 이XX 에게 감사의 마음을 전하기는 개뿔 밥사야 한다 이시키들 ㅡㅡ.

장난이고 ,대략적인 프로젝트의 설계를 말하도록 하겠다.


사진1. 파파고의 글 입력 부분.

파파고(다른 여타 번역기 포함)는 5000자의 번역 제한을 가지고 있다.
공부를 하다보면 영문으로 된 논문을 읽을때가 많은데 비루한 영어실력을 이용하여 읽자니 머리가 괴롭고, 그 긴 글자를 나눠서 번역하자니 귀찮을때가 아주 많았다.
그래서 생각한 아주 간단한 방법. 

"그럼 5000자씩 끊어서 번역한다음 붙이면 되잖아?"

바로 실행에 옮겼다.

솔직히 매우 빠르게 끝날줄 알았다.
어딘가로 데이터를 보낼꺼고, 그냥 그 데이터를 살짝 조작만 하면 바로바로 결과가 튀어나울줄 알았다. 

제일 먼저 생각한 방법은, PhantomJs같은 headerless 브라우저를 이용하여 파파고 페이지에 접속해서 지속적으로 요청과 응답을 받아와서 응답을 붙여 저장하면 될것. 이라는 방법이었다. 

파파고 http헤더 분석할 필요도 없고 아주 간단한 방법이라 생각했는데...

사진2. 소스를 편집했지만 변하지 않는 내용....??

textarea의 내용을 수정해도 글이 변하지 않는다...? 생각지도 못한 부분이었다. 조금 보다보니

사진3. keyboardEvent를 잡아내는 ????.js
파파고에 글이 입력될때마다 자동 언어선택 기능을 구동시키기 위해서 keyboardEvent를 잡아서 서버에 비동기통신을 통한 요청을 보내는 행위를 하는데 이러한것 때문에 내용이 바뀌지 않는다는 추측을 하게 되었다. (이유를 아시는 분 있으시면 답글 부탁드립니다.)
물론 시간만 있으면 이유를 알아낼 수 있었지만 프로젝트 기간에 매우 쫒기던 상황이라 그러지 못했다고 스스로 자기위로를....

쨋든.  이러한 이유로 인해 phantomJs 를 이용하는것을 포기하고 request헤더 분석으로 방향을 틀었다.


사진4. 파파고 번역시 request헤더

그래서 바로 http request header를 봤고, data부분이 단순히 base64인코딩 되어있어 인코딩을 풀어보았다.

사진5. base64 디코딩을 진행한 data

생각한것과 같이 data부분에 json형태로 데이터를 전송하는것을 확인할 수 있었다.
앞부분을 보면 글자가 깨지는것을 볼 수 있는데, 이 부분을 여러 테스트 결과 바뀌지 않는것을 확인하였다. 왜있는지는 아직도 잘 모르겠다.

인코딩방식이 base64이므로 문자열을 잘 자르면 데이터를 손상시키지 않고 잘라낼 수 있다.

사진6. 파이썬을 이용한 데이터 생성

그렇게 필요한 데이터를 찾아내고, 다시 만들어내는것까지 소스를 작성하였다. 이제 이거를 requests나 urllib로 전송하면 데이터가 오겠지 ^오^ 하는 생각으로 코드를 작성했는데... 결과는 똥망이었다. 데이터가 오질 않는다... 
user-agent를 검사하나? 해서 바꿔보기도 하고.. 이런저런 삽질 많이했다. 물론 지금도 이유를 모른다.

그렇게 오랜시간 삽질을 하다가. 문득, 헤더를 직접 보내는게 안먹힌다면, 실제 유저가 접근하는것처럼 하면...? 이란 생각을하고 처음에 묻어버렸다 phantomJS를 다시 꺼냈다.

사진7. phatomJS를 이용한 데이터 요청 소스와 결과

결과는 대 성공이었다.
이제 문자열을 보내면 번역을 해 주니 아주 긴~ 문자열을 5000자 안쪽으로 끊어주는 소스를 작성해야 한다.
이 부분은 나의 귀여운 후배 이XX씨가 작성해 주었는데 상당한 버그를 내재하고 있는 소스이다. 따로 포스팅 하지는 않겠다.

대략적인 로직을 말하자면, ., ?, 엔터 등등... 문단을 나눠준다 생각하는 녀석들을 기준으로 문자열을 최대 5000자가 넘지 않게 잘라서 각각의 문장을 번역기에 넣어주게 하였다.

그렇게 만들어진 번역기, 다른 언어도 다 지원하게 만들었다.

사진8. 테스트.


시간이 촉박했던만큼 날림으로 만든부분도 많아 아쉬움이 많이 남는 작품이다.
그래도.... 이상하게 학교프로젝트로 한건 다시 건들고 싶지가 않더라...
A+ 받았으니 난몰랑~

남들에게 공개하기는 많이 창피한 소스라, 내가 작성한 부분과 필요하다 생각되는 부분의 중요소스만 공개하도록 하겠다.


소스

translater.jsp

웹에서 form(번역 할 언어) to(번역 될 언어) data (번역될 문자열) 을 post나 get으로 넘겨주면 작동되게 되는 코드, 
parse.py파일을 tomcat의 bin폴더안에 있어야 한다.

from, to의 매개변수로는 
한국어
ko
영어
en
일본어
ja
중국어(간체)
zh-CN
중국어(번체)
zh-TW
스페인어
es
베트남어
vi
태국어
th
인도네시아어
id
가 있겠다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="java.util.*" %>
<%@ page import="java.io.*" %>
<%
String from = request.getParameter("from");
String to = request.getParameter("to");
String data = request.getParameter("data");
 
Process process = null;
BufferedReader in = null;
BufferedReader err = null;
 
if(from == null || to == null || data == null){
        out.println(from);
        out.println(to);
        out.println(data);
        out.println("nop");
        return;
}
 
 
String s;
try {
        process = Runtime.getRuntime().exec(new String[]{"python","parse.py",from,to,data});
 
        in =  new BufferedReader (new InputStreamReader(process.getInputStream()));
        while (( s = in.readLine ())!= null) {
                out.println(s+"<br>");
        }
        err = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        while (err.ready()) {
                out.println(err.readLine()+"<br>");
        }
catch (Exception e) {
        out.println("Error : "+e);
finally {
        if (in != nulltry { in.close(); }  catch (Exception sube) {}
        if (err != nulltry { err.close(); }  catch (Exception sube) {}
}
 
%>

parse.py

받은 문자열을 잘라서 서버에 보내는 역활을 하는 부분이다. (정확히는 서버에 보내는 프로그램으로 데이터를 던져준다.)
본문에 말했듯이, 필자가 작성한 소스코드는 translater 함수뿐 나머지는 나의 귀여운 후배가 작성해 주었다. 
잘 작동하는듯 하다가도 버그를 뿜뿜 내뿜는 소스이니 주의하길 바란다.

post.js와 같은 폴더 안에 있어야 동작한다.
translater부분에서 일본어->한글 한글->일본어가 나뉘는걸 볼 수 있는데.
오래전 프로젝트라 기억이 잘 안나는데 일본어는 저부분이 좀 달랐었다... 암튼 다름! 이렇게 안하면 결과 이상함!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#-*- coding: utf-8 -*-
import re
import sys
import base64
from urllib import quote, unquote
import requests, json
import urllib2, urllib
import ssl
import subprocess
 
def remove_tag(content):
   cleanr =re.compile('<.*?>')
   cleantext = re.sub(cleanr, '', content)
   return cleantext
 
def translater(From, to, string) :
        data_signature = "rlWxnJA0Vwc0paIyLCJkaWN0RGlzcGxheSI6NSwic291cmNlIjoi"
 
        request_message = '{}","target":"{}","text":"{}","deviceId":"714addce-8b1d-40b9-9645-b2e5ec5dbbb5"'.format(From, to, string) + '}'
 
 
        data_to_base64 = base64.encodestring(request_message)
        urlencoded_data = quote(data_to_base64)
 
        data =  data_signature + urlencoded_data
 
    if((From == "ko" and to == "ja"or (From=="ja" and to=="ko")) :
            cmd = ['phantomjs','--output-encoding=utf-8''post.js', data, "1"]
    else :
        cmd = ['phantomjs','--output-encoding=utf-8''post.js', data, "0"]
 
 
        fd_popen = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
        data = json.loads(remove_tag(fd_popen.read()))
        return data["translatedText"]
 
 
reload(sys)
sys.setdefaultencoding('utf-8')
 
 
string =  sys.argv[3]
From  = sys.argv[1]
to = sys.argv[2]
 
 
dotSpace = 0        #'. '기준으로 찾은 위치
dotPerN = 0         #'.\n'기준으로 찾은 위치
Qmark = 0           #'? '기준으로 찾은 위치
QmarkPerN = 0       #'?\n'기준으로 찾은 위치
splitedText = []    #5000이하의 단위로 잘라낸 문자열을 저장하는 리스트
start_point = 0     #search를 위해 만든 시작 위치 저장 포인트
end_point = 0       #어디까지 문자열 저장 리스트에 저장할지, 그리고 다음 스타트 위치가 어디일지를 결정
 
 
while len(string[start_point:])>5000:   #5000보다 크면 수행
    dotPerN = string[start_point:start_point+5000].rfind('.\n')         #5000단위로 찾음. rfind는 뒤에서부터 찾음
    dotSpace = string[start_point:start_point+5000].rfind('. ')
    QmarkPerN = string[start_point:start_point + 5000].rfind('?\n')
    Qmark = string[start_point:start_point + 5000].rfind('? ')
    end_point = max(Qmark,QmarkPerN,dotSpace,dotPerN)       #찾은 위치들 중 가장 큰 값을 이용. 그러니까 5000에 가장 가까운 것
    if end_point==-1:
        end_point = max(string[start_point:start_point + 5000].rfind('.'), string[start_point:start_point + 5000].rfind('?'), string[start_point:start_point + 5000].rfind('\n'))
        splitedText.append(string[start_point:start_point + end_point+1])
        start_point = start_point + end_point+1
        continue
 
 
 
    splitedText.append(string[start_point:start_point + end_point+2])   #리스트에 해당 위치까지 저장.  search기준 문자도 저장.
    start_point = start_point + end_point+2
 
splitedText.append(string[start_point:])    #반복문을 나와서 5000자 이하의 나머지 스트링을 append함
k=1
result = ""
for i in splitedText:
    result = result + translater(From, to, i)
 
print result
 
 

post.js

일반 자바스크립트파일이 아니라 phantom.js파일이다. 딱히 설명할건 없다고 생각한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var system = require('system');
 
var argsdata = system.args[1];
var ko_ja = system.args[2];
var server;
 
if(ko_ja == "0")
else
 
var page = require('webpage').create(),
    data = 'data='+argsdata;
 
page.open(server, 'post', data, function (status) {
    if (status !== 'success') {
        console.log('Unable to post!');
    } else {
        console.log(page.content);
    }
    phantom.exit();
});
 

댓글