본문 바로가기
IT/Scraping

지저분한 데이터 정리하기: 코드에서 정리

by Cyber_ 2025. 2. 20.

웹 스크레이핑에서 데이터를 수집할 곳을 정하거나, 수집할 데이터를 고를 수 없을 때가 많습니다. 잘못된 구두점, 일관성 없는 대문자 사용, 줄바꿈, 오타 등 지저분한 데이터는 웹의 큰 문제입니다.

 

언어학에서 n-그램은 텍스트나 연설에서 연속으로 나타난 단어 n개를 말합니다. 자연어를 분석할 때는 공통적으로 나타나는 n-그램, 또는 자주 함께 쓰이는 단어 집합으로 나눠서 생각하는 게 편리할 때가 많습니다.

 

아래의 4개의 함수를 통해 n-그램을 사용한 경우 발생하는 예외상황들에 대해 대처할 수 있습니다.

from urllib.request import urlopen
from bs4 import BeautifulSoup
import re
import string

def cleanSentence(sentence):
    sentence = sentence.split(' ')
    sentence = [word.strip(string.punctuation + string.whitespace)
        for word in sentence]
    sentence = [word for word in sentence if len(word) > 1
        or (word.lower() == 'a' or word.lower() == 'i')]
    return sentence

def cleanInput(content):
    content = content.upper()
    content = re.sub('\n|[[\d+\]]', ' ', content)
    content = bytes(content, "UTF-8")
    content = content.decode("ascii", "ignore")
    sentences = content.split(', ')
    return [cleanSentence(sentence) for sentence in sentences]

def getNgramsFromSentence(content, n):
    output = []
    for i in range(len(content)-n+1):
        output.append(content[i:i+n]
    return output

def getNgrams(content, n):
    content = cleanInput(content)
    ngrams = []
    for sentence in content:
        ngrams.extend(getNgramsFromSentence(sentence,n))
    return(ngrams)

html = urlopen('http://en.wkipedia.org/wiki/Python_(programming_language)')
bs = BeautifulSoup(html, 'html.parser')
content = bs.find('div', {'id':'mw-content-text'}).get_text()
ngrams = getNgrams(content, 2)
print(ngrams)
print('2-grams conut is: ' + str(len(ngrams)))
  • getNgrams: 기본적인 진입점 역할
  • cleanInput: 줄바꿈 문자와 인용기호를 제거하고, 마침표 뒤에 공백이 나타나는 것을 기준으로 텍스트를 '문장'으로 분할할 수 있습니다.
  • cleanSentece: 문장을 단어로 분할하고, 구두점과 공백을 제거하고, 한 글자로 이루어진 단어(I와 a를 제외한)를 제거합니다. cleanInput 안에서 호출합니다.
  • getNgramsFromSentnece: 매 문장마 getNgrams에서 호출하여 문장에 걸치는 n-그램을 생성되는 일을 방지합니다.

데이터 정규화(data normalization)

논리적으로 동등한 문자열, 예를 들어 전화번호 (555) 123-4567과 555.123.4567 같은 문자열이 똑같이 표시되도록, 최한 비교할 때 같은 것이라고 판단하게 하는 작업입니다.

 

위 코드로 추출된 결과에는 중목된 2-그램이 많다는 문제가 있습니다. 2-그램을 만나면 리스트에 추가할 뿐, 그 빈도를 기록하지 않습니다. 2-그램이 존재하는지만 보기보다는 그 빈도를 기록하면 흥미로울 뿐만 아니라, 데이터 정리 알고리즘이나 정규화 알고리즘을 바꿨을 때 어떤 효과가 있는지 알아보는 데도 유용합니다. 데이터를 성공적으로 정규화한다면 중복 없는 n-그램의 총 숫자는 줄어들겠지만 n-그램의 총 숫자는 줄어들지 않을 것입니다.

 

n-그램을 리스트가 아니라 Counter 객체에 추가하도록 수정하면 정규화가 수행됩니다.

from collections import Counter
...

def getNgrams(content, n):
    content = cleanInput(content)
    ngrams = Counter()
    ngrmas_list = []
    for sentence in content:
        newNgrams = [' '.join(ngrmas) for ngram in getNgrams in getNgrmasFromSentence(sentence,n)]
        ngrams_list.extend(newNgrams)
        ngrmas.update(newNgrams)
    return(ngrams)

 

Counter 객체를 사용할 경우 리스트는 해시를 적용할수 없어서, 각 n-그램마다 리스트 내포(list comprehension) 안에서

' '.join(ngram)을 통해 먼저 문자열로 바꿔야만 합니다.

 

 

대소문자 이슈로 인한 해결방안은 cleanInput 함수에 content = content.upper()를 추가하면 됩니다.