2023-02-10 작성

파이썬 웹크롤링으로 주식 정보 가져와서 엑셀 저장하기

지난번 포스팅에서는 OpenAPI를 이용하여 이미지를 수집해보았다. 이번에는 크롤링을 이용하여 웹 페이지의 데이터를 수집해 보려고 한다.

크롤링 대상은 KOSPI 일별 체결가로 정했지만, 만약 주식에 관심이 있다면 네이버 금융 주식거래 데이터를 크롤링할 수 있고, 부동산에 관심 있다면 KB시세 데이터를 크롤링하는 등 본인이 원하는 사이트를 대상으로 선택해도 된다.

웹사이트의 데이터를 수집해서 아래와 같이 엑셀 파일로 저장하는 것이 이번 포스팅의 목표이다.

최종 목표

사전 준비

1. 파이썬과 주피터가 설치되어 있지 않다면 파이썬 3.11 설치와 주피터 노트북 사용법 포스팅을 참고하여 설치한다.

2. 크롤링을 하기 위해 아래 라이브러리가 필요함을 숙지한다.

  • 웹 페이지 정보를 가져오기 위해, requests 라이브러리 사용
  • HTML 소스 파싱하여 원하는 정보 추출하기 위해, BeautifulSoup 라이브러리 사용

3. 크롤링에 필요한 라이브러리를 설치한다. '윈도우키+R'을 눌러서 나타난 실행창에 'cmd'를 입력하여 창을 켠다. 그리고 아래 명령어를 입력하여 라이브러리를 설치하자.

# requests 라이브러리 설치
pip install requests

# BeautifulSoup 라이브러리 설치
pip install bs4

# 구문 분석하기 위한 parser 설치
pip install lxml

1. 크롤링할 목표 대상 정하기

네이버 증권 사이트에서 코스피 일별 체결가를 수집해 볼 것이다. 아래처럼 네이버에서 증권 > 코스피 메뉴로 들어간다.

 
코스피 화면에서 스크롤을 내리다 보면 '일별시세' 란이 나온다. 포스팅 작성 시점인 2월 7일 기준으로 어제까지의 코스피 시세를 확인할 수 있는데, 여기서 필요한 데이터인 날짜, 체결가만 추출하려고 한다.

2. URL 정보 획득하기

일별시세 하단에는 페이징(paging)이 있다. 1페이지에서 2페이지로 이동하면 저번주의 날짜 기준으로 시세를 보여준다. 즉, 페이지 정보를 가져오면 과거 코스피 시세도 가져올 수 있다.

일별시세 URL 정보를 얻어보자. 크롬에서 F12키를 눌러 개발자도구를 연다. 아래 사진의 왼쪽은 네이버 증권, 오른쪽에는 개발자도구를 나란히 띄운 모습이다. [Network] 탭에서 🚫로 네트워크 내역을 클리어하자.

 
이 상태에서 2페이지로 이동하면, 페이지를 구성하는데 필요한 수많은 자원들이 로딩된다. 이 중에서 2페이지로 요청 중인 request url을 취득할 수 있다. 아래의 sise_index_day.naver?code=KOSPI&page=2 이다.

 
해당 링크를 더블클릭 해보면 https://finance.naver.com/sise/sise_index_day.naver?code=KOSPI&page=2 링크에서 2페이지의 표만 띄워진다. URL의 page 값을 3페이지, 4페이지로 변경하면 해당 페이지 정보로 값을 가져온다.

즉, 최종적인 일별시세 URL 정보는 https://finance.naver.com/sise/sise_index_day.naver?code=KOSPI&page=페이지번호가 된다.

3. HTML 소스 파싱하기

위에서 얻은 URL을 기반으로, 웹 페이지의 HTML 소스를 파싱 해볼 것이다. 아래 소스의 source 변수를 출력해 보면 HTML 소스가 출력된다.

import requests
import bs4

# 1. 요청 파라미터 세팅
code = "KOSPI"
page = 1

url = "https://finance.naver.com/sise/sise_index_day.naver?code={code}&page={page}".format(code = code, page = page)

# 2. 요청
response = requests.get(url)

# 3. 요청 성공시
if response.status_code == 200:
    
    # 4. HTML 파싱
    source = bs4.BeautifulSoup(response.text, 'lxml')
    print(source)
    # print(source.prettify())
        
# 5. 요청 실패시
else: 
    print("error code : ", response.status_code)

4. 원하는 정보 추출하기

