본문 바로가기
부트캠프(LIKELION AIS7)/수업

[AI스쿨 7기, 2주차] 데이터수집(2) - 네이버 금융 국내 증시의 일별 시세 수집 함수, JSON으로 ETF 데이터 수집

by aimaimee 2023. 4. 11.

220928 0202 실습파일 - 반복문으로 데이터 수집 / 0203 실습파일 - requests, BeautifulSoup, 판다스코드로 데이터 수집, 함수 만들기 / 0204 실습파일 - ETF데이터 JSON으로 수집(tqdm, trange, import time, concat, import requests, GET, POST, HTTP상태코드, BeautifulSoup, 파싱, response문서, html.a, html.find_all, html.select, 일별 시세 수집, user_agent, 파생변수, get_day_list 함수, ETF, JSON, XML 등)

멋쟁이 사자처럼 AI스쿨 7기, 박조은 강사님 강의

✅ 0202 실습 파일

반복문을 사용해 10페이지까지 수집

tqdm, trange

  • 프로세스 진행 상태를 보여주는 시스템
  • range는 파이썬 표준 라이브러리에서 제공하는 기능, tqdm에서 제공하는 trange. 원래는 별도 설치해야 하지만 코랩에서는 설치되어 있어서 import 하면 된다.
  • tqdm 공식 문서 https://github.com/tqdm/tqdm
  • import time
import time
from tqdm import trange

page_no = 1
news_list = []
for page_no in trange(1,11):
    temp = get_one_page_news(item_code, page_no)
    news_list.append(temp)
    time.sleep(0.01)

concat

  • 하나의 데이터프레임으로 만들기
  • 데이터는 news_list라는 변수에 들어있음
  • df_news = pd.concat(news_list)
    df_news.shape

저장하기

  • filename = f"news{itemcode}{item_name}.csv"
  • df_news.to_csv(file_name, index=False)
  • pd.read_csv(file_name)

✅ 0203번 실습파일/ 06번 이론파일

네이버 금융 국내증시 https://finance.naver.com/sise/ 에서 url을 가져온다.
inspect-network : css는 스타일, doc 등 클릭해보면서 원하는 데이터를 찾을 수 있다.

라이브러리 로드

import pandas as pd
import numpy as np
import requests

URL

item_code =
item_name =
page_no = 1
url = f"https://finance.naver.com/item/sise_day.naver?code={item_code}&page={page_no}"
2-1. pd.read_html(url)
테이블을 찾을 수 없다고 오류가 난다.
read_html은 테이블 태그가 있어야 불러올 수 있다.
url 들어가서 inspect 해보면 테이블 태그가 있는데도 불러올 수 없다. 이유는 접근 권한을 막아놔서

requests

  • import requests : 06번 파일 12쪽
  • HTTP for Humans
  • 소스코드만 보여줌
  • GET, HEAD, POST 등과 같은 HTTP 메소드 자료 : 06번 파일 13쪽
  • GET : 쿼리스트링에 담아 보내는 방식/ Payload가면 쿼리스트링
  • POST : 폼 전송. 검색어 입력해서 보내는 방식/ Payload가면 Form Data
  • HTTP 상태 코드 : 200 OK(성공) / 4xx(클라이언트 오류)
  • response = requests.get(url, headers = {"user-agenet" : "Mozilla/5.0"})
  • requests.get( , 헤더스 설정)
  • response.text : 방문하시려는 페이지의 주소가 잘못 입력되었거나 페이지의 주소가 변경 혹은 삭제되어 요청하신 페이지를 찾을 수 없습니다.
  • 위 headers 설정하고 나면 실행되면서 전체 소스 코드를 다 가져온다. 가격, 날짜 등등
  • inspect-header -> user-agent
  • __requests.get(url, headers = {"key" : "value"})

BeautifulSoup

