한국어 처리할 때 제일 골치아픈 것은 인코딩 문제이다. 특히 UnicodeDecodeError 이거는 시도때도 없이 튀어나와서 사람 애먹인다.
처음에는 utf-8 에러가 뜨길래, cp949로 바꿨다. 그랬더니 다른 문서에서 에러가 뜨길래 utf-16을 써봤으나 별 도움이 안 되었다. 에러의 대표적인 예시:
'utf-8' codec can't decode byte 0xbe in position 0: invalid start byte
UTF-16 stream does not start with BOM
'cp949' codec can't decode byte 0xbf in position 2: illegal multibyte sequence
전제: (txt 파일 read 로 불러올 때)
with open (file_path, encoding = encoding_name) as x:
text_name = x.read()
문법에서 file_path에 해당하는 텍스트계열 확장자에는 문제가 없음을 전제로 한다.
문제가 있다면 특정 확장자를 잘 불러올 수 있는 전용 라이브러리를 추가하시오.
다른 상황에 적용해야 하는 사람은 아래 코드나 흐름을 보고 응용하시오. 핵심은, 자주 쓰는 순서대로 계속 인코딩을 호출해가며 시도하는 것이다.
코드의 기능
아래의 코드는 텍스트파일이 인코딩 인식할 수 있을때까지 계속 꼬라박아보는 방식이다.
무식한데 이것만큼 효과적인 방법은 없었다.
다같은 txt파일로 저장해놨을텐데 인코딩이 중간중간 제각각이다.
막상 써보면 utf-8, cp949, utf-16 이 세개만 있어도 사실상 충분하니 빨리 돌리고 싶으면 저것만 쓰자.
textfile_path # 텍스트 파일의 주소. type str. 확장자는 txt 권장. 아마도 메모장으로 바로 열리는 정도면 유사 확장자여도 충분히 될것이다.
encoding_list = ['utf-8','cp949','utf-16'] # 3대 한국어 인코딩. 이거면 충분할 것이다.
# encoding_list = ['utf-8','utf-8-sig','utf8mb4','cp949','euc-kr','utf-16'] # 내가 주로 쓰던 목록. cp949가 euc-kr의 상위호환일것이나 일단 넣어놓았다.
for k in range(len(encoding_list)):
try:
with open(textfile_path, encoding=encoding_list[k]) as h:
temp_text = h.read()
h.close()
print(encoding_list[k], 'encoding success')
# 첫번째 인코딩부터 시작해서 하나씩 대입하여 읽어오라
except UnicodeDecodeError as e1:
print('UnicodeDecode Error:',e1)
pass
# 인코딩에 실패했는데, 유니코드_디코드_에러가 났다면 알려주고 다음 목록으로 진행
# 아래는 대표예제
# UnicodeDecode Error: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte
except UnicodeError as e2:
print('UnicodeError:', e2)
pass
# 인코딩에 실패했는데, 유니코드_에러가 났다면 알려주고 다음 목록으로 진행
# 정확히 뭐였는지는 기억안나는데, 무튼 다른 이유로 인식실패했다는 문구
except:
print('Errors: etc')
# 대부분의 인코딩 에러가 유니코드_디코드_에러 아니면 유니코드_에러 라서 이쪽 에러면은 직접 찾아볼것.
else:
break
# try 문이 에러없이 잘 수행되었다면, 더 할거없으니 for k loop를 종료.
'euc_kr' codec can't decode byte 0xff in position 0: illegal multibyte sequence
Unicode Decode Error 별 최적 인코딩과 주요 에러문구
최적 인코딩 | <<< 다음 바이트 오류면 | 다른 인코딩의 에러문구 |
'utf-8' | 0x0a 0xbf |
'cp949' codec can't decode byte 0xbf in position 2: illegal multibyte sequence 'utf-16-le' codec can't decode byte 0x0a in position 34616: truncated data UnicodeError: UTF-16 stream does not start with BOM |
'utf-16' | 0xff | 'euc_kr', 'cp949' codec can't decode byte 0xff in position 0: illegal multibyte sequence 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte |
'euc-kr' 'cp949' |
0xb0 0xb4 0xbe 0xbf 0xc0 0xc1 |
'utf-8' codec can't decode byte 0x?? in position 34: invalid start byte 'utf-16-le' codec can't decode bytes in position 76-77: illegal UTF-16 surrogate |
그 밖에 에러문구.
#1. utf8mb4 코덱 인식불가
Errors: unknown encoding: utf8mb4
#2: ascii 코덱 범위초과
UnicodeDecodeError: 'ascii' codec can't decode byte 0x84 in position 4: ordinal not in range(128)
아스키 코덱은 설정 자체가 0x00 ~ 0x7F(127) 까지만 인식하므로, 0x80~0xFF(128~255)에 해당하는 바이트 값이 인식되면 튕긴다.
256까지 확장된 latin-1 (https://en.wikipedia.org/wiki/ISO/IEC_8859-1) 으로 바꾸면 될거다.
위의 방식처럼 낱개로 찾다간 끝이 없을것 같아서, utf-8, cp949, utf-16 총3가지에 대해 16진수 4자리(== 4니블 == 2바이트)를 어떻게 인식하는지를 쭉 나열해 보았다.
주의: 유니코드와 인코딩 간의 개념이 명확하지 않을때 적은 것이라 오류가 좀 있다.
글자를 인식하려고 2바이트씩 부르는데, 개중에 대응하지 않는 2바이트가 있으면 Unicode Decode Error 가 난다고 보면 되겠다. 나무위키의 cp949 등의 문서를 읽어보면 조금 도움된다.
그 결과는 아래와 같으며, 바로 다운받거나 자기 구글드라이브에 복사해서 쓰거나 다운받는 등 여러가지 방법으로 가져갈 수 있게 해놨다. 구글 스프레드시트 열어서 바로 엑셀 다운받기 누르면 약 3분정도 걸린다.
https://docs.google.com/spreadsheets/d/1uqwFQKfm1Dfo9er6lcG8eAmnsPg-mkVZKNQYXy0gc70/edit?usp=sharing
표 만들때 실험하거나 썼던 코드
하도 Unicode Decode Error 가 잘 나길래, 도대체 어디서 에러가 나는지 궁금해서 바이트로 저장했다가 뽑아보는 코드들도 썼다.
## 여러가지 encode decode 방법
## 1. 바이트로 저장된 값을 리스트로 변환했다, 바이트로 변환했다 디코드.
a = b'\xea\xb7\x9c\xec\xa0\x95\xed\x95\x98\xeb\x8a\x94 \xea\xb7\xb8\xeb\x9f\xac\xed\x95\x9c'
b = list(a)
c = bytes(b)
d = c.decode('utf-8')
print('결과:_',d)
## 2. 바이트로 저장된 값을, bytearray 기능으로 바꾸고 디코드
a = b'\xb1\xb2'
c = bytearray(a)
d = c.decode('cp949')
print('결과:_',d)
e = c.decode('utf-16')
print('결과:_',e)
스프레드시트 표를 만들때 썼던 코드.
바이트 표기를 엑셀로 만드려고, 네다섯 시간 정도 썼네
pip install openpyxl
pip install xlsxwriter
import pandas as pd
import re
en = ['utf-8','cp949','utf-16']
l = [[],[],[]]
fmt = '\\x{0:02X}\\x{1:02X}'
for k in range(3):
for i in range(256*256):
x1 = fmt.format(*(i.to_bytes(2, byteorder='big'))) # 바이트값 \xFF\xFF 만드려고 사용
x2 = i.to_bytes(2,byteorder='big') # 단순 계산용
# 나도 x1, x2로 나누어서 쓰고 싶지 않았는데 내 머리로는 안 된다.
try:
y = x2.decode(en[k]) # 이 인코딩이 먹힌다면,
l[k].append(y) # decoded의 약자 dcdd를 리스트에 추가
except:
l[k].append(x1) # 인코딩이 안 먹힌다면, byte 값(\xFF\xFF 형식)을 리스트에 추가
df = pd.DataFrame(l).T
df
# 아래 코드는 저장하다 에러나는데 어떻게 잘 수정해보기 바란다.
ILLEGAL_CHARACTERS_RE = re.compile(r'[\000-\010]|[\013-\014]|[\016-\037]')
df2 = df.applymap(lambda x: ILLEGAL_CHARACTERS_RE.sub(r'', x) if isinstance(x, str) else x)
df2.to_excel("save.xlsx")
## 그냥 기록용. 당신한테 필요없으면 아래는 모두 지워도 된다.
'''
아래의 방법은 특수문자를 저장하는 데 모두 실패했다.
원인은 그
그냥 저장.
pip install xlsxwriter 사용.
applymap 사용
dataframe = dataframe.applymap(lambda x: x.encode('unicode_escape').
decode('utf-8') if isinstance(x, str) else x)
'''
코드를 만들때 핵심이 되었던 질의응답들
11번의 시도, 2번의 나름 성공을 제외한 나머지 과정에서 겪은 질의응답은 생략.
2바이트의 앞 또는 뒤 바이트가 ASCII로 표현되는걸 막기위한 기능
유니코드 문자 잔뜩 들은것을, 바로 df.to_excel() 쓰면 illegal character error 가 뜨는데 그거 해결법