하얀설표 블로그




해결)파이썬 크롤링시 유니코드 변환문제(\xa0,  , \u3000)(how to convert unicode text in python)





( 수정됨)


파이썬으로 제일 많이 하는 것이 웹 크롤링이 아닐까 생각한다.
웹사이트 크롤링 자체는 큰 문제가 없긴 한데.. 관련 정보를 확인해두면 좋다.

해결방법

해결방법은 보통 2가지인데, 개인적으로 사용하는 방법까지 총 3가지가 있다.

  1. replace를 이용한 유니코드 제거
    - 일반적으로 가장 많이 사용하는 방법이 아닐까 생각한다.
  2. unicodedata.normalize를 이용한 유니코드 변환
    - 파이썬에서 제공하는 모듈을 통해 한 번에 유니코드를 제거할 수 있다.
  3. split과 join을 이용한 제거
    - 개인적으로 애용하는 방법이다.

설명

개인적으로 여러 사이트들을 크롤링하며 모은 유니코드와 대응하는 문자는 다음과 같다.

  • \xa0 : 1칸의 공백
  •   : 1칸의 공백
  • \u3000 : 탭(\t)
  • \u200b : 이모티콘(이모지)
  • \u200d : 이모티콘(이모지)

문제 상황

위 문자들을 무시한 채로 beautifulSoup 객체로 변환하면 다음과 같은 결과를 확인할 수 있다.

import bs4
string = '1,''\xa0'',2,' ' '',3,' '\u3000' ',4,''\u200b'',5,''\u200d'',6'
soup = bs4.BeautifulSoup(string, 'html.parser')
print('string')
print(f'  {soup=}')
print(f'  {str(soup)=}')
print(f'  {soup.get_text()=}')

>> string
  soup=1, ,2, ,3, ,4,​,5,‍,6
  str(soup)='1,\xa0,2,\xa0,3,\u3000,4,\u200b,5,\u200d,6'
  soup.get_text()='1,\xa0,2,\xa0,3,\u3000,4,\u200b,5,\u200d,6'

단순히 beautifulSoup 객체를 출력하면 공백처럼 보이지만, str 객체로 변환하거나 get_text() 명령을 사용하면 유니코드가 숨어있다는 것을 확인할 수 있다.

replace 사용하기

replace를 사용하는 경우 각 유니코드를 확실하게 제거할 수 있다.
이 방법의 유일한 단점은 새로운 유니코드가 나타날 때마다 replace 명령을 추가해줘야 한다는 것이다.

코드

import bs4
string = '1,''\xa0'',2,' ' '',3,' '\u3000' ',4,''\u200b'',5,''\u200d'',6'
soup = bs4.BeautifulSoup(string.replace('\xa0', ' ').replace(' ', ' ').replace('\u3000', '\t'), 'html.parser')
print('string.replace')
print(f'  {soup=}')
print(f'  {str(soup)=}')
print(f'  {soup.get_text()=}')

>> string.replace
  soup=1, ,2, ,3,       ,4,​,5,‍,6
  str(soup)='1, ,2, ,3,\t,4,\u200b,5,\u200d,6'
  soup.get_text()='1, ,2, ,3,\t,4,\u200b,5,\u200d,6'
  [" ".join(soup.get_text().split())]=['1, ,2, ,3, ,4,\u200b,5,\u200d,6']

unicodedata.normalize 사용하기

파이썬 내장모듈인 unicodedata.normalize 명령을 사용할 수도 있다.
여기서는 NFKC와 NFKD를 사용해볼 것이다.

자세한 사용방법은 파이썬 공식문서에서 확인할 수 있다.

이 방법의 단점은 유니코드가 아닌 문자는 변환되지 않는다는 것이다.
 와 같이 unescape시 유니코드로 변환되는 문자열을 따로 제거해주거나, 변환 후 다시 한 번 unicodedata.normalize 작업을 수행해줘야 한다.

코드

