수학과의 좌충우돌 프로그래밍

[crawling] 02. request를 통한 HTTP 요청 - GET 본문

웹프로그래밍/crawling

[crawling] 02. request를 통한 HTTP 요청 - GET

ssung.k 2019. 3. 21. 03:18
requests_get

requests

이번에는 requests 라는 라이브러리를 통해서 본격적으로 데이터를 가져와보도록 하겠습니다. 위에서 HTTP 메소드에 대해서 이야기를 했었고 requests를 통해서 모든 메소드에 대해서 접근이 가능하지만 가장 많이 쓰이고 중요한 GET과 POST 에 대해서만 다뤄보도록 하겠습니다.

GET 요청

데이터를 불러오는 방법은 생각보다 간단합니다. 그리고 그 간단함이 바로 requests 라이브러리의 가장 큰 장점 중 하나죠.

import requests
response = requests.get('http://naver.com')
response.text  

결과를 확인하면 아래와 같습니다.

'<!doctype html>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<html lang="ko">\n<head>\n<meta charset="utf-8">\n<meta name="Referrer" content="origin">\n<meta http-equiv="Content-Script-Type" content="text/javascript">\n<meta http-equiv="Content-Style-Type" content="text/css">\n<meta http-equiv="X-UA-Compatible" content="IE=edge">\n<meta name="viewport" content="width=1100">\n<meta name="apple-mobile-web-app-title" content="NAVER" />\n<meta name="robots" content="index,nofollow"/>\n<meta name="description" content="네이버 메인에서 다양한 정보와 유용한 컨텐츠를 만나 보세요"/>\n<meta property="og:tit [생략]

단 3줄만으로 naver 의 html 코드를 가져오는데 성공했습니다. 물론 저 데이터를 바로 사용할 수는 없지만 보기 좋게 가공하는 과정은 뒤에서 다루도록 하고 지금은 데이터를 성공적으로 가져오는데 초점을 맞추도록 하겠습니다.

requests 객체의 메소드

여기서부터는 requests 를 import 하는 과정은 생략하도록 하겠습니다. 맨 위에서 했다는 전제로 진행을 하죠.

이번에는 유용하게 사용되는 requests의 메소드들을 알아보도록 하겠습니다.

response = requests.get('http://naver.com')
response.status_code
response.ok
response.content
response.text

마찬가지로 naver 메인 페이지의 정보를 가져오는 상황입니다. 결과값을 확인하고 각 쓰임들을 알아보도록 하겠습니다.

200
True
b'<!doctype html>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<html lang="ko">\n<head>\n<meta charset="utf-8">\n<meta name="Referrer" content="origin">\n<meta http-equiv="Content-Script-Type" content="text/javascript">\n<meta http-equiv="Content-Style-Type" content="text/css">\n<meta http-equiv="X-UA-Compatible" content="IE=edge">\n<meta name="viewport" content="width=1100">\n<meta name="apple-mobile-web-app-title" content="NAVER" />\n<meta na [생략]
'<!doctype html>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n<html lang="ko">\n<head>\n<meta charset="utf-8">\n<meta name="Referrer" content="origin">\n<meta http-equiv="Content-Script-Type" content="text/javascript">\n<meta http-equiv="Content-Style-Type" content="text/css">\n<meta http-equiv="X-UA-Compatible" content="IE=edge">\n<meta name="viewport" content="width=1100">\n<meta name="apple-mobile-web-app-title" content="NAVER" />\n<meta name="robots" content="index,nofollow"/>\n<meta name="description" content="네이버 메인에서 다양한 정보와 유용한 컨텐츠를 만나 보세요"/>\n<meta property="og:tit [생략]
  • status_code

    • 응답 상태 코드를 return하게 됩니다. 응답 상태 코드란 말 그대로 HTTP 요청에 대해서 요청이 성공했는지 실패했는지 혹은 어떤 상태인지를 말해줍니다. 각 요청에 대한 건 https://developer.mozilla.org/ko/docs/Web/HTTP/Status 에서 자세히 확인하실 수 있습니다. 결과 값으로 나온 200은 성공했다는 의미입니다.
  • ok

    • status_code의 값을 통해 값이 결정되는데 200이상 400미만, 즉 성공했을 경우에는 True 를 그렇지 않을 경우에는 False 를 return 하게 됩니다. 데이터를 잘 불러오고 있나 확인이 가능하죠.
  • content

    • content는 가져온 데이터를 bytes 타입으로 return 합니다. 일반적으로는 사람이 보고 데이터를 읽을 수가 없습니다. 그렇기 때문에 인코딩 작업이 필요하죠.
  • text

    • 예시에서도 살펴봤듯이 str 타입의 데이터를 return 합니다. 기본적으로 content 의 bytes 타입의 데이터를 인코딩하는 방식이죠. 그 결과 우리도 데이터를 쉽게 읽을 수 있습니다.

 

그러면 여기서 한 가지 의문이 들 수 있습니다. requests는 content를 어떤 방식으로 디코딩 하여 text로 바꿔주는지 어떻게 알 수 있을까요? 정답은 응답헤더 에 있습니다.

 

응답헤더

헤더의 내용을 확인하기 위해서는 headers 메소드를 사용합니다.

response = requests.get('http://naver.com')
response.headers

그 결과는 아래와 같습니다.