이제 HTML 소스에서 원하는 정보를 추출해 보자. 위에서 파싱 된 HTML 소스에서 날짜, 체결가를 구할 수 있겠지만, 개발자도구를 이용하여 추출하려고 한다. 

다시 네이버 금융 페이지의 일별시세 표에서, 개발자도구 마우스 모양을 클릭한 뒤 일별시세 날짜를 클릭한다. 그러면 선택한 element가 표시되는데 이것이 해당 날짜의 태그이다.


날짜 태그는 'td'이고 클래스는 'date'이다. 이것을 가지고 파이썬에서 찾아보면 다음과 같다.

# 날짜 목록
date_list = source.find_all('td','date')

find_all 메서드는 소스에서 찾고자 하는 태그들을 찾아 리스트 형식으로 반환한다. 뒤에 인자는 클래스도 써줄 수 있다. 이러면 태그가 td이고 클래스가 date인 태그들이 리스트형식으로 주어진다.

체결가도 마찬가지로 데이터를 가지고 오면 다음과 같다.

# 체결가,등락률,거래량,거래대금 목록
price_list = source.find_all('td','number_1')

하지만 체결가는 이렇게 가져오면 문제가 발생한다. 아래의 개발자도구를 확인해 보면 체결가, 등락률, 거래량, 거래대금이 모두 같은 'number_1'  클래스에 포함되어 있다. 체결가의 경우, 클래스 하나로 추출하기는 어렵다.


이를 토대로 코드를 구성해 보았다. price_list 변수에는 해당 행의 '체결가, 등락률, 거래량, 거래대금'이 세트로 묶여있으니 체결가(price)는 0번째, 4번째, 8번째... 4의 배수임을 기억하자.

import requests
import bs4

# 요청 파라미터 세팅
code = "KOSPI"
page = 1

url = "https://finance.naver.com/sise/sise_index_day.naver?code={code}&page={page}".format(code = code, page = page)

# 요청
response = requests.get(url)

# 요청 성공시
if response.status_code == 200:
    
    # HTML 파싱
    source = bs4.BeautifulSoup(response.text, 'lxml')
    
    # 1. 소스에서 태그를 이용하여 값 추출
    date_list = source.find_all('td','date') # 날짜 목록
    price_list = source.find_all('td','number_1') # 체결가,등락률,거래량,거래대금 목록
    daily_list = [] # 날짜,체결가만 담을 목록
    
    # 2. 날짜,체결가 목록 출력 
    for n in range(len(date_list)):
    
    	# 3. 해당 행의 날짜, 체결가를 추출
        date = date_list[n].string
        price = price_list[n*4].string

        daily_list.append(date)
        daily_list.append(price)

    print(daily_list)

# 요청 실패시
else: 
    print("error code : ", response.status_code)

아래는 daily_list의 출력 결과다. 일별시세의 1페이지에 해당하는 날짜, 체결가를 가지고 왔다.

['2023.02.10', '2,469.73', '2023.02.09', '2,481.52', '2023.02.08', '2,483.64', '2023.02.07', '2,451.71', '2023.02.06', '2,438.19', '2023.02.03', '2,480.40']

5. 마지막페이지 번호 가져오기

1페이지를 가져와 봤는데 2페이지, 3페이지, 마지막 페이지도 가져와야 제대로 된 일별시세를 가져올 수 있다. 다시 네이버 금융 화면에서 페이징의 '맨뒤'를 클릭한 뒤, 개발자도구로 마지막 페이지 번호를 클릭한다.

 
내 경우 1,432가 마지막 페이지 번호이다. 이 번호를 기억해서 아까같이 개발자도구로 찾아도 되지만, 좀 더 쉬운 방법으로 print(source)로 HTML 소스를 출력하여, 본인의 마지막 페이지번호를 검색하면 아래와 같이 쉽게 찾아낼 수 있다.
즉, 마지막 페이지 번호의 태그는 'td'이고 클래스는 'pgRR'이다.

<td class="pgRR">
	<a href="/sise/sise_index_day.naver?code=KOSPI&amp;page=1432">맨뒤
		<img alt="" border="0" height="5" src="https://ssl.pstatic.net/static/n/cmn/bu_pgarRR.gif" width="8"/></a>
</td>

여기까지의 소스이다.

import requests
import bs4

# 요청 파라미터 세팅
code = "KOSPI"
page = 1

url = "https://finance.naver.com/sise/sise_index_day.naver?code={code}&page={page}".format(code = code, page = page)

# 요청
response = requests.get(url)

