몇 번째 td 찾기 selenium

몇 번째 td 찾기 selenium

퀀트 투자의 최대 장점은 데이터만 있다면 동일한 로직을 전 세계 어떤 곳에도 적용이 가능하다는 점이다. 그러나 여기서 가장 큰 문제는 '전 세계 데이터'를 어떻게 구하는가의 문제이다. 기관 투자자의 경우 다양한 소스를 통해 데이터를 구할 수 있지만, 개인 투자자의 경우 크롤링을 통해 이러한 문제를 해결할 수 있다.

금융 데이터를 수집하는데 있어 가장 먼저 수집해야할 정보는 '티커 리스트'다. 즉 한 국가의 주식시장 내에서 어떠한 종목들이 거래되고 있는가를 알아야 하는 것이다. 한국 시장의 경우 '한국거래소' 홈페이지에서 쉽게 구할 수 있지만 모든 국가의 거래소에서 이러한 정보를 일일이 수집하는 것은 매우 번거롭다.

인베스팅닷컴의 스크리너 기능을 사용하면 이러한 문제를 굉장히 쉽게 해결할 수 있다. 해당 페이지에는 각 국가별 상장종목 리스트를 제공하고 있다.

인베스팅닷컴에 접속한 후 [Markets - Stocks - Stock Screener] 항목을 클릭한다.

몇 번째 td 찾기 selenium

URL은 다음과 같다.

https://www.investing.com/stock-screener/?sp=country::5|sector::a|industry::a|equityType::a%3Ceq_market_cap;1

그 후 [United States]로 설정된 국가를 다른 국가(예: Japan)로 설정해보자. 하단의 Screener Results가 일본 주식 종목들로 바뀌며 URL이 다음과 같이 바뀐다.

https://www.investing.com/stock-screener/?sp=country::35|sector::a|industry::a|equityType::a%3Ceq_market_cap;1

URL을 뜯어보면 country 뒷 부분이 5에서 35로 변경되었다. 즉 미국의 국가 코드는 5, 일본의 국가 코드는 35 임을 유추할 수 있다. 또한 국가를 바꿀때마다 페이지는 그대로 유지된채 URL 주소와 내부의 내용만 바뀌는 동적 페이지임을 알 수 있다. 이러한 홈페이지의 내용은 일반적인 크롤링으로는 수집할 수 없으며, 셀레니움을 이용한 동적 크롤링으로 수집해야 한다.

셀레니움을 이용한 크롤링을 하기 위해 먼저 크롬 드라이버를 설치한다. 크롬에서 [설정-Chrome 정보]를 확인해보면 본인의 크롬 버젼 정보를 확인할 수 있다.

몇 번째 td 찾기 selenium

본인의 버젼에 맞는 크롬 드라이버를 아래 사이트에서 찾아 다운로드 한 후, 압축을 푼다.

그 후 파이썬에서 selenium 패키지를 설치하도록 하자.

이제 위에서 살펴본 일본 주식 티커 리스트를 받아보도록 하자.

from selenium import webdriver from selenium.webdriver.common.by import By from bs4 import BeautifulSoup import math driver = webdriver.Chrome("C:/Users/doomoolmori/Dropbox/My Book/quant_python/chromedriver.exe")

몇 번째 td 찾기 selenium

Chrome() 내부에 위에서 받은 크롬 드라이버 주소를 입력하면 테스트용 크롬창이 열린다.

url = 'https://investing.com/stock-screener/?sp=country::35|sector::a|industry::a|equityType::a%3Ceq_market_cap;1' driver.get(url)

몇 번째 td 찾기 selenium

driver.get() 내에 접속하고 싶은 URL을 입력하면 해당 주소로 이동된다.

html = BeautifulSoup(driver.page_source, 'lxml') print(html)

BeautifulSoup() 내에 driver.page_source를 입력하면, 현재 열려있는 창의 HTML 정보를 가져올 수 있다. 이제 해당 데이터를 이용해 각종 정보를 가져오도록 하자.

몇 번째 td 찾기 selenium

먼저 개발자도구 화면을 통해 종목명의 HTML 정보를 확인해보면 [symbol left bold elp] 클래스에 위치하고 있다. 이를 이용해 종목명 정보만 추출하도록 하자.

data_name = html.find_all(class_ = 'symbol left bold elp') [i.get_text() for i in data_name] ['Toyota Motor', 'Sony', 'Nippon Telegraph & Telephone Corp', 'Keyence', 'KDDI Corp.', .....]

find_all() 함수를 통해 해당 클래스명의 내용을 모두 추출한다. 그 후 list 내에서 for loop 구문을 통해 get_text() 즉 텍스트 데이터만 출력한다.

이제 가장 중요한 티커 정보를 찾아보도록 하자.

몇 번째 td 찾기 selenium

티커 정보는 td 태그의 data-column-name 속성 중 viewData.symbol 속성 값에 위치하고 있다. 이를 이용해 티커 정보만 추출해보자.

data_ticker = html.select('td[data-column-name = "viewData.symbol"]') [i.get_text() for i in data_ticker] ['7203', '6758', '9432', '6861', '9433', ...]

