우리 회사 개발 업무 특성 상 일률적으로 들어오는 상품 데이터 각 오픈 마켓에 맞게 가공 해야 할 때가 많다. 상품명에 특정 특수문자가 들어가지 않아야하고 length가 70자가 넘지 않아야 하는 아주 간단한 가공부터 상품 옵션이 각 마켓에서 필요로 하는 조건으로 바꿔줘야하는 아주 까다로운 것 까지 다양하다.


그 중 내가 개발한 알고리즘(?) 중에 가장 뿌듯했던 것을 소개해보고자 한다.


그것은 바로 키워드 추출 알고리즘이다.


여기서 말하는 키워드란 오픈 마켓을 사용하는 이용자가 어떤 상품을 찾기 위해 가장 많이 사용하는 검색어를 말한다. 모든 마켓에서 키워드가 필요하지만 쿠팡에서는 키워드와 가격 설정이 상품을 상위 노출 시키는데 다른 마켓보다 더욱 중요하다 (쿠팡에서도 키워드 입력을 적극 추천한다). 가격 설정 알고리즘도 개발하였는데 이 부분은 나중에 소개할 기회가 된다면 블로그에 올리도록 하겠다.


우리 회사가 쿠팡 이용자들이 사용하는 검색어들을 실시간으로 추적하여 데이터를 가지고 올 수 있다면 더욱 정확한 분석을 통해 키워드를 추출할 수 있겠지만 그게 아니니 쿠팡 검색어 자동 완성 단어들을 가지고 오기로 했다. 하지만 어떠한 이유로 쿠팡에서 Open API에 공개된 API외에는 해외 IP들을 차단되게끔 해놔서 기껏 다 만들어 놓고 사용할 수 없게 되어 네이버 쇼핑 검색 api를 이용하게 되었다.

먼저 한 단어를 검색했을 때, 자동완성 되는 단어들을 결과값으로 얻게 되는 함수이다. 


하얀색 네모로 표시된 부분은 네이버 쇼핑 api를 통해 자동완성 되는 부분을 리턴값으로 가져온다. 하지만 리턴값이 JSON이 아닌 HTML이므로 파란색 네모 부분처럼 처리를 해줘야 HTML리턴값을 확인 할 수 있다. 하지만...

보시는 바와 같이 엄청 추접하게 나온다. 다른 검색어들로 여러번 돌려본 결과, 녹색 네모부분은 공통적으로 나온다는것을 파악했고 그 부분의 인덱스를 파이썬 문자열 내장 함수인 rfind()로 찾아내서 잘라내고 노란색 부분을 통하여 nested list들까지 flatten 시켜보았지만 .. 

여전히 추접하다. 이때쯤 그만두고 싶었지만 조금 더 자세히 들여다 보니 각 빨간색 동그라미 부분과 같이 4i 인덱스에서 내가 필요로 하는 키워드들이 있다라는 것을 파악했다.


그래서 빨간색 네모 부분과 같이 0번 인덱스부터 4번째 인덱스들을 추출하도록 하는 for loop을 만들었는데 이 부분에서 문제가 발생했다. 정상적인 검색어들의 범위를 벗어나게 되면 검색어와 상관없는 숫자 또는 url들이 나와서 아래와 같이 쓸데없는 것들이 키워드 리스트로 들어가게 되는 것이다. 


고민을 많이 했다. 어떻게 하면 효과적으로 쓸데없는 것들을 처리할 수 있을까.. 고심끝에 얻은 결론은 무의식적으로 사용하는 try, except구문을 사용하는 것이었다. 통상적으로 정상적인 부분을 처리하는 부분이 try이고, try에서 발생하는 에러들을 처리하는 부분이 except인데 이것을 거꾸로 사용하면 어떨까하고 시도해본 방법이었는데 다행히도 성공적이었다.

빨간색 네모의 try를 보면, 리스트 안의 item들을 돌면서 전부 int(item)을 거치게 한다. 스트링을 int화 시키게 되면 에러가 생기게 되고 에러가 생긴다면 숫자가 아닌 것이고 숫자가 아니라면 내가 찾는 검색어이므로 final_keyword_list로 들어가게 된다. 따라서 내가 찾는 또는 정상적인 부분을 에러로 인식하게끔 만들어 except부분에서 처리하는 것이다.