# 요청 성공시
if response.status_code == 200:
    
    # HTML 파싱
    source = bs4.BeautifulSoup(response.text, 'lxml')
    
    # 소스에서 태그를 이용하여 값 추출
    date_list = source.find_all('td','date') # 날짜 목록
    price_list = source.find_all('td','number_1') # 체결가,등락률,거래량,거래대금 목록
    daily_list = [] # 날짜,체결가만 담을 목록
    
    # 날짜,체결가 목록 출력 
    for n in range(len(date_list)):
        date = date_list[n].string
        price = price_list[n*4].string

        daily_list.append(date)
        daily_list.append(price)
    
    # 1. 마지막페이지 URL 추출
    last_page = source.find('td', class_='pgRR').find('a')['href']
    print(last_page)
    
    # 2. 마지막페이지 번호 추출
    # 주소 예시 : /sise/sise_index_day.naver?code=KOSPI&page=1432
    last_page = last_page.split('&')[1] # page=1432 추출
    last_page = last_page.split('=')[1] # 1432 추출
    last_page = int(last_page) # 숫자형 변수로 변환

# 요청 실패시
else: 
    print("error code : ", response.status_code)

6. 함수로 만들어서 반복 호출하기(최종 소스)

일별시세의 첫 번째 페이지의 정보와 마지막페이지 번호 값을 가져와봤다. 이제 이 부분을 함수로 만들어서, 마지막페이지 수만큼 요청하여 딕셔너리에 담아보고, 추출한 결괏값을 엑셀로 추출해 봤다.

import requests
import bs4
import openpyxl
  
# 일별시세 조회 함수
def search_daily(code, page, last_page):
    url = "https://finance.naver.com/sise/sise_index_day.naver?code={code}&page={page}".format(code = code, page = page)
    
    # 3. 요청
    response = requests.get(url)
    
    # 4. 요청 성공시
    if response.status_code == 200:
        
        # 5. HTML 파싱
        source = bs4.BeautifulSoup(response.text, 'lxml')

        # 6. 날짜,체결가 목록 추출
        date_list = source.find_all('td','date') # 날짜 목록
        price_list = source.find_all('td','number_1') # 체결가,등락률,거래량,거래대금 목록
        
        # 7. 날짜,체결가 목록 담기
        for n in range(len(date_list)):
            date = date_list[n].string
            price = price_list[n*4].string
                
            # 8. 딕셔너리에 저장
            daily_list[date] = price

        # 9. 최초 함수 호출시 마지막페이지 정보 가져오기
        if last_page == 0:
            last_page = source.find('td','pgRR').find('a')['href'] # 마지막페이지 URL 추출
            last_page = last_page.split('&')[1]
            last_page = last_page.split('=')[1]
            last_page = 100 #int(last_page) # 마지막페이지 번호 추출 (임시로 100페이지로 설정함)
                
        # 10. 페이지가 마지막 페이지까지 돌 수 있도록 하기
        if page < last_page:
            page += 1
            search_daily(code, page, last_page)
        
        return daily_list
        
    # 요청 실패시
    else:
        print("error code : ", response.status_code)

#------------------------------------------------------------------------------------------------------------
daily_list = dict()

# 1. 입력 파라미터 세팅
code = "KOSPI"
page = 1
last_page = 0

# 2. 일별시세 조회
daily_list = search_daily(code, page, last_page) # 1페이지부터 조회 시작

# 11. 조회한 결과값을 엑셀 파일로 저장하기 위해 엑셀항목 세팅(import openpyxl 처리 필요)
excel_file = openpyxl.Workbook()
excel_sheet = excel_file.active
excel_sheet.column_dimensions['B'].width = 20
excel_sheet.column_dimensions['C'].width = 20
excel_sheet.append(['순번', '날짜', '체결가'])

# 12. 조회한 결과값 수만큼 엑셀파일에 저장
count = 0
for key, val in dict(daily_list).items():
    excel_sheet.append([count+1, key, val])
    count += 1   
excel_file.save('daily_price.xlsx')
excel_file.close()

실행 결과는 작업 중인 디렉토리에 엑셀 파일로 저장된다.

지금까지 웹 크롤링을 이용하여 네이버 금융 KOSPI 데이터를 가져와봤다. 만약 KOSPI 말고 KOSDAQ의 일별시세를 가져오고 싶다면 code 변수에 'KOSDAQ'을 입력 후 실행해보자. 네이버 코스피, 코스닥의 화면 레이아웃이 비슷하기 때문에 다른 지수 값도 가져올 수 있다.