이번에는 select() 함수를 통해 데이터를 추출한다.select(태그[속성 = 속성값]) 형태를 통해 우리가 원하는 티커 정보만 추출할 수 있다.

위의 과정은 1페이지의 정보만을 추출하는 것이었다. 그렇다면 다음 페이지의 내용은 어떻게 추출할까? 페이지 하단의 페이지네이션 부분에서 [2]를 클릭해보자.

몇 번째 td 찾기 selenium

URL이 다음과 같이 변경되었다.

https://www.investing.com/stock-screener/?sp=country::35|sector::a|industry::a|equityType::a|exchange::20%3Ceq_market_cap;2

즉, market_cap; 뒤의 숫자가 페이지 부분에 해당한다. 문제는 몇번째까지 페이지가 존재하는지 알 수 없다는 점이지만, 매우 쉽게 유추할 수 있다. 페이지의 Screener Results 뒤에 있는 숫자를 통해서 말이다. 한 페이지에는 50 종목이 표현되므로, 해당 숫자를 50으로 나눈 후 올림을 해주면 그 숫자만큼의 페이지가 존재함을 유추할 수 있다. 이제 해당 데이터를 추출해보자.

몇 번째 td 찾기 selenium

해당 정보는 js-total-results 클래스에 위치하고 있다.

end_num = driver.find_element(By.CLASS_NAME, value = 'js-total-results').text math.ceil(int(end_num) / 50)

셀레니움에서 find_element() 내에 By.CLASS_NAME을 입력하면 클래스명( value)에 해당하는 데이터를 찾는다. 그 후 text를 붙이면 텍스트 정보만을 찾는다. 해당 값(end_num)은 텍스트 형태이므로 정수 형태로 변경한 후 50으로 나누어 ceil()을 통해 올림을 한다. 결과적으로 78 페이지 있다는 것이 확인된다.

이제 for loop 구문을 사용하면 모든 페이지의 정보를 가져올 수 있다. 이를 코드로 나타내면 다음과 같다.

from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait from bs4 import BeautifulSoup import math import pandas as pd from tqdm import tqdm import time driver = webdriver.Chrome("C:/Users/doomoolmori/Dropbox/My Book/quant_python/chromedriver.exe") nationcode = '35' url = f'https://investing.com/stock-screener/?sp=country::{nationcode}|sector::a|industry::a|equityType::a%3Ceq_market_cap;1' driver.get(url) WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.CLASS_NAME, 'js-total-results'))) end_num = driver.find_element(By.CLASS_NAME, value = 'js-total-results').text end_num = math.ceil(int(end_num) / 50) all_data_df = []

  1. 필요한 패키지들을 불러온다.

  2. webdriver.Chrome()을 통해 크롬 페이지를 연다.

  3. 일본의 국가코드에 해당하는 35를 입력한고, f string을 통해 url을 생성한다.

  4. driver.get()을 통해 해당 주소를 연다.

  5. 페이지가 열리기까지 시간이 걸리므로 바로 내용을 크롤링 할 수 없다. 먼저 WebDriverWait() 함수를 통해 10초간 기다리며, until(EC.visibility_of_element_located())을 통해 element가 존재할 때 까지, 즉 페이지가 열릴때 까지 기다린다. 여기서는 종목수에 해당하는 부분을 입력하였다. 만일 10초 이전에 해당 데이터가 존재할 경우 wait를 멈춘다.

  6. 총 몇페이지까지 존재하는지 계산한다.

  7. 티커가 들어갈 빈 리스트(all_data_df)를 만든다.

이제 for loop 구문을 통해 모든 정보를 크롤링해보도록 하자.

for i in tqdm(list(range(1, end_num + 1 ))) : url = f'https://investing.com/stock-screener/?sp=country::{nationcode}|sector::a|industry::a|equityType::a%3Ceq_market_cap;{i}' driver.get(url) WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.XPATH, '//*[@id="resultsTable"]/tbody'))) html = BeautifulSoup(driver.page_source, 'lxml') # 종목명 data_name = html.find_all(class_ = 'symbol left bold elp') data_name = [i.get_text() for i in data_name] # 티커 data_ticker = html.select('td[data-column-name = "viewData.symbol"]') data_ticker = [i.get_text() for i in data_ticker] # 합치기 data_df = pd.DataFrame( {'name' : data_name, 'ticker' : data_ticker, } ) all_data_df.append(data_df) time.sleep(2)

  1. list(range(1, end_num+1))을 통해 1부터 끝페이지까지 리스트를 생성하며, 진행상황을 보기 위해 tqdm() 함수를 이용한다.

  2. f-string 내에 국가코드와 페이지 정보를 입력해 url을 만든다.

  3. driver.ge()을 통해 페이지를 연다.

  4. 이번에는 XPATH가 '//*[@id="resultsTable"]/tbody'인, 즉 테이블 정보가 열릴때 까지 기다린다.

  5. BeautifulSoup()을 통해 html 정보를 가져온다.

  6. 티커와 섹터 정보를 각각 가져와 저장한다.

  7. 데이터프레임 형태로 합친다.

  8. 위에서 만들어둔 all_data_df에 append()를 통해 붙인다.