위에 올린 코드에는 나와 있지 않지만 이 블로그를 작성하다가 url도 포함될 수 있다라는 것을 발견했고 except부분에 아래와 같이 추가하였다. Url이라면 http는 필연적으로 가지고 있을테고 url을 포함하고 있다면 그 부분은 건너뛰게 했다.


그래서 결과적으로...

정상적인 키워드 리스트들을 리턴하게 되었다. 이 함수의 기능은 '한 단어'를 통해 자동 완성 되는 검색어들을 리턴하는 것이고, 이 함수를 for loop으로 돌려서 최종 키워드들을 리턴하는 함수는 따로 있는데 이 부분은 간단하므로 패스하겠다.


결론:

알고리즘이라고 다 대단히 어렵고 복잡한 것은 아니라는 것을 이번 기회를 통해 알게 되었다. 매일 아침 toy문제들을 포기해보면서 '아 알고리즘은 나랑 안맞는것 같다', '너무 어렵다' 라고 생각했지만 주구장창 그것만 연습해서 잘 푸는것 보다 간단한 알고리즘이라도 목적에 맞게 사용한다면 그게 바로  실전 알고리즘이고 참된 개발이 아닐까 생각해본다.


파이썬을 쓰다보면 '@' 이런 모양을 종종 만나게 된다. 저것은 데코레이터(decorator)라고 하는 것이다. 난 데코레이터를 처음 봤을때 '오 귀여운 골뱅이네'라고 생각했다. 플라스크를 처음 사용할 때도 귀여운 골뱅이를 보았다.


from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
return "Hello World!"


위 예시 코드에서 '@app.route("/")' 이 부분이 바로 데코레이터다.


그렇다면 데코레이터는 무슨 일을 할까?

코드를 바꾸지 않고 기존 함수를 수정할 때 사용할 수 있다. 데코레이터는 함수를 인자로 받아 다른 함수를 출력한다. 직관적으로 이해가 되지 않으니 예제를 한번 만들어보자.


def document_it(func):
def new_function(*args):
print('Running function:', func.__name__)
print('Arguments:', args)
result = func(*args)
print('This is result:', result)
return result
return new_function


함수의 이름과 인자, 그리고 결과를 알려주는 데코레이터 함수를 만들었다! 이 데코레이터 함수는 함수를 인자로 받아 새로운 함수를 반환한다. *args는 파이썬의 위치인자이다. **kwargs를 넣으면 키워드 인자까지 알려줄 수 있지만 예제에선 위치인자만 넣어보았다.


그럼 한번 사용해보자.


def give_me_beer(a):
return "I need {} more beer!!".format(a)

give_me_beer(5)
>> I need 5 more beer!!

another_give_me_beer = document_it(give_me_beer)

another_give_me_beer(900)
>> Running function: give_me_beer
>> Arguments: 900
>> This is result: I need 900 more beer!!
>> I need 900 more beer!!


위에서는 수동으로 데코레이터함수를 할당했다. 그래서 귀여운 골뱅이 '@' 모양을 볼 수 가 없다.

데코레이터를 사용하고 싶다면 사용하고 싶은 함수 위에 '@decorator_name'을 쓰면 된다.


@document_it
give_me_beer(5)
>> I need 5 more beer!!


give_me_beer(900)
>> Running function: give_me_beer
>> Arguments: 900
>> This is result: I need 900 more beer!!
>> I need 900 more beer!!


짠 이제 give_me_beer만 실행해도 내가 원하는 데코레이터 함수가 적용되어 작동한다. 예제에서는 하나만 썼지만 데코레이터는 여러개를 붙여서 사용할 수도 있다.



데코레이터를 어떤 경우에 쓸까

함수 마다 실행되는 시간을 측정해야 되는 경우가 있다고 가정해보자. 함수가 약 20개 정도 되는데 모든 함수의 소스코드에 실행 시간을 측정하는 코드를 짜 넣는 것은 비효율적이다. 게다가 매우 귀찮은 과정이 될 것이다.

그럴 때 시간을 실행 시간을 측정하는 데코레이터 함수를 만들어 함수의 위에다 붙여주기만 하면 각 함수의 코드를 변경하지 않아도 실행 시간을 측정할 수 있지 않을까?(물론 다른 방법들도 생각이 났는데 이건 데코레이터 포스팅이니까)


프로젝트를 하다보면 언젠가 요긴하게 쓸 날이 올 것 같은 기분적 느낌이 드는 기분이다. 그 날이 오면 골뱅이 무침에 맥주를 마시러 만선호프에 가자.

+ Recent posts