# unicodedata.normalize('NFKC')
import unicodedata
import bs4
string = '1,''\xa0'',2,' ' '',3,' '\u3000' ',4,''\u200b'',5,''\u200d'',6'
soup = bs4.BeautifulSoup(unicodedata.normalize('NFKC', string), 'html.parser')
print("bs4.BeautifulSoup(unicodedata.normalize('NFKC', string)")
print(f'  {soup=}')
print(f'  {str(soup)=}')
print(f'  {soup.get_text()=}')
print(f'  {[" ".join(soup.get_text().split())]=}')

>> bs4.BeautifulSoup(unicodedata.normalize('NFKC', string)
  soup=1, ,2, ,3, ,4,​,5,‍,6
  str(soup)='1, ,2,\xa0,3, ,4,\u200b,5,\u200d,6'
  soup.get_text()='1, ,2,\xa0,3, ,4,\u200b,5,\u200d,6'
  [" ".join(soup.get_text().split())]=['1, ,2, ,3, ,4,\u200b,5,\u200d,6']

# unicodedata.normalize('NFKD')
import unicodedata
import bs4
string = '1,''\xa0'',2,' ' '',3,' '\u3000' ',4,''\u200b'',5,''\u200d'',6'
print("unicodedata.normalize('NFKD', string)")
print(f'  {soup=}')
print(f'  {str(soup)=}')
print(f'  {soup.get_text()=}')
print(f'  {[" ".join(soup.get_text().split())]=}')

>> unicodedata.normalize('NFKD', string)
  soup=1, ,2, ,3, ,4,​,5,‍,6
  str(soup)='1, ,2,\xa0,3, ,4,\u200b,5,\u200d,6'
  soup.get_text()='1, ,2,\xa0,3, ,4,\u200b,5,\u200d,6'
  [" ".join(soup.get_text().split())]=['1, ,2, ,3, ,4,\u200b,5,\u200d,6']

unicodedata.normalize는 왜 \xa0를 남겼을까?

이런 결과를 보게 되면 당황스럽지만.. 납득할만한 이유가 있다. 변환되기 전 \xa0는 유니코드가 아니었기 때문이다.
무슨 말이냐면, 유니코드 변환 후에도 남아있는 \xa0는  가 unescape된 문자라는 것이다.

다음 코드를 확인해보자.

코드

import html
print(f"{html.unescape(' ')=}")

>> html.unescape(' ')='\xa0'

split과 join을 사용하는 방법

각각의 유니코드로 이루어진 문자다.
그런데 재미있는 부분이 있는데, 이모티콘인 \u200b, \u200d을 제외한 나머지(\xa0,  , \u3000)는 파이썬에서 공백 문자로 인정된다는 것이다.

다음 코드를 보면 split 명령으로 인해 남아있던 유니코드가 제거된 것을 확인할 수 있다.
웹페이지의 특정 문구만을 사용할 때 이 방법을 사용하면 별다른 작업없이 유니코드를 제거할 수 있다.
\u200b, \u200d와 같은 문자는 이모티콘이기 때문에 unicodedata.normalize를 사용하더라도 잔존하기 때문에, 제거하려면 replace나 다른 방법을 사용해야한다.

코드

import bs4
string = '1,''\xa0'',2,' ' '',3,' '\u3000' ',4,''\u200b'',5,''\u200d'',6'
soup = bs4.BeautifulSoup(string, 'html.parser')
print('string')
print(f'  {soup=}')
print(f'  {str(soup)=}')
print(f'  {soup.get_text()=}')
print(f'  {[" ".join(soup.get_text().split())]=}')

>> string
  soup=1, ,2, ,3, ,4,​,5,‍,6
  str(soup)='1,\xa0,2,\xa0,3,\u3000,4,\u200b,5,\u200d,6'
  soup.get_text()='1,\xa0,2,\xa0,3,\u3000,4,\u200b,5,\u200d,6'
  [" ".join(soup.get_text().split())]=['1, ,2, ,3, ,4,\u200b,5,\u200d,6']


공감 : 0







white.seolpyo.com