# Constants Start
# 뉴스 기사 카테고리 환율,유가,금시세,금리,원자재
CATEGORIES = ("exchange", "oil", "gold", "interest", "material")
# 네이버 금융, 환율전체, 특정국가 환율, 금융 관련 뉴스 페이지 URL
BASE_URL = "https://finance.naver.com"
EX_URL = "/marketindex/exchangeList.nhn"
FX_URL = "/marketindex/exchangeDailyQuote.nhn?marketindexCd="
NEWS_URL = "/marketindex/news/newsList.nhn?category=%s&date=%s"
# 과거의 뉴스가 언제부터 있는가. 이미 고정되어 있기 때문에 상수 저장함.
NEWS_LIMIT = {
CATEGORIES[0]: date(2009, 1, 19),
CATEGORIES[1]: date(2009, 2, 27),
CATEGORIES[2]: date(2009, 5, 20),
CATEGORIES[3]: date(2009, 2, 11),
CATEGORIES[4]: date(2009, 11, 29),
}
# Constants End
# Global Variable Start
today = date.today() # 프로그램 실행일
this_year = today.year # 프로그램을 실행하는 당일의 연도
# 프로그램 실행 당일과, 각 카테고리의 뉴스가 존재하는 첫 날의 날짜 차이를 일수로 계산한다.
news_date_gap = {
CATEGORIES[0]: (today - NEWS_LIMIT[CATEGORIES[0]]).days,
CATEGORIES[1]: (today - NEWS_LIMIT[CATEGORIES[1]]).days,
CATEGORIES[2]: (today - NEWS_LIMIT[CATEGORIES[2]]).days,
CATEGORIES[3]: (today - NEWS_LIMIT[CATEGORIES[3]]).days,
CATEGORIES[4]: (today - NEWS_LIMIT[CATEGORIES[4]]).days,
}
# Global Variable End
def get_exchange():
"""환율 정보를 가져온다."""
# Requests를 사용하여 환율 정보 페이지의 Response를 받아온다.
resp = requests.get(BASE_URL + EX_URL)
# BeautifulSoup를 사용하여 Response의 소스 정보를 html로 파싱하여 soup 변수에 저장한다.
soup = BeautifulSoup(resp.text, "html.parser")
# soup에서 class td인 td를 모두 가져온다.
td = soup.find_all("td", class_="tit")
# 각 td에 a태그가 있으므로 a태그를 국가 리스트를 생성하여 집어넣는다.
country_list = [c.find("a") for c in td]
del td # td는 필요 없으므로 삭제(메모리 공간을 최대한 절약하기 위해 더 이상 사용되지 않는 obj를 삭제한다.
국가명과 환율 코드가 앞뒤로 상당히 많은 공백을 함께하므로 strip 해주어야 한다.
# 전세계 당일환율을 가져오는 옵션을 주기 위해 country_dict를 초기화하고 전세계 옵션을 0번으로 준비한다.
country_dict = {0: ("전세계", EX_URL)}
# country_dict의 Key를 1부터 시작해야 하기 때문에 i을 1로 초기화해서 준비한다.
i = 1
# country_list에서 국가명과 url을 tuple 형식으로 country_dict에 넣는다.
for country in country_list:
country_dict[i] = (country.get_text().strip(), country["href"].split("=")[-1])
i += 1
country_length = len(country_dict) # 환율 개수
country_dict는 아래와 같은 모습으로 가공되었다.
{
0: ('전세계', '/marketindex/exchangeList.nhn'),
1: ('미국 USD', 'FX_USDKRW'),
2: ('유럽연합 EUR', 'FX_EURKRW'),
...
42: ('러시아 RUB', 'FX_RUBKRW'),
43: ('헝가리 HUF', 'FX_HUFKRW'),
44: ('폴란드 PLN', 'FX_PLNKRW')
}
# 조회할 국가를 입력받는다.
print("환율을 조회하고 싶은 국가 번호를 입력해 주세요.")
[print(f"{key}: {country_dict[key][0]}") for key in country_dict.keys()]
number = 0
# 국가 수 + 1(전세계) - 1의 수 중에서 하나를 입력받을 때까지 계속 입력 받는다.
while True:
try:
number = int(input())
if number >= country_length or number < 0:
print("허용되지 않는 번호를 입력하셨습니다.")
else:
break
except ValueError:
print("숫자가 아닌 문자를 입력할 수 없습니다.")
if number == 0: # 세계환율 선택시
tr = soup.select("tr")
inter_df = pd.concat(
[
pd.DataFrame(
[
[
tr[row].select("a")[0].get_text().strip(),
tr[row].select("td")[1].get_text(),
tr[row].select("td")[2].get_text(),
tr[row].select("td")[3].get_text(),
tr[row].select("td")[4].get_text(),
tr[row].select("td")[5].get_text(),
tr[row].select("td")[6].get_text(),
]
],
columns=[
"통화명",
"매매기준율",
"현찰_살 때",
"현찰_팔 때",
"송금_보낼 때",
"송금_받을 때",
"미화환산율",
],
index=[row - 2],
)
for row in range(2, len(tr))
]
)
inter_df.reset_index(inplace=True)
inter_df.drop('index', axis=1, inplace=True)
inter_df.to_json('world_fx.json')
print("world_fx.json 파일을 저장하였습니다.")
del inter_df
print("끝")
else:
print(f"{country_dict[number][0]}을/를 선택하셨습니다.")
page = 1
fx_df = pd.DataFrame()
while True:
fx_resp = requests.get(
BASE_URL + FX_URL + country_dict[number][1] + "&page=%d" % (page)
)
fx_soup = BeautifulSoup(fx_resp.text, "html.parser")
fx_tr = fx_soup.find_all("tr")
if len(fx_tr) < 3: # 헤더만 있다는 뜻이므로 멈춤.
break
fx_df = pd.concat(
[
fx_df,
pd.concat(
[
pd.DataFrame(
[
[
fx_tr[row].select("td")[0].get_text().strip(), # 날짜
fx_tr[row].select("td")[1].get_text(), # 매매기준율
"""
전일대비의 경우 up/down이 존재하는데,
원 소스에서 up 또는 down 클래스로 나뉘어져 있으므로
클래스에 따라 기호를 부가한다.
"""
fx_tr[row].select("td")[2].get_text()
if fx_tr[row]["class"][0] == "up"
else (
"-" + fx_tr[row].select("td")[2].get_text()
), # 전일대비
fx_tr[row].select("td")[3].get_text(), # 현찰 살 때
fx_tr[row].select("td")[4].get_text(), # 현찰 팔 때
fx_tr[row].select("td")[5].get_text(), # 송금 보낼 때
fx_tr[row].select("td")[6].get_text(), # 송금 받을 때
fx_tr[row]
.select("td")[8]
.get_text()
.strip(), # 외화수표 팔 때
]
],
columns=[
"날짜",
"매매기준율",
"전일대비",
"현찰_살 때",
"현찰_팔 때",
"송금_보낼 때",
"송금_받을 때",
"외화수표 팔 때",
],
index=[row - 2],
)
for row in range(2, len(fx_tr))
]
),
]
)
page += 1
first_date = f"{fx_df.iloc[-1, 0]}".split()[0] # 환율이 존재하는 첫 날
recent_date = f"{fx_df.iloc[0, 0]}".split()[0] # 환율이 존재하는 가장 최근 날
fx_df.reset_index(inplace=True)
print(f"{country_dict[number][0]}의 {first_date}부터 {recent_date}까지의 환율정보를 취득하였습니다.")
fx_df.to_json(f"{country_dict[number][1]}.json")
print(f"{country_dict[number][1]}.json 파일을 저장하였습니다.")
del fx_df
print("끝")
42 러시아를 선택해 보았다.
이 글을 쓰고 있는 날은 토요일이라, 전날인 금요일(2021.8.21)까지의 환율까지 가져오게 되었다.
가져온 환율은 json 파일로 저장하였다.
(T/C 사실 때는 항상 N/A만 나와서 크롤링에서 제외하였다.)
내용을 보면 수치가 전부 정확하게 일치한다.
세계환율 선택시에는 출력 내용이 살짝 다르다.
world_fx.json 파일로 저장하게 하였다.
역시 수치가 동일하다.
(이하, 소스 이어붙인 전문)
def get_exchange():
"""
"""
# HTTP Response
resp = requests.get(BASE_URL + EX_URL)
soup = BeautifulSoup(resp.text, "html.parser")
# class td인 td를 모두 가져온다.
td = soup.find_all("td", class_="tit")
# 각 td에 a태그가 있으므로 a태그를 국가 리스트를 생성하여 집어넣는다.
country_list = [c.find("a") for c in td]
del td # td는 필요 없으므로 삭제(메모리 공간을 최대한 절약하기 위해 더 이상 사용되지 않는 obj를 삭제한다.
# 전세계 당일환율을 가져오는 옵션을 주기 위해 country_dict를 초기화하고 전세계 옵션을 0번으로 준비한다.
country_dict = {0: ("전세계", EX_URL)}
i = 1
for country in country_list:
country_dict[i] = (country.get_text().strip(), country["href"].split("=")[-1])
i += 1
country_length = len(country_dict) # 환율 개수
# 조회할 국가를 입력받는다.
print("환율을 조회하고 싶은 국가 번호를 입력해 주세요.")
[print(f"{key}: {country_dict[key][0]}") for key in country_dict.keys()]
number = 0
# 국가 수 + 1(전세계) - 1의 수 중에서 하나를 입력받을 때까지 계속 입력 받는다.
while True:
try:
number = int(input())
if number >= country_length or number < 0:
print("허용되지 않는 번호를 입력하셨습니다.")
else:
break
except ValueError:
print("숫자가 아닌 문자를 입력할 수 없습니다.")
if number == 0: # 세계환율 선택시
tr = soup.select("tr")
inter_df = pd.concat(
[
pd.DataFrame(
[
[
tr[row].select("a")[0].get_text().strip(),
tr[row].select("td")[1].get_text(),
tr[row].select("td")[2].get_text(),
tr[row].select("td")[3].get_text(),
tr[row].select("td")[4].get_text(),
tr[row].select("td")[5].get_text(),
tr[row].select("td")[6].get_text(),
]
],
columns=[
"통화명",
"매매기준율",
"현찰_살 때",
"현찰_팔 때",
"송금_보낼 때",
"송금_받을 때",
"미화환산율",
],
index=[row - 2],
)
for row in range(2, len(tr))
]
)
inter_df.reset_index(inplace=True)
inter_df.drop('index', axis=1, inplace=True)
inter_df.to_json('world_fx.json')
print("world_fx.json 파일을 저장하였습니다.")
del inter_df
print("끝")
else:
print(f"{country_dict[number][0]}을/를 선택하셨습니다.")
page = 1
fx_df = pd.DataFrame()
while True:
fx_resp = requests.get(
BASE_URL + FX_URL + country_dict[number][1] + "&page=%d" % (page)
)
fx_soup = BeautifulSoup(fx_resp.text, "html.parser")
fx_tr = fx_soup.find_all("tr")
if len(fx_tr) < 3: # 헤더만 있다는 뜻이므로 멈춤.
break
fx_df = pd.concat(
[
fx_df,
pd.concat(
[
pd.DataFrame(
[
[
fx_tr[row].select("td")[0].get_text().strip(), # 날짜
fx_tr[row].select("td")[1].get_text(), # 매매기준율
fx_tr[row].select("td")[2].get_text() if fx_tr[row]["class"][0] == "up" else ("-" + fx_tr[row].select("td")[2].get_text()), # 전일대비
fx_tr[row].select("td")[3].get_text(), # 현찰 살 때
fx_tr[row].select("td")[4].get_text(), # 현찰 팔 때
fx_tr[row].select("td")[5].get_text(), # 송금 보낼 때
fx_tr[row].select("td")[6].get_text(), # 송금 받을 때
fx_tr[row]
.select("td")[8]
.get_text()
.strip(), # 외화수표 팔 때
]
],
columns=[
"날짜",
"매매기준율",
"전일대비",
"현찰_살 때",
"현찰_팔 때",
"송금_보낼 때",
"송금_받을 때",
"외화수표 팔 때",
],
index=[row - 2],
)
for row in range(2, len(fx_tr))
]
),
]
)
page += 1
first_date = f"{fx_df.iloc[-1, 0]}".split()[0] # 환율이 존재하는 첫 날
recent_date = f"{fx_df.iloc[0, 0]}".split()[0] # 환율이 존재하는 가장 최근 날
fx_df.reset_index(inplace=True)
fx_df.drop('index', axis=1, inplace=True)
print(f"{country_dict[number][0]}의 {first_date}부터 {recent_date}까지의 환율정보를 취득하였습니다.")
fx_df.to_json(f"{country_dict[number][1]}.json")
print(f"{country_dict[number][1]}.json 파일을 저장하였습니다.")
del fx_df
print("끝")
이 글은 이만 마치며, 다음은 크롤링(2)환율관련 뉴스기사 가져오기를 써보도록 하겠다.