본문 바로가기
개발 프로젝트/Python

Python 웹 크롤링을 활용한 원자재 가격 데이터 수집 (2)

by BuyAndPray 2020. 11. 22.
반응형

금과 WTI유 각각의 HistoricalDataAjax 파일의 Request URL은 동일하지만 Form Data를 살펴보면 차이가 나는 부분이 몇 가지 있다. 이 차이가 나는 부분이 바로 서버에서 원자재를 구분하는 요소들이다. 아래에서 header는 원자재 이름 + "선물 내역"으로 고정되어 있고, 원자재 별로 curr_idsmlID가 다르기 때문에 해당 페이지에서 이 식별자들을 찾아줘야 한다.

 

추가적으로 원자재 가격 5년 치 데이터를 주별로 받아올 것이기 때문에 st_date는 5년 전 날짜, end_date는 오늘 날짜 그리고 interval_sec는 Weekly로 변경해서 요청을 보낼 것이다. 그 아래 sort 부분은 정렬 방법을 나타내는 것이므로 수정할 필요는 없다.

 

금과 WTI유의 Form Data

curr_id와 smlID 찾기

골드 선물 과거 데이터에서 오른쪽 마우스를 클릭 > 페이지 소스 보기를 누른 뒤 Ctrl + F를 눌러 smlID에 해당되는 300004를 검색하면 아래와 같은 script를 찾을 수 있다. 해당 script에서 pairId는 Form Data에서 curr_id와 동일하기 때문에 POST 데이터 요청에 필요한 데이터를 모두 수집할 수 있다.

 

pairId와 smlId

 

python으로 한 번 확인해보자. Investing.com은 봇의 접근을 막고 있는데 이것은 User-Agent를 설정해서 해결할 수 있다. requests 결과를 파싱 한 뒤 script 태그 중에서 window.histDataExcessInfo 문자열을 포함한 태그만 찾았다.

 

import requests
from bs4 import BeautifulSoup

headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'}

url = r"https://kr.investing.com/commodities/gold-historical-data"

response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.content, 'html.parser')

for script in soup.findAll('script'):
    if script.string and "window.histDataExcessInfo" in script.string:
        print(script.string)
    
''' Result

        window.histDataExcessInfo = {
        pairId: 8830,
        smlId: 300004    }
        
'''

 

결과가 잘 나오는 것을 확인했으니 정규표현식을 사용해서 원하는 숫자만 추출한다.

 

import re


''' (중략) '''

if script.string and "window.histDataExcessInfo" in script.string:
        histData = script.string.strip().replace("\n", "").replace(" ", "")
        curr_id, smlId = re.findall("\d+", histData)
        print(curr_id, smlId)
        
 ''' Result
 8830 300004
 '''

 

원자재 과거 가격 가져오기

이제 아래와 같은 순서로 원자재 가격 데이터를 가져와보겠다. 

 

  1. 원자재 선물 가격 페이지에서 원자재 리스트를 뽑음
  2. 해당 상품의 과거 데이터 페이지(상품명-historical-data)에서 curr_id와 smlID를 추출
  3. HistoricalDataAjax로 요청을 보내 5년 치 데이터를 추출

Ajax로 데이터를 주고받기 때문에 HistoricalDataAjax로 요청을 보내는 헤더에는 'X-Requested-With' : 'XMLHttpRequest' 옵션을 추가해주어야 한다.

 

최종 코드는 아래와 같다. 마지막 원자재 과거 가격 데이터의 응답은 HTML 형식으로 나오는데 이 HTML에서 BeautifulSoup를 활용해 가격을 뽑아내는 것은 앞부분과 거의 유사하기 때문에 생략하도록 하겠다. 

 

import requests, re, time
from bs4 import BeautifulSoup

headers = {
    'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
    'X-Requested-With' : 'XMLHttpRequest'
}

url = r"https://kr.investing.com/commodities/real-time-futures"
baseURL = r"https://kr.investing.com"

''' 1. 원자재 리스트 뽑기 '''
commodities = []

response = requests.get(url, headers=headers)
soup = BeautifulSoup(response.content, 'html.parser')

commodityDiv = soup.find('div', {'id': 'cross_rates_container'})

for a_tag in commodityDiv.find('tbody').find_all('a'):
    href = a_tag.get("href") # ex) /commodities/gold
    title = a_tag.get("title") # ex) 금 선물
    
    if "commodities" in href:
        commodities.append((baseURL + href, title)) # url, title 튜플로 저장

''' 2. curr_id와 smlID를 추출 '''

ids = []

for URL, _ in commodities:
    historicalURL = URL + "-historical-data"
    
    response = requests.get(historicalURL, headers=headers)
    soup = BeautifulSoup(response.content, 'html.parser')

    for script in soup.findAll('script'):
        if script.string and "window.histDataExcessInfo" in script.string:
            histData = script.string.strip().replace("\n", "").replace(" ", "")
            curr_id, smlId = re.findall("\d+", histData)
            ids.append((curr_id, smlId)) # 투플로 저장
                    
    time.sleep(0.2)

''' 3. HistoricalDataAjax 5년치 데이터를 추출 '''

formData = {
    "curr_id" : "",
    "smlID" : "",
    "header" : "",
    "st_date" : "2016/01/01",
    "end_date" : "2020/11/22",
    "interval_sec" : "Weekly",
    "sort_col" : "date",
    "sort_ord" : "DESC",
    "action" : "historical_data"
}

POSTURL = r"https://kr.investing.com/instruments/HistoricalDataAjax"

for (_, title), (curr_id, smlID) in zip(commodities, ids):
    formData["curr_id"] = curr_id
    formData["smlID"] = smlID
    formData["header"] = title + " 내역"
    
    response = requests.post(POSTURL, headers=headers, data=formData)
    f = open(title, "w")
    f.write(response.text)
    f.close()
    
    time.sleep(0.2)

 

원자재 가격과 주가와의 상관관계 분석 시리즈

반응형

댓글