[얼죽아] 크롤링 2단계: 데이터 수집 및 단어 유사도 추출

[얼죽아] 크롤링 2단계: 데이터 수집 및 단어 유사도 추출

1. 정보 수집 및 처리 과정

앞 단계에서 중복을 제거한 데이터를 이용해서 가게의 정보를 수집하고자 합니다. 필요한 정보는 다음과 같습니다.

지리적 좌표는 naver cloude platform에 있는 Geocoding API를 이용해서 주소를 좌표로 변환해주었습니다.

메뉴에서 아메리카노에 해당하는 메뉴를 찾아내기 위해서 단어 유사도를 추출하는 방법을 사용했습니다. 이에 대한 상세한 설명은 후술하겠습니다.

데이터 수집 프로세스의 효율성을 높이기 위해 멀티스레딩 기법을 도입했습니다. ThreadPoolExecutor를 사용하여 여러 카페의 정보를 동시에 수집함으로써 전체 처리 시간을 크게 단축했습니다.

2. 단어 유사도

가게마다 아이스 아메리카노라는 메뉴를 표기하는 방법이 다양합니다. 예를들어 스타벅스는 카페 아메리카노, 어떤 가게는 ice americano, 또 다른 가게는 아메리카노 (ice/hot) 으로 가게마다 다른 것을 알 수 있습니다. 따라서 메뉴 이름의 유사도를 추출하여 가장 아이스 아메리카노와 유사한 메뉴를 추출하는 과정을 추가했습니다.

2-1. 토큰화와 정규화

먼저, 입력된 문자열을 토큰화하고 정규화하는 과정을 거칩니다.

def tokenize_and_normalize(string):
    string = string.lower()
    string = string.replace('/', ' ')
    string = re.sub(r'[^a-z0-9가-힣\s]', ' ', string)
    string = re.sub(r'\s+', ' ', string)
    tokens = string.split()

    expanded_tokens = []
    for token in tokens:
        parts = re.findall(r'[a-z]+|[가-힣]+', token)
        if len(parts) > 1:
            expanded_tokens.extend(parts)
        else:
            expanded_tokens.append(token)

    return expanded_tokens

이 함수는 다음과 같은 작업을 수행합니다:

  1. 모든 문자를 소문자로 변환
  2. ’/’ 문자를 공백으로 변환
  3. 숫자, 알파벳, 한글 이외의 문자 제거
  4. 연속된 공백을 하나의 공백으로 변환
  5. 문자열을 토큰으로 분리
  6. 복합 단어 처리 (예: “ice아메리카노” → [“ice”, “아메리카노”])

2-2. 동의어 처리

메뉴 이름에 사용되는 동의어를 정의하여 유사도 계산에 활용합니다.

def get_synonyms():
    return {
        'ice': {'ice', 'iced', '아이스'},
        'americano': {'americano', '아메리카노'},
        'hot': {'hot', '따뜻한', '뜨거운'}
    }

2-3. 유사도 계산

토큰화된 단어들과 동의어 정보를 바탕으로 유사도를 계산합니다.

이 함수는 다음과 같은 과정으로 유사도를 계산합니다.

def calculate_similarity(target_tokens, compare_tokens, synonyms):
    target_set = set(target_tokens)
    compare_set = set(compare_tokens)

    # 동의어 확장
    expanded_target = set()
    expanded_compare = set()
    for synonym_set in synonyms.values():
        if target_set & synonym_set:
            expanded_target.update(synonym_set)
        if compare_set & synonym_set:
            expanded_compare.update(synonym_set)

    target_set.update(expanded_target)
    compare_set.update(expanded_compare)

    # 교집합 계산
    intersection = target_set & compare_set

    # 가중치 부여
    weight = sum(2 if token in {'americano', '아메리카노'} else 1 for token in intersection)
    max_weight = sum(2 if token in {'americano', '아메리카노'} else 1 for token in target_set | compare_set)

    return weight / max_weight if max_weight > 0 else 0
  1. 동의어를 이용해 토큰 집합을 확장
  2. 두 토큰 집합의 교집합 계산
  3. ‘아메리카노’와 같은 핵심 단어에 가중치 부여
  4. 가중치가 부여된 유사도 점수 반환

이 알고리즘은 자카드 유사도를 변형한 방법인데, 자카드 유사도를 이용한 이유는 다음과 같습니다.

americano iced, 아메리카노 (ice/hot) 과 같이 “아이스” 라는 단어와 “아메리카노”라는 단어의 순서가 항상 일정하지 않을 수 있습니다. 따라서 집합을 이용한 자카드 유사도를 사용하여 어떤 순서에 나오더라도 유사도 점수에 반영되도록 하였습니다.