https://www.crummy.com/software/BeautifulSoup/bs4/doc/
HTML 정보를 파싱해서 원하는 정보를 찾아온다.
BeautifulSoup(html_doc)해주면 구조화되어 있지 않던 response 출력 결과 문서를 보기 좋게 정렬해준다.
함수 생성에 beautifulsoup을 사용 안하는 이유는? pd.readhtml에 테이블 태그 기능이 내장되어 있기 때문

  1. from bs4 import BeautifulSoup as bs
  2. bs(response.text)를 html에 넣어서 불러서 사용하자. : html = bs(response.text)
  3. 공식 사이트의 매서드를 보자 : html.title/ .table / pd.DataFrame(html.table)
  • html.a : 링크 태그 하나만 가져온다.
  • html.find_all("a")[:3] : html.find("a")은 하나만 가져오고, html.find_all("a")은 링크태그 다 가져온다.
  • 슬라이싱 넣어서 앞에서 3개만 가져올 수 있다.
  1. find_all과 select의 차이
  • find_all은 attribute 지정을 해야 한다.
  • select는 콕 집어서 필요한 링크만 가져와달라고 지정할 수 있다.
  • find는 soup.find('div').find('p')
  • select는 soup.select_one('div > p')
  • inspect->copy->copy selector
  1. select 지정 순서
  • html.select("body > table") : body에서 table을 찾는다. 구조 지정. hierarchy
  • "table > tr > td"
  • body > table.type2
  • html.select("table.type2")
  • html.select("body > table.type2") 해도 된다.
  • 일별 시세를 셀렉트해서 가져오는 것

판다스 코드로 데이터 수집하기

  1. pd.read_html
  • 네이버 일별 시세는 cp949 인코딩으로 불러 올 수 있다. 기본 인코딩 설정은 utf-8
  • table = pd.read_html(response.text)
  • 출력해보면 NaN 결측치 값과, 맨 밑의 페이징 부분도 같이 출력되어서 제거가 필요하다.
  1. 페이징 테이블 제거
  • table[0]와 table[1]을 확인해보면 tabel[0]에 필요한 데이터가 있다.
  • temp = table[0]
  • table[1] 은 페이징 테이블
  1. .dropna()
  • table[0] 했을 때 나오는 NaN을 dropna를 통해 결측치가 들어있는 row를 제거한다.
  • temp.dropna()

일자별 시세를 페이지별로 수집하는 함수 만들기

뉴스 기사 수집과 일별 시세 수집에서의 차이? - 테이블 접근 권한 때문에 user_agent를 썼다.
def get_day_list(item_code, page_no):

  1. url을 만든다.
    url = f"https://finance.naver.com/item/sise_day.naver?code={item_code}&page={page_no}"
  2. requests를 통해 html 문서를 받아온다.
    response = request.get(url, headers = {"user-agent" : "Mozilla/5.0"})
  3. read_html을 통해 table 태그를 읽어온다.
    table = pd.read_html(response.text)
  4. 결측행을 제거
    temp = table[0]
    temp.dropna()
  5. 데이터 프레임을 반환
    return temp

-> 조은님 코드 : 한줄로 해주는 경우
df_table = pd.read_html(response.text)[0].dropna()

return df_table

반복문을 통한 전체 일자 데이터 수집하기

쏘카도 데이터가 적어서 실습했다. item_code = "403550" , item_name = "쏘카"
for문은 갯수가 정해져 있을 때.(1~10페이지까지 수집하겠다.)
마지막 페이지를 모를 경우 while문

import time

item_code = "373220"
item_code = "LG에너지솔루션"
page_no = 1

# 데이터를 저장할 빈 변수 선언
item_list=[]
curr_day = ""
# curr_day = None해도됨
# last_day랑 비교할 초기값을 아무거나 설정해주어야 함.

while True:
    # 한 페이지의 일별 시세 수집
    df_item = get_day_list(item_code, page_no)
    # 해당 데이터의 마지막 날짜를 가져옴
    last_day = df_item.iloc[-1]["날짜"]

    print(page_no, curr_day, last_day)

    # 해당 데이터의 마지막 날짜와 이전 데이터의 마지막 날짜 비교
    # 맨 첫 데이터에는 이전 데이터 날짜가 없기 때문에 반복문 밖에서 초기화를 해주었음.
    # 이전 데이터의 마지막 날짜와 현재 데이터의 마지막 날짜가 같다면 반복문을 빠져나감.
    if last_day == curr_day:
        break

    # 현재 데이터의 날짜를 다음 턴에서 비교할 수 있게 변수에 값을 넣어줌.
    curr_day = last_day
    # 수집한 일별 시세를 리스트에 추가
    item_list.append(df_item)
    # 다음 페이지를 수집하기 위해 페이지 번호 증가
    page_no = page_no + 1

    time.sleep(0.01)

concat

df_day = pd.concat(item_list)

파생변수 만들기

df_day["종목코드"]=item_code
df_day["종목명"]=item_name

컬럼 순서 변경하기

cols = ['종목코드', '종목명', '날짜', '종가', '전일비', '시가', '고가', '저가', '거래량']
df_day = df_day[cols]
df_day.head(2)

중복데이터 제거

df_day = df_day.drop_duplicates()