9. 2초간의 슬립을 준다.

이제 마지막으로 정리를 해준다.

all_data_df_bind = pd.concat(all_data_df, axis = 0) data_country = html.find(class_ = 'js-search-input inputDropDown')['value'] all_data_df_bind['country'] = data_country driver.quit()

  1. pd.concat()을 통해 리스트 내의 데이터프레임을 하나로 묶는다.

  2. js-search-input inputDropDown 클래스는 국가를 선택하는 드랍박스 부분을 의미하며, value는 국가명을 의미한다.

  3. 데이터프레임의 country 열에 해당 정보를 입력한다.

  4. driver.quit()을 통해 크롬창을 종료한다.

몇 번째 td 찾기 selenium

데이터프레임을 확인해보면 일본 주식시장의 모든 티커 리스트가 들어와있다. 위 코드에서 국가코드만 변경하면 전 세계 모든 국가의 티커 리스트를 받을 수 있다.

몇 번째 td 찾기 selenium

이제 위 정보를 바탕으로 주가와 재무제표를 받아보도록 하자. 예제로 가장 상단의 Toyota Motor(티커: 7203) 정보를 받아보자. 이는 야후 파이낸스 API를 이용하면 손쉽게 받을 수 있다.

그러나 위의 티커를 바로 사용할 수는 없다. 전 세계에는 동일한 티커가 여러 주식시장에 상장될 수 있다. 예를 들어 일본이 아닌 유럽의 어떤 국가에서 7203이라는 티커로 전혀 다른 종목이 상장되어 있을 수도 있다. (실제로 전세계에서 특정 티커가 8개 국가에 상장된 경우도 있다.) 이러한 것을 구분하기 위해 야후 파이낸스에서는 티커 뒤에 국가를 구분하는 구분자가 있다. 먼저 야후 파이낸스의 검색창에서 7203을 입력해보자.

몇 번째 td 찾기 selenium

놀랍게도 총 3개 종목이 나타난다. 가장 상단은 우리가 원하는 도요타 모터스 이지만 하단의 Elm Company는 사우디에 상장된 회사, WANGZNG은 쿠알라룸프르에 상장된 회사다. 이처럼 동일티커 문제를 해결하기 위해 일본 종목에는 .T 라는 구분자를 사용하며, 각 국의 구분자는 모두 다르다. (한국의 경우 코스피 상장종목은 .KS, 코스닥 상장종목은 .KQ이며, 미국 종목은 구분자가 없이 그대로 사용해도 된다.)

파이썬에서 야후 파이낸스의 주가 및 재무제표는 yahoo_fin 패키지를 이용하면 된다.

import yahoo_fin.stock_info as si price = si.get_data('7203.T') price.head() Open High Low Close Adj Close Volume Date 1999-05-06 698.0 722.0 690.0 722.0 417.551849 15575000 1999-05-07 718.0 720.0 690.0 696.0 402.515350 15165000 1999-05-10 696.0 704.0 690.0 694.0 401.358765 6305000 1999-05-11 700.0 704.0 688.0 688.0 397.888763 8430000 1999-05-12 688.0 700.0 688.0 700.0 404.828705 12980000

get_data 함수 내에 티커를 입력하면 주가가 다운로드 된다.

import yahoo_fin.stock_info as si data_y = si.get_financials('7203.T', yearly=True, quarterly=False) data_y {'yearly_income_statement': endDate 2022-03-31 ... 2019-03-31 Breakdown ... researchDevelopment None ... None effectOfAccountingCharges None ... None incomeBeforeTax 3990532000000 ... 2645531000000 minorityInterest 908851000000 ... 718985000000 netIncome 2850110000000 ... 1882873000000 sellingGeneralAdministrative 2975977000000 ... 2986695000000 grossProfit 5971673000000 ... 5443896000000 ebit 2995696000000 ... 2457201000000 operatingIncome 2995696000000 ... 2457201000000 ....}

get_financials() 함수 내에 티커를 입력한 후 yearly=True, quarterly=False 인자를 입력하면 연도별 재무제표 데이터가 다운로드 된다. 만일 분기별 재무제표 데이터를 원할 경우 yearly=False, quarterly=True를 입력하면 된다.

이제 위에서 받은 티커 리스트에 for loop 구문을 적용하면 모든 종목의 주가 및 재무제표를 다운로드 받을 수 있다. 단, 티커 리스트에 있는 종목 중 야후 파이낸스에 데이터가 없는 종목도 있으며 이는 trycatch() 구문을 사용해 처리하면 된다.

그러나 이보다 훨씬 더 쉽고 간단하게 전 세계 금융 데이터를 사용할 수 있는 방법도 있다.

  1. 두물머리에 입사한다.

  2. SQL을 켠다.

  3. 쿼리를 날린다.

  4. 사용한다.

몇 번째 td 찾기 selenium

몇 번째 td 찾기 selenium