https://developers.naver.com/main/
여기에 들어가면 네이버 오픈 API들을 쓸 수 있다.
오픈 API 목록은 다음과 같다.
여기서 "검색" API를 활용해서 네이버의 데이터를 끌어오자!!
API를 사용하지 않고 자체적으로 크롤링을 할 수도 있는데 그럴 경우 너무 빠르게 크롤링을 하면 디도스 공격 들어온 줄 알고 IP를 차단해 버린다(..) 그걸 방지하려고 느리게 할 수도 있는데 그럼 또 너무 느리게 걸린다.
그러니까 API를 사용해서 검색 결과들을 쭈루룩 가져오면 쉽고 편하다!
위에서 오픈 API 이용 신청 누른 다음에 개발 가이드를 보자.
https://developers.naver.com/docs/serviceapi/search/blog/blog.md#%EB%B8%94%EB%A1%9C%EA%B7%B8
아주 상세하게 설명이 있다. (심지어 구현 예제도 있다.)
검색에서도 블로그 검색을 할지 책 검색을 할 지 선택할 수 있다.
나는 책 검색을 할 거임.
요런 기본적인 정보 확인.
중요한 것은 바로 이 parameter다. 그래야 어떤 방식으로 데이터를 가져올 건지 지정할 수 있다.
아모튼~~ 이것들을 써서 네이버 도서 검색으로 "주식"에 해당하는 결과들을 뽑아오자. 내가 처음에 짠 코드는 이렇다. (chatGPT의 도움을 받아 예쁘게 다듬음)
!pip install aiohttp
!pip install asyncio
!pip install pandas
!pip install nest_asyncio
import os
import sys
import urllib.request
import aiohttp
import asyncio
import pandas as pd
import json
import nest_asyncio
import urllib.parse
%%time
client_id = "님들 아이디 쓰셈"
client_secret = "님들 비밀번호 쓰셈"
query = "주식"
encText = urllib.parse.quote(query)
start = 1
data = []
while True:
url = f"https://openapi.naver.com/v1/search/book.json?query={encText}&start={start}&display=100"
request = urllib.request.Request(url)
request.add_header("X-Naver-Client-Id", client_id)
request.add_header("X-Naver-Client-Secret", client_secret)
try:
response = urllib.request.urlopen(request)
rescode = response.getcode()
if rescode == 200:
response_body = response.read().decode('utf-8')
data.append(json.loads(response_body))
else:
print(f"Error Code: {rescode}")
break
except urllib.error.HTTPError as e:
print(f"HTTP Error: {e.code} - {e.reason}")
break
except urllib.error.URLError as e:
print(f"URL Error: {e.reason}")
break
total = data[-1].get("total", 0)
if start + 100 > total or start >= 901:
break
start += 100
print(data)
하나씩 풀어보면,
%%time
이 코드를 통해 전체 프로그램이 실행한 시간을 기록한다.
Wall time과 CPU times가 나오는데, 전자는 셀이 실행된 전체 시간을 의미하고, 후자는 CPU가 사용된 시간만을 의미한다. 그러니 후자는 프로그램의 complexity를 볼 때 사용한다.
client_id = "xATd5DXnJ8RMxh6x80an"
client_secret = "QuaLgPepky"
query = "주식"
encText = urllib.parse.quote(query)
위의 id와 secret은 님들이 네이버에 오픈 API 신청하면서 받은 아이디와 비번을 입력하는 곳이다.
query는 검색어인데 한글을 그대로 주소에 넣으면 물론 주소창에 넣으면 작동하지만 여기에 넣으면 작동하지 않기 때문에 특별히 URL 인코딩을 해야 한다.
start = 1
data = []
while True:
url = f"https://openapi.naver.com/v1/search/book.json?query={encText}&start={start}&display=100"
request = urllib.request.Request(url)
request.add_header("X-Naver-Client-Id", client_id)
request.add_header("X-Naver-Client-Secret", client_secret)
start는 몇 번째 페이지의 검색 결과를 요청할 것인지를 나타내고, data는 그렇게 뽑은 데이터들을 저장하는 곳이다.
그리고 아주 중요한 url을 완성해야 한다. formatter를 이용해서 encText를 집어넣고, start의 값도 매번 바뀌어야 하기 때문에 {start}를 해서 넣었다. 이게 뭔지 모른다면 님들은 설명서를 제대로 안 읽은 거임,,
알간?
주소 뒤에 ?를 붙이고 parameter들을 &를 사용해서 계속 붙이면 된다.
그 아래 request는 HTTP 요청 객체인데 여기에 url을 붙이고 header도 추가해서 이 조선 통신사가 무사히 네이버의 시스템 내부에 침입하여 정보를 빼올 수 있게 옷을 입혀줘야 한다.
try:
response = urllib.request.urlopen(request)
rescode = response.getcode()
if rescode == 200:
response_body = response.read().decode('utf-8')
data.append(json.loads(response_body))
else:
print(f"Error Code: {rescode}")
break
except urllib.error.HTTPError as e:
print(f"HTTP Error: {e.code} - {e.reason}")
break
except urllib.error.URLError as e:
print(f"URL Error: {e.reason}")
break
위에서 만든 request 객체를 urllib.request.urlopen() 안에 들여보내서 네이버 API에 HTTP GET 요청을 보낸다. 빼낸 값을 response에 저장하면 된다.
이 값이 200으로 잘 나왔다면 읽고 디코딩해서 response_body안에 넣어주고, json으로 변환해서 우리가 처음에 만든 data 안에 넣어두면 된다. 200이 아니라면? Error Code가 났음을 알려주면 된다.
다른 오류가 발생했다면 try ~ except ~ 로 예외를 보내서 무슨무슨 오류가 났다고 처리하면 된다.
total = data[0].get("total", 0)
if start + 100 > total or start >= 901:
break
start += 100
start가 전체 검색 페이지를 넘거나 1000을 넘을 경우 break를 해주면 된다. 왜 1000이냐?
이걸 안 읽은 게 분명함,, 읽으랬잖슴,, start는 최댓값이 1,000이다.
그런데 경우에 따라서는 검색 페이지가 1,000페이지도 넘을 수 있지 않을까? 그런 경우에는 뭐 수동으로 하거나 정식으로 제휴 신청해서 돈 내고 쓰시면 됨 ㅎ
아무튼 이렇게 하면 결과가 잘 나온다.
pandas dataframe으로 빼보면 적당히 잘 나왔다는 것을 확인할 수 있다.
그런데 저 코드는 시간이 무려 6초나 걸리는 것을 알 수 있다!
더 줄일 수는 없을까?
무지성으로 chatGPT한테 시간을 더 줄여달라고 징징거려 봤다.
async, await를 사용하라고 한다.
%%time
client_id = "님들 아이디 쓰셈"
client_secret = "님들 비밀번호 쓰셈"
query = "주식"
encText = urllib.parse.quote(query)
async def fetch(session, url, headers):
async with session.get(url, headers=headers) as response:
if response.status == 200:
return await response.json()
else:
print(f"Error Code: {response.status}")
return None
async def main():
headers = {
"X-Naver-Client-Id": client_id,
"X-Naver-Client-Secret": client_secret,
}
tasks = []
start = 1
async with aiohttp.ClientSession() as session:
while start <= 900:
url = f"https://openapi.naver.com/v1/search/book.json?query={encText}&start={start}&display=100"
tasks.append(fetch(session, url, headers))
start += 100
responses = await asyncio.gather(*tasks)
fast_data = [response for response in responses if response is not None]
print(fast_data)
# Run the event loop
asyncio.run(main())
시간이 아주 획기적으로 줄었다.
이 코드는 여러 HTTP 요청을 동시에 보내고 응답을 기다림으로써 속도를 높일 수 있다.
러프하게 대충 뜯어보면,
async def fetch(session, url, headers):
async with session.get(url, headers=headers) as response:
if response.status == 200:
return await response.json()
else:
print(f"Error Code: {response.status}")
return None
fetch함수를 정의할 때 앞에 async를 붙였다. 비동기 처리 방식을 사용할 수 있는 권리를 얻은 셈이다!
다음 줄을 유심히 봐야 하는데 async with을 씀으로써 session.get()로 HTTP GET 요청을 "비동기적으로" 보내고 응답이 도착할 때까지 다른 일을 한다. 그러다가 응답이 도착하면 이를 response에 저장한다.
나머지는 비슷한데 status가 200으로(정상적으로) 찍히면 이를 json으로 parsing 하는데 이게 완료될 때까지 다른 일을 한다.
async def main():
headers = {
"X-Naver-Client-Id": client_id,
"X-Naver-Client-Secret": client_secret,
}
tasks = []
start = 1
async with aiohttp.ClientSession() as session:
while start <= 900:
url = f"https://openapi.naver.com/v1/search/book.json?query={encText}&start={start}&display=100"
tasks.append(fetch(session, url, headers))
start += 100
responses = await asyncio.gather(*tasks)
fast_data = [response for response in responses if response is not None]
print(fast_data)
# Run the event loop
asyncio.run(main())
별 다른 건 없는데 여기서 "async with aiohttp.ClientSession() as session:"를 좀 보자.
ClientSession은 aiohttp 라이브러리에 들어있는 클래스다. 이 클래스는 HTTP 클라이언트 세션을 나타내는데 이를 통해 여러 HTTP 요청을 효율적으로 관리할 수 있다.
세션은 client와 server 간의 일시적 연결 상태를 나타내는 개념이다.
HTTP는 원래 상태를 유지하지 않는(stateless) 프로토콜이다. 각 요청이 독립적이고 이전 요청을 기억하지 못한다. 그런데 세션을 사용하면 상태를 유지할 수 있다. 그러니까 로그인한 상태를 유지할 수 있는 셈이다.
그리고 tasks라는 list 안에 fetch 함수들을 넣어두기만 하고 실행은 안 했는데 그 이유는 일단 다 넣어둔 다음에 한 번에
"await asyncio.gather(*tasks)"로 처리하기 위해서다. 이 함수를 사용하면 tasks 함수 안에 있는 모든 비동기 작업들이 동시에 실행된다. 그러니까 빠른 것이다!
그렇게 responses들을 완성하고 이 중에서 None이 아닌 값들만 fast_data안에 넣어두면 된다.
뭔가 해두고 답을 기다리기 전에 이것저것 하니까 시간이 효율적으로 사용되는 것이다.
'분명 전산학부 졸업 했는데 코딩 개못하는 조준호 > Web' 카테고리의 다른 글
프론트엔드 & 백엔드 포함된 초간단 서비스 만들어 보기 - (1) 프론트엔드의 주요 라이브러리/프레임워크 (0) | 2024.10.04 |
---|---|
공공데이터포털에서 오픈 API를 사용해서 치킨집 데이터 긁어오기 (7) | 2024.07.23 |
CSS 적용 우선순위 - 기타 중요한 사항 총정리 (1) | 2024.07.02 |
CSS 적용 우선순위 - 선택자끼리의 비교 (1) | 2024.07.02 |
CSS 적용 우선순위 - 스타일 시트끼리의 비교 (0) | 2024.07.02 |
한국은행 들어갈 때까지만 합니다
조만간 티비에서 봅시다