본문 바로가기
Python/error

[error]웹 크롤링 시 Unicodeencodeerror: 'ascii' codec can't encode characters in position.. 해결법

by 전봇대파괴자 2020. 8. 23.

웹 크롤링을 시도하다 보면 종종 UnicodeEncodeError: 'ascii' codec can't encode characters in position 10-14: ordinal not in range(128) 와 같은 에러와 마주하게 됩니다. 저 같은 경우 복습 겸 간단한 크롤링 코드를 만들어 연습할 때 발생하곤 했는데, 코드는 아래와 같습니다. 

 

# 간단한 크롤링 코드(한글 위키피디아)

# 라이브러리 불러오기
from bs4 import BeautifulSoup
from urllib.request import urlopen
import time

query = '블레이드 러너'
url = "https://ko.wikipedia.org/wiki/" + query

html = urlopen(url)
soup = BeautifulSoup(html, 'html.parser')

# print(soup)

text = soup.find('div', {'class' : 'mw-parser-output'}).get_text() # 본문 전체 크롤링
text = text.replace('\n', '') # 이스케이프 문자 제거

text

 

이 코드를 실행시키면 다음과 같은 에러가 발생합니다.

 

---------------------------------------------------------------------------
UnicodeEncodeError                        Traceback (most recent call last)
<ipython-input-9-0998ac2562e2> in <module>
     12 url = "https://ko.wikipedia.org/wiki/" + query
     13 
---> 14 html = urlopen(url)
     15 soup = BeautifulSoup(html, 'html.parser')
     16 

~\Anaconda3\lib\urllib\request.py in urlopen(url, data, timeout, cafile, capath, cadefault, context)
    220     else:
    221         opener = _opener
--> 222     return opener.open(url, data, timeout)
    223 
    224 def install_opener(opener):

~\Anaconda3\lib\urllib\request.py in open(self, fullurl, data, timeout)
    523             req = meth(req)
    524 
--> 525         response = self._open(req, data)
    526 
    527         # post-process response

~\Anaconda3\lib\urllib\request.py in _open(self, req, data)
    541         protocol = req.type
    542         result = self._call_chain(self.handle_open, protocol, protocol +
--> 543                                   '_open', req)
    544         if result:
    545             return result

~\Anaconda3\lib\urllib\request.py in _call_chain(self, chain, kind, meth_name, *args)
    501         for handler in handlers:
    502             func = getattr(handler, meth_name)
--> 503             result = func(*args)
    504             if result is not None:
    505                 return result

~\Anaconda3\lib\urllib\request.py in https_open(self, req)
   1358         def https_open(self, req):
   1359             return self.do_open(http.client.HTTPSConnection, req,
-> 1360                 context=self._context, check_hostname=self._check_hostname)
   1361 
   1362         https_request = AbstractHTTPHandler.do_request_

~\Anaconda3\lib\urllib\request.py in do_open(self, http_class, req, **http_conn_args)
   1315             try:
   1316                 h.request(req.get_method(), req.selector, req.data, headers,
-> 1317                           encode_chunked=req.has_header('Transfer-encoding'))
   1318             except OSError as err: # timeout error
   1319                 raise URLError(err)

~\Anaconda3\lib\http\client.py in request(self, method, url, body, headers, encode_chunked)
   1227                 encode_chunked=False):
   1228         """Send a complete request to the server."""
-> 1229         self._send_request(method, url, body, headers, encode_chunked)
   1230 
   1231     def _send_request(self, method, url, body, headers, encode_chunked):

~\Anaconda3\lib\http\client.py in _send_request(self, method, url, body, headers, encode_chunked)
   1238             skips['skip_accept_encoding'] = 1
   1239 
-> 1240         self.putrequest(method, url, **skips)
   1241 
   1242         # chunked encoding will happen if HTTP/1.1 is used and either

~\Anaconda3\lib\http\client.py in putrequest(self, method, url, skip_host, skip_accept_encoding)
   1105 
   1106         # Non-ASCII characters should have been eliminated earlier
-> 1107         self._output(request.encode('ascii'))
   1108 
   1109         if self._http_vsn == 11:

UnicodeEncodeError: 'ascii' codec can't encode characters in position 10-13: ordinal not in range(128)

 내용을 보면 한글 검색어를 아스키 코드로 표현할 수 없기 때문에 생기는 문제라고 볼 수 있습니다. 구글링을 해보니 이러한 에러는 urlopen을 사용하여 웹 크롤링 시(특히 검색어를 한글로 입력하였을 때) 자주 발생하는 오류라고 합니다.

 

이 에러를 해결하기 위해 제가 알아낸 방법은 두 가지입니다.

 

 

 

 

1. quote로 검색어 감싸주기

 아스키 코드가 한글을 받아들이지 못해 생기는 문제이므로 인코딩을 해주는 방법입니다. urllib.parse.quote() 함수는 아스키 코드가 형식이 아닌 글자를 URL 형식(퍼센트 인코딩이라고도 합니다)으로 인코딩해줍니다. 네이버나 다음 검색창에서 주소를 복사할 때면, 흔히 복사할 때만 해도 한글이었던 검색어가 붙여넣기만 하면 %와 숫자가 섞인 긴 뭉텅이로 변해있는 것을 볼 수 있습니다. 

 

# URL 형식 예시
'블레이드 러너'
=> %EB%B8%94%EB%A0%88%EC%9D%B4%EB%93%9C+%EB%9F%AC%EB%84%88

 

이러한 인코딩 과정을 거치면 아스키 코드로 검색어를 받아들일 수 있게 되며 오류가 나지 않게 됩니다. 수정된 코드는 다음과 같습니다.

 

# 간단한 크롤링 코드(한글 위키피디아)
from bs4 import BeautifulSoup
from urllib.request import urlopen
from urllib.parse import quote # 요게 핵심!
import time

query = quote('블레이드 러너') # query가 한글일 경우, quote로 감싸주면 에러가 발생하지 않는다
url = "https://ko.wikipedia.org/wiki/" + query

html = urlopen(url)
soup = BeautifulSoup(html, 'html.parser')

# print(soup)

text = soup.find('div', {'class' : 'mw-parser-output'}).get_text() # 본문 전체 크롤링
text = text.replace('\n', '') # 이스케이프 문자 제거
text

 

 

'  다른 뜻에 대해서는 블레이드 러너 (동음이의) 문서를 참조하십시오.서기 2019 블레이드 러너Blade Runner감독리들리 스콧제작마이클 딜리각본햄톤 팬커데이비드 피플즈원작필립 K. 딕의 《안드로이드는 전기양을 꿈꾸는가?》출연해리슨 포드  루트거 하우어  숀 영  에드워드 제임스 올모스음악반젤리스촬영조던 크로넨웨스편집테리 로링스마샤 나카스쉬마제작사라드 컴퍼니   배급사워너 브라더스개봉일 미국 1982년 6월 25일 대한민국 1986년 1월 20일 대한민국 1989년 1월 7일 (MBC) 대한민국 1993년 5월 8일시간117분국가 미국언어영어제작비$28,000,000흥행수익$27,580,111 (미국)《서기 2019 블레이드 러너》(Blade Runner)는 1982년 미국에서 제작된 SF 액션 스릴러 영화이다...

 

보다시피 본문이 제대로 크롤링되는 것을 확인할 수 있습니다. 

 

 

 

 

 

2. requests 사용하기

두 번째는 HTTP 라이브러리 requests를 활용하여 자동 인코딩하는 방법입니다. requests.get(url) 함수는 괄호 안에 들어간 URL을 가진 웹페이지의 데이터들을 가져오는데, 이 뒤에 붙은 .content라는 속성은 이 데이터들을 아스키 코드로 표현할 수 있도록 인코딩해주는 역할을 합니다.

 

# 간단한 크롤링 코드(한글 위키피디아)
from bs4 import BeautifulSoup
import requests # 라이브러리 임포트
from urllib.request import urlopen
import time

query = '블레이드 러너'
url = "https://ko.wikipedia.org/wiki/" + query

html = requests.get(url).content # 코드 변경! # 뒷부분의 content를 빼먹지 않도록 한다!
soup = BeautifulSoup(html, 'html.parser')

# print(soup)

text = soup.find('div', {'class' : 'mw-parser-output'}).get_text() # 본문 전체 크롤링
text = text.replace('\n', '') # 이스케이프 문자 제거
text

 

