금과 WTI유 각각의 HistoricalDataAjax 파일의 Request URL은 동일하지만 Form Data를 살펴보면 차이가 나는 부분이 몇 가지 있다. 이 차이가 나는 부분이 바로 서버에서 원자재를 구분하는 요소들이다. 아래에서 header는 원자재 이름 + "선물 내역"으로 고정되어 있고, 원자재 별로 curr_id와 smlID가 다르기 때문에 해당 페이지에서 이 식별자들을 찾아줘야 한다.
추가적으로 원자재 가격 5년 치 데이터를 주별로 받아올 것이기 때문에 st_date는 5년 전 날짜, end_date는 오늘 날짜 그리고 interval_sec는 Weekly로 변경해서 요청을 보낼 것이다. 그 아래 sort 부분은 정렬 방법을 나타내는 것이므로 수정할 필요는 없다.
curr_id와 smlID 찾기
골드 선물 과거 데이터에서 오른쪽 마우스를 클릭 > 페이지 소스 보기를 누른 뒤 Ctrl + F를 눌러 smlID에 해당되는 300004를 검색하면 아래와 같은 script를 찾을 수 있다. 해당 script에서 pairId는 Form Data에서 curr_id와 동일하기 때문에 POST 데이터 요청에 필요한 데이터를 모두 수집할 수 있다.
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
'''
원자재 과거 가격 가져오기
이제 아래와 같은 순서로 원자재 가격 데이터를 가져와보겠다.
- 원자재 선물 가격 페이지에서 원자재 리스트를 뽑음
- 해당 상품의 과거 데이터 페이지(상품명-historical-data)에서 curr_id와 smlID를 추출
- 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)
원자재 가격과 주가와의 상관관계 분석 시리즈
'개발 프로젝트 > Python' 카테고리의 다른 글
Python을 활용해 원자재 가격과 주가와의 상관관계 분석 (2) | 2020.12.02 |
---|---|
Python 라이브러리를 활용한 코스피/코스닥 주가 데이터 수집 (0) | 2020.11.29 |
Python 웹 크롤링을 활용한 원자재 가격 데이터 수집 (1) (1) | 2020.11.22 |
댓글