파일명 만들고, 저장

  1. 날짜 column의 첫 row 값 확인
    date = df_day.iloc[0]["날짜"]
    date
  2. 종목명, 종목코드, 날짜를 이름으로 하는 csv 파일명 만들기
    filename = f"{item_name}{itemcode}{date}.csv"
    file_name
  3. 파일로 저장
    df_day.to_csv(file_name, index=False)
    제대로 저장되었는지 read로 확인
    pd.read_csv(file_name)

한 개의 함수

def get_day_list(item_code, page_no):

page_no = 1

item_list = []
prev_day = None

while True:
    df_item = get_day_list(item_code, page_no)
    last_day = df_item.iloc[-1]["날짜"]

    print(page_no, prev_day, last_day)

    if last_day == prev_day:
        break

    prev_day = last_day
    item_list.append(df_item)
    page_no = page_no + 1

    time.sleep(0.01)

df_day = pd.concat(item_list)
df_day["종목코드"] =  item_code
df_day["종목명"] = item_name
cols = ['종목코드', '종목명', '날짜', '종가', '전일비', '시가', '고가', '저가', '거래량']
df_day = df_day[cols]

date = df_day.iloc[0]["날짜"]

file_name = f"{item_name}_{item_code}_{date}.csv"
df_day.to_csv(file_name, index=False)

return file_name

✅ 0204 실습 파일, 06번 파일 61쪽

ETF 네이버 금융 사이트의 데이터 수집 https://finance.naver.com/sise/etf.nhn

ETF

1-1. 라이브러리
판다스, 넘파이, requests 불러온다.

1-2. url
url = "https://finance.naver.com/sise/etf.nhn"

1-3. pd. read_html(url)

JSON

지금까지 한 것은 웹스크래핑 방식으로 한 것이고, 지금은 API를 통해 가져오는 것을 실습
키워드 : 키-값 쌍 / XML

  1. requests
    url = "https://finance.naver.com/api/sise/etfItemList.nhn?etfType=0&targetColumn=market_sum&sortOrder=desc"
    response = requests.get(url)
  2. pd.read_html(response.text)
  3. requests의 응답을 json 타입으로 받기
    etf_json = response.json()
  4. result > etfItemList
    etfItemList = etf_json["result"]["etfItemList"]
    print(len(etfItemList))
    etfItemList[-1]
    여기 len() 해주는 거랑 이해 안된다
  5. 데이터프레임 형태로 만들기
    df = pd.DataFrame(etfItemList)
  6. 파생변수 만들기
    df["브랜드"] = df["itemname"].str.split(expand = True)[0]
  • expand=True 리스트 형식이 아니라, 데이터프레임 형태로 반환해달라는 의미
  • 그냥 split()만 하면 요소가 리스트 형식이 된다.

저장

  1. 파일명 만들기
    from datetime import datetime
    today = datetime.today().strftime("%Y-%m-%d")

file_name = f"dtf-{today}_raw.csv

  1. 저장하고 불러오기
    df.to_csv(file_name, index=False)

pd.read_csv(file_name, dtype={"itemcode" : np.object})

  • itemcode 숫자 앞의 0이 지워져서 출력됨. dtype으로 지정해주면 문자형태로 읽어옴.

퀴즈

10/12문제
1. 틀린 것

  • 3번문제:이라 표현된 태그 안의 텍스트를 수집하고자 했을 때, HTMl 태그를 찾는 적절한 BeautifulSoup 코드는? html.select("td.title")
    html.select("td > title")했는데.
    CSS 셀렉터 ID 값은 앞에 #이 붙고, 클래스 값은 앞에 . 이 붙는다고 한다.
  • 9번 문제 : #content > div > div.view-content > div > table > tbody > tr:nth-child(1) > td.data-title.aLeft > a 셀렉터 값에서 a태그만 찾고자 할 때.
    • html.select("#content > div > div.view-content > div > table > tbody > tr > td > a")
    • html.table.select("a")
    • html.select("table > tbody > tr > td > a")
    • html.select("a")는 아니다.
  1. 맞았는데 애매한 것
  • 4번 문제 : HTML태그에서 하이퍼 링크를 의미하는 것 __
  • 7번 문제 : 인덱스 값을 다시 만들고 기존 인덱스를 제거하는 코드 : df.reset_index(drop=True)

해결 과제

  1. item_code = df_krx.loc[df_krx["Name"]=="넷마블", "Symbol"].values[0] 이 코드에서 .values[0]가 의미하는 것 까먹었다. 다시 찾아보기
  2. iloc와 loc 다시 구분 찾아보기
  3. df.reset_index(drop=True) 를 어디서 썼는지 실습 파일 찾아보기

댓글