이렇게 수정한 코드를 실행시켜도 결과는 오류 없이, 위와 동일하게 나오는 것을 알 수 있습니다.

 

개인적으로는 quote보다 requests를 사용하는 것이 편해서 자주 애용하고 있습니다. 도움이 되기를 바랍니다. 

 

 

 

 

 

 

 

 

영문 위키피디아 크롤링

# 영문 크롤링 코드(위키피디아)
from bs4 import BeautifulSoup
from urllib.request import urlopen
import time

query = 'blade runner' # 똑같은 영화 이름을 영어로만 바꿈
url = "https://en.wikipedia.org/wiki/" + query # 주소 앞부분 ko => en으로 바꿈

html = urlopen(url) 
soup = BeautifulSoup(html, 'html.parser')

# print(soup)

text = soup.find('div', {'class' : 'mw-parser-output'}).get_text() # 본문 전체 크롤링
text = text.replace('\n', '') # 이스케이프 문자 제거
text

 

 

---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
<ipython-input-26-c51e3c20b515> in <module>
     12 url = "https://en.wikipedia.org/wiki/" + query
     13 
---> 14 html = urlopen(url) # html = requests.get(url).content로 하는 방법도 있다.
     15 soup = BeautifulSoup(html, 'html.parser')
     16 

~\Anaconda3\lib\urllib\request.py in urlopen(url, data, timeout, cafile, capath, cadefault, context)
    220     else:
    221         opener = _opener
--> 222     return opener.open(url, data, timeout)
    223 
    224 def install_opener(opener):

~\Anaconda3\lib\urllib\request.py in open(self, fullurl, data, timeout)
    529         for processor in self.process_response.get(protocol, []):
    530             meth = getattr(processor, meth_name)
--> 531             response = meth(req, response)
    532 
    533         return response

~\Anaconda3\lib\urllib\request.py in http_response(self, request, response)
    639         if not (200 <= code < 300):
    640             response = self.parent.error(
--> 641                 'http', request, response, code, msg, hdrs)
    642 
    643         return response

~\Anaconda3\lib\urllib\request.py in error(self, proto, *args)
    567         if http_err:
    568             args = (dict, 'default', 'http_error_default') + orig_args
--> 569             return self._call_chain(*args)
    570 
    571 # XXX probably also want an abstract factory that knows when it makes

~\Anaconda3\lib\urllib\request.py in _call_chain(self, chain, kind, meth_name, *args)
    501         for handler in handlers:
    502             func = getattr(handler, meth_name)
--> 503             result = func(*args)
    504             if result is not None:
    505                 return result

~\Anaconda3\lib\urllib\request.py in http_error_default(self, req, fp, code, msg, hdrs)
    647 class HTTPDefaultErrorHandler(BaseHandler):
    648     def http_error_default(self, req, fp, code, msg, hdrs):
--> 649         raise HTTPError(req.full_url, code, msg, hdrs, fp)
    650 
    651 class HTTPRedirectHandler(BaseHandler):

HTTPError: HTTP Error 400: Bad Request

 

검색어가 영어인 경우에도 가끔씩 이런 오류가 발생하곤 하는데, 원인은 단어 사이의 띄어쓰기로 보입니다. 

해결 방법은 위와 같이 quote로 감싸주거나 requests를 사용하면 됩니다. 편법으로 검색어를 bladerunner처럼 공백 없이 붙여서 쓰거나, 'blade' + 'runner' 같이 써주는 방법도 있습니다.   

 

결과는 아래와 같습니다. 

 

'This article is about the 1982 film. For other uses, see Blade Runner (disambiguation).1982 film directed by Ridley ScottBlade RunnerTheatrical release poster by John AlvinDirected byRidley ScottProduced byMichael DeeleyScreenplay byHampton FancherDavid PeoplesBased onDo Androids Dream of Electric Sheep?by Philip K. Dick...

 

 

 

※ 참고 : http://pythonstudy.xyz/python/article/403-%ED%8C%8C%EC%9D%B4%EC%8D%AC-Web-Scraping

'Python > error' 카테고리의 다른 글

[error]수식 블록과 사라지는 $  (0) 2021.01.31
[error]tensorflow-gpu 설치 중 발생 error  (2) 2021.01.26