{'Server': 'NWS', 'Date': 'Wed, 20 Mar 2019 16:19:26 GMT', 'Content-Type': 'text/html; charset=UTF-8', 'Transfer-Encoding': 'chunked', 'Connection': 'keep-alive', 'Set-Cookie': 'PM_CK_loc=d60457a8fc51c5f539b7c71e807c9cc3f89533c169b3a46c94604f64e7dff11c; Expires=Thu, 21 Mar 2019 16:19:26 GMT; Path=/; HttpOnly', 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'P3P': 'CP="CAO DSP CURa ADMa TAIa PSAa OUR LAW STP PHY ONL UNI PUR FIN COM NAV INT DEM STA PRE"', 'X-Frame-Options': 'DENY', 'X-XSS-Protection': '1; mode=block', 'Content-Encoding': 'gzip', 'Strict-Transport-Security': 'max-age=63072000; includeSubdomains', 'Referrer-Policy': 'unsafe-url'}

보기만 해도 어려워보이지만 지금 모든 내용을 다 알 필요는 없습니다. 그래도 한 가지 알 수 있는 건 dictionary 라는 점입니다. 확인해보도록 할까요?

type(response.headers)

결과는 예상과 다릅니다.

requests.structures.CaseInsensitiveDict

맨 뒤에 Dict가 붙은걸로 봐서 Dict 과 같은 기능을 하긴 하지만 requests 만의 dictionary 입니다. 가장 두드러진 특징은 key 값으로 접근 할 때 대소문자의 구분이 없다는 점이죠.

다시 본론으로 돌아와서 저 많은 내용 중에 디코딩, 인코딩과 관련된 내용은 다음과 같이 확인할 수 있습니다.

response.headers['content-type']

결과값은 아래와 같습니다.

'text/html; charset=UTF-8'

UTF-8 방식으로 디코딩 하는걸 확인할 수 있습니다. 이 방식만 알고 있다면, text 를 사용하지 않고 content를 직접 디코딩해서 사용할 수 있습니다. 아래와 같이 말이죠.

response.content.decode('utf8')

결과는 text 와 동일합니다.

하지만 우리 마음대로 되지는 않을 겁니다. content-typecharset 이 지정되어있지 않으면 어떻게 될까요? 디코딩을 제대로 하지 못해 글자를 알 수 볼 수 없을 겁니다.

 

한글이 깨질 때는 어떻게 해야하지?

여태까지와 동일하게 크롤링을 시도했을 때, 한글이 깨지는 경우가 발생하기도 합니다. 아래 예시를 함께 보도록 하죠.

response = requests.get('http://www.puka.co.kr/')
response.text[50:60]

알아 볼 수 없는 결과가 출력이 됩니다.

Ä¡¿øÃÑ¿¬ÇÕȸ ºÎ»êÁöÈ'

위에서도 말했듯이, 그 이유는 charset 이 지정되어 있지 않기 때문입니다.

response.headers['content-type']

결과는 아래와 같습니다.

'text/html'

이럴 경우에는 어떤 방식으로 디코딩 되었는지 직접 찾아줘야 합니다. 해당 사이트로 접속한 후에, 다음과 같은 방법으로 페이지의 html 코드를 확인할 수 있습니다.

오른쪽 마우스 -> 페이지 소스보기

meta 태그 를 찾고 charset 이 어떻게 지정되어 있는지를 확인합니다.

<meta http-equiv="Content-Type" content="text/html; charset=euc-kr">

euc-kr 를 이용해서 디코딩하면 원하는 결과를 얻을 수 있을 겁니다.

# 방법 1
response.content.decode('euc-kr')
# 방법 2
response.encoding = 'euc-kr'
response.text

방법 1은 content의 내용을 직접 디코딩하는 방식이고, 방법 2는 인코딩 방식을 알려줘서 text 가 제대로 된 방식을 찾도록 도와줍니다. 결과로 반가운 한글을 확인할 수 있습니다.

'\r\n<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">\r\n<html>\r\n<head>\r\n<title>::: 한국유치원총연합회 부산지회 :::</title>\r\n<meta h 

 

queryString

뜬금없이 또 생소한 단어가 나왔지만 집고 넘어가야 할 부분입니다. 먼저 용어에 대한 설명부터 하자면,

웹 클라이언트에서 서버로 전달하는 데이터 방식

우리는 기존에 웹 클라이언트가 서버로 데이터를 전달할 때 url 을 통해 전달한다는 사실을 알았습니다. 그리고 url을 살펴보도록 하죠. 제가 아까 본 네이버 스포츠 뉴스의 기사입니다.

https://sports.news.naver.com/basketball/news/read.nhn?oid=065&aid=0000178691

우리가 주목해야할 부분은 ? 이후의 부분으로서 바로 이 부분을 queryString 이라고 합니다. & 와 = 을 통해 값들을 연결하고 있는데 이는 dictionary에서 , 와 : 에 대응됩니다. 즉 위 url 에서의 queryString 을 dictionary 로 나타내면 다음과 같습니다.

{'oid':'065', 'aid':'0000178691'}

그리고 복잡해보이던 위 url 을 좀 더 가독성 좋게 표현할 수 있습니다.

params = {'oid':'065', 'aid':'0000178691'}
response = requests.get('https://sports.news.naver.com/basketball/news/read.nhn', params = params)
response.text[520:560]

params 를 통해 queryString 에 해당하는 부분을 dictionary 로 넘겨줘도 데이터를 잘 불러올 수 있습니다.

[KBL시상식] 1위와 MVP의 엇갈림, 현대모비스와 KCC의 희비

 

 

Comments