본문 바로가기

취미코딩/hobby

파이썬으로 업무 자동화까지 <7> requests 2

코딩 초보의 정리글입니다. 0편부터 보세요.

import requests

url = "https://news.naver.com"

r = requests.get(url)

print(r)

네이버 뉴스 메인페이지 호출 스크립트다. 한줄씩 보면,

import requests

requests 패키지를 설치한 후에, 이 구문으로 불러온다. 파이썬에서 기본적으로 제공하는 게 아니라면, import로 불러온 이후에 쓸 수 있다.

url = "https://news.naver.com"

호출하고자 하는 주소를 문자열로 변수에 할당했다.

r = requests.get(url)

requests 라이브러리에서 get이라는 함수를 실행했다. 간단하게 이해하면, requests라는 라이브러리가 있고, 그 안에 get이라는 함수가 있다. 그래서 `패키지.함수` 형태로 마침표를 찍어 쓰면 해당 함수를 쓸 수 있다. 불러올 주소를 ( )안에 넣는데, 인자라고 부른다. 즉, url 변수를 인자로 받아 requests의 get이라는 함수를 실행하라는 의미다.

이 명령어를 r이라는 변수에 할당했다. 이건 get함수가 일을 다 처리하고 나서 응답을 반환(return)하는데, 그 값을 이용하기 위해서다.

print(r)

화면에 해당 변수를 출력한다.

함수

수학시간에 배우던 것과 비슷한다. `f(x) = x + 1` 따위 말이다. 이걸 파이썬 함수로 작성하면

def plusone(x):
    return x+1

과 같다. plusone이라는 이름의 함수로, x라는 인자를 받으면 1을 더해서 돌려주라는 얘기다. 이 함수는 x가 숫자여야 유효하지만, 함수는 훨씬 더 많은 역할을 수행할 수 있다.

return 앞에 4개의 빈칸에 유의하자. 파이썬은 빈칸이 매우 중요하다. 다만 지금 당장 써먹을 일은 없으니 눈에 담아두기만 하자.

예상하지 못한 결과

코드를 실행해보자. 제일 간단한 실행 방법은 에디터 화면에서 우클릭, `Run Python File in Terminal`을 선택하는 것이다. 인터프리터를 제대로 선택했다면 가상환경에서 파일이 제대로 실행이 될 것이다. 그리고 아래 문구를 만날 것이다.

<Response [403]>

403은 HTTP 상태값이다. 요청을 거부한다는 뜻이다. 응답이 정상이라면 보통 200이 출력된다.

몇달 전까지만 하더라도 200 응답이 정상적으로 와서, 다음 작업을 할 수 있었다. 그런데 막아버렸으니, HTTP 호출의 좀 더 자세한 사안들을 알고 넘어가야 할 필요가 생겼다.

그런데 글을 쓰고 있는 사이 다시 변경점이 생겼다.

raise ConnectionError(err, request=request) requests.exceptions.ConnectionError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))

아예 파이썬 함수 자체 에러가 난다. 앞선 결과는 함수는 정상적으로 실행, 응답 HTTP가 에러 코드로 반환됐었다.

에러 원인을 알기 위해서는 앞쪽 에러부터 이해해야 한다. 일단 뒤쪽 에러를 경험한 사람은 맨 처음 코드에서 URL을 네이버가 봇 접근을 허용한 URL인 "https://naver.com"으로 변경해 다시 응답을 정상적으로 받아보자.

import requests

url = "https://naver.com/"
r = requests.get(url)
print(r)
print(r.request.headers)

마지막에 프린트 함수를 하나 더 추가했다. r이라는 응답은 requests 함수가 생성한 객체다. 객체라는 말이 처음에 이해하기 어렵고 쉽게 설명하기도 쉽지 않다. 당장은 그 안에 여러가지 종합적인 정보를 담아냈다고 기억해두자. r.requests.headers라는 건 우리가 보낸 HTTP 호출(requests)의 header 값을 담고 있다. 마침표는 한번에 계속 이어서 쓸 수 있다. 

<Response [200]>
{'User-Agent': 'python-requests/2.25.0', 'Accept-Encoding': 'gzip, deflate', 'Accept': '*/*', 'Connection': 'keep-alive'}

허용한 URL을 호출했으므로 정상적인 200 응답이 오고, 그 밑에는 우리가 호출할 때 보낸 header 값이 나온다. 

HTTP Headers

크롬 브라우저에서 네이버에 접속하자. 그리고 F12를 누르자. 우클릭해서 검사를 눌러도 된다. 오른쪽에 창이 하나 열리는데, 네트워크 탭을 클릭하고 페이지를 새로고침하자. 수십개의 목록이 뜰텐데 맨 위로 스크롤을 올려 naver.com을 클릭하면 옆에 다시 작은 정보창이 바뀐다.

해당 정보는 naver.com을 호출할 때 브라우저에서 보낸 정보와 응답에 대한 정보를 담고 있다. 첫번째 탭이 headers다. 헤더는 HTTP 호출을 할 때 이런저런 설정을 담은 값이라고 보면 된다. 동사무로로 치면 우리가 서류를 발급받기 위해 서식을 작성해서 제출하면, 다시 일정한 서식으로 원하는 서류를 받는 것과 같다. 헤더는 서식에 입력하는 내 자신에 대한 정보 정도로 보면 된다.

headers에서 맨 마지막의 request headers를 보자. 맞다. 위에서 출력했던 r.requests.headers와 비슷하다. 즉 requests가 브라우저에서 보내는 headers를 대신 보내준 것이다.

내 크롬 브라우저에서 호출한 네이버 메인페이지의 requests headers는 다음과 같다.

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36

중요한 건 마지막 User-Agent값이다. 파이썬 코드의 실행값과 비교했을 때 내용이 다르다는 게 보일 것이다. 애초 에러가 났던 네이버 뉴스 페이지에 들어가서 같은 과정을 거칠 때도 동일하다.

브라우저에서는 정상적으로 접속되고 파이썬에서 동일한 호출을 했을 때 에러가 나는 이유는, 네이버에서 자신들이 허용하지 않는 방식으로 접근하는 호출을 걸러내기 때문이고, User-Agent 값을 기준으로 하기 때문이다. 처음엔 403 코드를 돌려주다가, 그 뒤로는 아예 응답 자체를 하기 전에 차단해버린듯 하다. 해당 방식만으로도 어설프게 requests 라이브러리로 접근하는 시도를 상당수 차단해 서버 부하를 줄일 수 있기 때문이 아닐까 상상해본다.

결론, User-Agent 값을 조정하면 requests에서 정상적인 호출이 가능해진다. 다음 편으로.