검색 기능 고도화를 어떻게 하면 좋을까?
일반적으로 django에서 검색 기능을 만든다고 하면
대부분은 아래와 같이 만든다.

Product라는 모델이 있고 name을 기준으로 검색을 한다고 하면

search_text <= 이건 검색하는 단어

product_list = Product.objects.filter(name__icontains=search_text)

대부분 이렇게 검색 필터를 만들 것이다.

하지만 여기에는 치명적인 단점들이 존재한다.
예를 들면 Product의 이름이 "아이폰 14,128G, 실버" 라고 한다면
누군가를 "아이폰14"로 붙여서 검색을 한다면 결과에 잡히지 않는다.
또 "아이폰 실버"라고 검색해도 나오지 않는다.

문제는 여기서 끝이 아니다.
"맥북m2", "애플워치se2" 이런 영어와 숫자까지 함께 들어간다면,
어떻게 해야할까? 

해당 텍스트와 정확한 이름(띄어쓰기포함)이 없으면
그냥 결과는 아무것도 나오지 않는다.

"맥북m2"를 검색한 고객의 의도는 무엇일까? 
맥북프로 m2 모델을 검색하고 싶었을 수도 있고 맥북에어 m2 모델을 검색하고 싶었을 수도 있다. 
또 그것 뿐만 아니라 16인지 m2 모델도 있고, 14인치 m2 모델이 있을 수도 있다.

고객의 의도를 알 수 없는 우리는 최대한 많은 검색 결과를 알려줘야 하는데
위의 로직대로 구현을 해버리면 아무 검색 결과도 나오지 않게된다.

왜나하면 상품명은
Apple 2022 맥북에어, 실버, m2, 8G
애플 2021 맥북프로 16, 실버, m2 
이런 형태로 되어 있기 때문이다.

그럼 우리가 취해야 하는 것은 무엇일까?
일단 구글에 더 좋은 방법으로 한 것이 없을까 열심히 서칭을 해봤지만,
나오지 않았다.

그래서 다음에 생각한 것은 Product에 검색할 때 이용할 검색 참고 field를 하나 만들어서,
Apple 2022 맥북에어, 실버, m2, 8G의 해당 필드에 
맥북에어, 맥북m2, 맥북 실버, 2022 맥북 등 검색에 쓰일만한 텍스트들을 모두 넣어두는 것이다.
그래서 제품명 + 해당 필드에 있는 내용을 활용해서, 검색기능을 고도화 시키는 것이다.
그럼 유사한 keyword에도 검색결과가 잡히게 할 수 있으니 좋은 방법이기도 하다!
(실제로 커머스에서 많이 사용하는 방법이기도 하다)

하지만 상품이 많다면... 그리고 처음부터 도입시켜 놓지 않았다면
막막할 수 밖에 없다. 그리고 해당 검색 field에 모든 예시들을 넣어놓을 수 없을테니깐 이슈가 또 발생할 것 같았다.

그럼 현재 상황에서 가장 간단하게 최선의 방법을 선택하면 어떻게 하면 될까?
머리를 돌려보았다.

내가 생각했던 방법은 아래와 같다.
실제 고객이 검색하고 아무런 결과값을 보내지 못 했던 검색어가 아래에 있다.

"맥북m2", "애플워치se2" 

결국 찾고 싶은 것은
"맥북", "m", "2"가 모두 포함된 제품의 이름을 찾으면 되지 않을까?

애플워치se2는
"애플워치", "se", "2"가 모두 포함된 제품의 이름을 찾으면 되지 않을까?

완벽하지 않지만 당장에는 이렇게 하게 되면 
"맥북m2"라고 검색을 해도

Apple 2022 맥북에어, 실버, m2, 8G
애플 2021 맥북프로 16, 실버, m2 

이 2개의 제품이 모두 검색되게 할 수 있을 것이다.
왜냐하면 위의 제품명에는 맥북이라는 글자와 m이라는 글자, 2라는 글자 모두 포함되어 있기 때문이다.
(이 예시를 보면 m2를 합쳐서 생각하면 더 좋을 것 같긴 하다. 하지만 더 고려해야 할 것들이 많으니 일단은 넘어가자)

이런 형태로 하게 되면 애플워치se2도 많은 부분 개선된다.
애플워치se2를 한글, 영어, 숫자로 나눠보면
"애플워치", "se", "2"로 나눌 수 있고 이것을 제품명에 모두 포함한 제품들은 아래와 같이 될 수 있다. 

'Apple 2022 애플워치 SE 2세대 알루미늄 케이스, 40mm, GPS, 실버 / 화이트 스포츠밴드',

 'Apple 2022 애플워치 SE 2세대 알루미늄 케이스, 40mm, GPS, 스타라이트 / 스타라이트 스포츠 밴드',
 'Apple 2022 애플워치 SE 2세대 알루미늄 케이스, 40mm, GPS+Cellular, 스타라이트 / 스타라이트 스포츠 밴드',
 'Apple 2022 애플워치 SE 2세대 알루미늄 케이스, 44mm, GPS+Cellular, 미드나이트 / 미드나이트 스포츠밴드',

처음에는 검색해서 나오지 않았던 결과값들이 그래도 나름 괜찮게 그리고 의미있게 결과값들을 뽑아낼 수 있게 된다!!

이까지 아이디어가 나왔으면 이제 우리가 할 일은
정규식만 서칭하면 된다!! 오예!!

한글 정규식

hangle = re.compile("[가-힣]+").findall(search_text)

영어 정규식

english = re.compile("[a-zA-Z]+").findall(search_text)

숫자 정규식

number = re.compile("[0-9]+").findall(search_text)


findall을 하면 list가 만들어지고

search_text = "애플워치se2"
hangle = ["애플워치"]
english = ["se"]
number = ["2"]

그럼 이렇게 된 것을 하나의 list에 넣어준다.

temp_search_text = ["애플워치", "se", "2"]

그럼 우리가 해야 하는 것은 이 list안에 것들이 모두 포함된 제품명을 찾으면 된다!!

그럼 이제 로직으로 보면

hangle = re.compile("[가-힣]+").findall(search_text)
english = re.compile("[a-zA-Z]+").findall(search_text)
number = re.compile("[0-9]+").findall(search_text)

temp_search_text = hangle + english + number
# temp_search_text = ["애플워치", "se", "2"]


product_list = Product.objects.filter(is_deleted=False)

for text in temp_search_text:
    product_list = product_list.filter(name__icontains=text)

result_product_list = []

for product in product_list:
    result_product_list.append(product)


쏘 심플!! django의 ORM 쿼리셋은 lazy loading이라 매번 호출하지 않는다.
실제로 제일 아래 product_list를 for문으로 돌 때 실행되기 때문에
저런 형태로 작성해도 걱정하지 않아도 된다!

그럼 Product의 Name을 기준으로 해당 나눠놓은 텍스트들이 모두 포함된 제품들을 반환할 수 있게 된다. 

DB 변경 없이 코드 몇 줄 변경으로 아주 좋은 효율의 Django 검색 필터를 만들 수 있다.

해당 로직들을 적용 전과 후


만들고 나니, 생각보다 괜찮은 방법인 것 같고,
좋은 아이디어인 거 같아서 공유하기 위해 글을 적는다.

django를 사용하는 주니어 개발자분들에게 도움이 되기를!!

+ Recent posts