종목 이름 가져오기]ticker, name, sector 응용 #2
▣ BPS(Book-Value Per Share)
BPS(Book-Value Per Share, 주당순자산가치)는 기업의 총자산에서 총부채를 뺀 순자산(자기자본)을 발행된 총주식 수로 나눈 값으로, 기업이 파산하여 모든 자산을 처분하고 부채를 갚은 뒤 주주들에게 돌아갈 몫을 1주당 금액으로 나타낸 지표입니다. 즉, BPS가 높을수록 1주당 장부가치가 높고 재무건전성이 좋다고 해석할 수 있습니다.
BPS의 의미와 활용
- 기업 청산가치:
BPS는 기업을 청산했을 때 1주당 주주에게 돌아갈 수 있는 가치를 의미합니다. - 재무건전성 판단:
BPS가 높은 기업은 자기자본이 많아 재무적으로 안정적이며, 투자가치가 높다고 볼 수 있습니다. - PBR과 함께 활용:
BPS 자체만으로는 주가가 자산가치에 비해 얼마나 저평가 또는 고평가되었는지 판단하기 어렵기 때문에, BPS에 현재 주가를 나누어 계산하는 PBR(주가순자산비율)과 함께 사용하여 투자 가치를 판단합니다. - 예시
BPS가 1만원인 기업의 주가가 5,000원이라면 PBR은 0.5가 되어 주가가 장부가치보다 낮고, 주가가 20,000원이라면 PBR은 2가 되어 주가가 장부가치보다 높다고 볼 수 있습니다.
import requests
from bs4 import BeautifulSoup
import pandas as pd
from openpyxl import load_workbook
import re
import time
import os
# 설정
FILE_PATH = "ticker.xlsx"
SHEET_NAME = "tickersheet"
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
SAVE_DEBUG_HTML = True # 못 찾은 종목 HTML 저장 여부
# 숫자 정리 함수
def clean_num(s):
if not s:
return None
s = str(s).replace('\xa0',' ').strip()
m = re.search(r'-?\d{1,3}(?:,\d{3})*(?:\.\d+)?|-?\d+(?:\.\d+)?', s)
if m:
num_str = m.group(0).replace(',', '')
# 소수점이 있으면 float, 없으면 int로 변환
return float(num_str) if '.' in num_str else int(num_str)
return None
# BPS 추출 함수
def extract_bps_from_soup(soup):
# 1) id="_pbr" 바로 아래 <em> 태그에서 BPS 추출
pbr_tag = soup.select_one('em#_pbr')
if pbr_tag:
next_em = pbr_tag.find_next('em')
if next_em:
v = clean_num(next_em.get_text())
if v is not None: return v
# 2) aside_invest_info 블럭
invest = soup.select_one('.aside_invest_info')
if invest:
trs = invest.select('tr')
for i, tr in enumerate(trs):
tds = tr.find_all(['th','td'])
if len(tds) >= 2 and 'PBR' in tds[0].get_text(strip=True):
# PBR 다음 <em> 태그 추출
next_em_count = 0
for j in range(i + 1, len(trs)):
em_tags = trs[j].select('em')
for em in em_tags:
next_em_count += 1
if next_em_count == 1:
v = clean_num(em.get_text())
if v is not None: return v
# 3) "BPS" 텍스트 주변
for text_node in soup.find_all(string=re.compile(r'\bBPS\b', re.I)):
tr = text_node.find_parent('tr')
if tr:
tds = tr.find_all(['th','td'])
for i, td in enumerate(tds):
if 'BPS' in td.get_text(strip=True):
if i + 1 < len(tds):
v = clean_num(tds[i + 1].get_text())
if v is not None: return v
# 4) 전체 텍스트에서 탐색
page_text = soup.get_text(" ", strip=True)
m = re.search(r'BPS[^0-9\-+]*([-]?\d{1,3}(?:,\d{3})*(?:\.\d+)?)', page_text, re.I)
if m:
return clean_num(m.group(1))
return None
# ---- 메인 실행 ----
df = pd.read_excel(FILE_PATH, sheet_name=SHEET_NAME)
bps_list = []
total = len(df)
start_time = time.time()
for idx, row in df.iterrows():
code = str(row["Ticker"]).zfill(6)
name = row.get("Name", "")
print(f"▶ [{idx+1}/{total}] [{code}] {name} BPS 조회 중...")
url = f"https://finance.naver.com/item/main.naver?code={code}"
try:
res = requests.get(url, headers=HEADERS, timeout=10)
res.encoding = res.apparent_encoding or 'utf-8'
soup = BeautifulSoup(res.text, "html.parser")
except Exception as e:
print(f" ✖ HTTP/네트워크 오류: {e}")
bps_list.append(None)
continue
bps_value = extract_bps_from_soup(soup)
if bps_value is None:
print(" → BPS: None (찾지 못함)")
if SAVE_DEBUG_HTML:
debug_name = f"debug_{code}.html"
with open(debug_name, "w", encoding="utf-8") as f:
f.write(res.text)
print(f" (디버그 HTML 저장: {os.path.abspath(debug_name)})")
else:
print(f" → BPS: {bps_value}")
bps_list.append(bps_value)
time.sleep(0.3) # 서버 부담 완화
elapsed = time.time() - start_time
found = sum(1 for v in bps_list if v is not None)
print(f"\n완료: {found}/{total} 개 항목에서 BPS를 찾음 (소요 {elapsed:.1f}초)")
# ---- F열만 갱신 ----
book = load_workbook(FILE_PATH)
ws = book[SHEET_NAME]
# F2부터 BPS 값 기록
for i, val in enumerate(bps_list, start=2):
ws[f"F{i}"] = val
book.save(FILE_PATH)
print("✅ BPS 크롤링 완료 후 F열만 갱신하여 저장되었습니다.")
2025년, 09월 15일 종목별 BPS 다운로드
PER, PBR, ROE란? 투자의 나침반이예요 [요즘 금융용어 #금융보카]
다음 중 주가와 기업 가치를 평가하는데 자주 쓰이는 용어로 개념이 틀린 것은 무엇일까요? ① PER : 기...
blog.naver.com
▣ Price to Book Ratio, P/B Ratio
PBR은 맥락에 따라 여러 의미로 사용되지만,가장 흔히 가리키는 것은 주가순자산비율(Price to Book Ratio, P/B Ratio)입니다. 이는 주식 투자와 기업 가치 평가에서 중요한 재무비율로, 주식의 시장 가격이 기업의 순자산 가치(장부가치) 대비 얼마나 높은지(또는 낮은지)를 나타냅니다. 다른 의미(예: 네트워킹의 Policy-Based Routing이나 컴퓨터 그래픽스의 Physically Based Rendering)도 있지만, 금융 분야에서 압도적으로 자주 언급됩니다.
예시: 주가가 2,000원이고 BPS가 1,500원이라면, PBR = 2,000 / 1,500 = 1.33배입니다.
이는 주가가 순자산 가치의 1.33배로 거래되고 있음을 의미합니다.
PBR로 알 수 있는 것
PBR은 기업의 내재 가치 대비 주가의 적정성을 판단하는 데 유용합니다. 주요 인사이트는 다음과 같습니다:
- PBR < 1: 주가가 순자산 가치보다 낮아 '저평가'된 상태로 보입니다.
자산 가치가 주가에 제대로 반영되지 않았을 수 있어, 가치 투자자들이 매수 기회로 삼습니다.
(예: 자산 중심 산업처럼 제조업에서 자주 관찰됨) - PBR = 1: 주가가 순자산 가치와 비슷해 적정 수준으로 평가됩니다.
- PBR > 1: 주가가 순자산 가치보다 높아 '고평가'된 상태입니다.
성장 잠재력(예: 기술주)이 반영된 경우일 수 있지만, 과열 가능성을 경고합니다.
추가 활용:
- 업종 비교: 같은 산업 내 기업들을 비교해 상대적 가치를 평가합니다. (예: 은행주는 평균 PBR이 낮음)
- 투자 전략: 저PBR 종목은 '가치주'로 분류되어 장기 투자에 적합하며, ROE(자기자본이익률)와 함께 사용하면 기업의 자산 효율성을 더 깊이 분석할 수 있습니다.
- 한계: 무형자산(브랜드, 특허)이 많은 기업(예: IT 기업)에서는 PBR이 과소평가될 수 있으니, PER(주가수익비율) 등 다른 지표와 함께 봐야 합니다.
※ 네이버 증권 사이트에서 PBR 값을 가져오는 파이썬 코드
import requests
from bs4 import BeautifulSoup
import pandas as pd
from openpyxl import load_workbook
import re
import time
import os
# 설정
FILE_PATH = "ticker.xlsx"
SHEET_NAME = "tickersheet"
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
SAVE_DEBUG_HTML = True # 못 찾은 종목 HTML 저장 여부
# 숫자 정리 함수
def clean_num(s):
if not s:
return None
s = str(s).replace('\xa0',' ').strip()
m = re.search(r'-?\d{1,3}(?:,\d{3})*(?:\.\d+)?|-?\d+(?:\.\d+)?', s)
if m:
num_str = m.group(0).replace(',', '')
return float(num_str) if '.' in num_str else int(num_str)
return None
# PBR 추출 함수
def extract_pbr_from_soup(soup):
# 1) id="_pbr" 바로 아래 <em> 태그에서 PBR 추출
pbr_tag = soup.select_one('em#_pbr')
if pbr_tag:
v = clean_num(pbr_tag.get_text())
if v is not None: return v
# 2) aside_invest_info 블럭
invest = soup.select_one('.aside_invest_info')
if invest:
trs = invest.select('tr')
for tr in trs:
tds = tr.find_all(['th','td'])
if len(tds) >= 2 and 'PBR' in tds[0].get_text(strip=True):
v = clean_num(tds[1].get_text())
if v is not None: return v
# 3) "PBR" 텍스트 주변
for text_node in soup.find_all(string=re.compile(r'\bPBR\b', re.I)):
tr = text_node.find_parent('tr')
if tr:
tds = tr.find_all(['th','td'])
for i, td in enumerate(tds):
if 'PBR' in td.get_text(strip=True):
if i + 1 < len(tds):
v = clean_num(tds[i + 1].get_text())
if v is not None: return v
# 4) 전체 텍스트에서 탐색
page_text = soup.get_text(" ", strip=True)
m = re.search(r'PBR[^0-9\-+]*([-]?\d{1,3}(?:,\d{3})*(?:\.\d+)?)', page_text, re.I)
if m:
return clean_num(m.group(1))
return None
# ---- 메인 실행 ----
df = pd.read_excel(FILE_PATH, sheet_name=SHEET_NAME)
pbr_list = []
total = len(df)
start_time = time.time()
for idx, row in df.iterrows():
code = str(row["Ticker"]).zfill(6)
name = row.get("Name", "")
print(f"▶ [{idx+1}/{total}] [{code}] {name} PBR 조회 중...")
url = f"https://finance.naver.com/item/main.naver?code={code}"
try:
res = requests.get(url, headers=HEADERS, timeout=10)
res.encoding = res.apparent_encoding or 'utf-8'
soup = BeautifulSoup(res.text, "html.parser")
except Exception as e:
print(f" ✖ HTTP/네트워크 오류: {e}")
pbr_list.append(None)
continue
pbr_value = extract_pbr_from_soup(soup)
if pbr_value is None:
print(" → PBR: None (찾지 못함)")
if SAVE_DEBUG_HTML:
debug_name = f"debug_{code}.html"
with open(debug_name, "w", encoding="utf-8") as f:
f.write(res.text)
print(f" (디버그 HTML 저장: {os.path.abspath(debug_name)})")
else:
print(f" → PBR: {pbr_value}")
pbr_list.append(pbr_value)
time.sleep(0.3) # 서버 부담 완화
elapsed = time.time() - start_time
found = sum(1 for v in pbr_list if v is not None)
print(f"\n완료: {found}/{total} 개 항목에서 PBR를 찾음 (소요 {elapsed:.1f}초)")
# ---- G열만 갱신 ----
book = load_workbook(FILE_PATH)
ws = book[SHEET_NAME]
# G2부터 PBR 값 기록
for i, val in enumerate(pbr_list, start=2):
ws[f"G{i}"] = val
book.save(FILE_PATH)
print("✅ PBR 크롤링 완료 후 G열만 갱신하여 저장되었습니다.")
※ 2025년 09월 15일 네이버 증권 사이트에서 종목별 PBR 다운로드
▣ ROE (Return on Equity -자기자본이익률
기업이 주주가 투자한 자본(자기자본)을 얼마나 효율적으로 활용해 수익을 창출했는지를 나타내는 재무 지표입니다. 주로 기업의 수익성과 운영 효율성을 평가할 때 사용됩니다.
예시: 당기순이익이 100억 원이고, 평균 자기자본이 500억 원이라면, ROE = (100억 / 500억) × 100 = 20%입니다. 이는 기업이 자기자본 1원당 20원의 수익을 창출했음을 의미합니다.
ROE로 알 수 있는 것
ROE는 기업의 자본 효율성과 수익 창출 능력을 평가하는 데 유용합니다. 주요 인사이트는 다음과 같습니다:
- 높은 ROE (예: 15% 이상):
기업이 자본을 효율적으로 활용해 높은 수익을 내고 있음을 나타냅니다.
투자자들에게 매력적인 기업으로 보일 가능성이 큽니다. - 낮은 ROE (예: 5% 미만):
자본 대비 수익성이 낮아 비효율적이거나 성장성이 떨어질 수 있음을 시사합니다. - 업종 비교:
ROE는 산업별로 평균값이 다릅니다.
예를 들어, 기술주나 성장주는 높은 ROE를 보이는 반면, 자본집약적 산업(예: 제조업)은 상대적으로 낮을 수 있습니다. - PBR과의 연계: ROE는
PBR(주가순자산비율)과 함께 분석하면 기업의 가치와 수익성을 종합적으로 판단할 수 있습니다.
높은 ROE와 낮은 PBR은 저평가된 우량주일 가능성을 높입니다
주의점
- 한계:
ROE가 높다고 무조건 좋은 기업은 아닙니다.
과도한 부채(레버리지)로 ROE가 부풀려질 수 있으므로, 부채비율(D/E Ratio)과 함께 봐야 합니다. - 단기 왜곡: 일회성 수익(예: 자산 매각)으로 ROE가 높아질 수 있으니, 장기 추세를 확인하는 것이 중요합니다.
- 산업 특성:
무형자산(특허, 브랜드 등)이 중요한 기업에서는 자기자본이 낮게 평가될 수 있어 ROE가 과대 표시될 가능성이 있습니다.
※ 네이버 증권 사이트에서 ROE 값을 가져오는 파이썬 코드
import requests
from bs4 import BeautifulSoup
import pandas as pd
from openpyxl import load_workbook
import re
import time
import os
# 설정
FILE_PATH = "ticker.xlsx"
SHEET_NAME = "tickersheet"
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}
SAVE_DEBUG_HTML = True # 못 찾은 종목 HTML 저장 여부
# 숫자 정리 함수
def clean_num(s):
if not s:
return None
s = str(s).replace('\xa0', '').strip()
m = re.search(r'-?\d{1,3}(?:,\d{3})*(?:\.\d+)?', s)
if m:
num_str = m.group(0).replace(',', '')
return float(num_str)
return None
# ROE 추출 함수 (최종 수정)
def extract_roe_from_soup(soup, target_code):
"""
네이버 금융 페이지에서 ROE를 추출합니다.
우선순위 1: 종목 메인 재무정보 테이블에서 ROE 찾기
우선순위 2: 동일업종 비교 테이블에서 해당 종목의 ROE 찾기
"""
# 1) 종목 메인 재무정보 테이블에서 ROE 찾기
# 이 테이블은 종목에 따라 다른 클래스를 가질 수 있으므로 여러 클래스를 시도합니다.
main_tables = soup.select('.section.invest_info, .tb_type1.tb_num, table.per_table, .tb_ty_summary')
for table in main_tables:
trs = table.find_all('tr')
for tr in trs:
tds = tr.find_all(['th', 'td'])
if len(tds) >= 2 and 'ROE(%)' in tds[0].get_text(strip=True):
v = clean_num(tds[1].get_text())
if v is not None:
return v
# 2) '동일업종 비교' 테이블에서 해당 종목의 ROE 값을 찾아내는 로직
related_table = soup.select_one('.related_section.main_tab table')
if related_table:
# 1. 헤더(th)에서 종목 코드(target_code)가 포함된 열의 인덱스 찾기
headers = [th.get_text(strip=True) for th in related_table.find_all('th')]
target_col_idx = -1
for i, header in enumerate(headers):
# 헤더 텍스트에 종목 코드 포함 여부 확인
if target_code in header:
target_col_idx = i
break
# 해당 종목 열을 찾았다면
if target_col_idx != -1:
# 2. 'ROE(%)' 행을 찾아 해당 열의 값 가져오기
rows = related_table.find_all('tr')
for row in rows:
if 'ROE(%)' in row.get_text(strip=True):
tds = row.find_all('td')
if target_col_idx < len(tds):
v = clean_num(tds[target_col_idx].get_text())
return v
return None
# --- 메인 실행 ---
df = pd.read_excel(FILE_PATH, sheet_name=SHEET_NAME)
roe_list = []
total = len(df)
start_time = time.time()
for idx, row in df.iterrows():
code = str(row["Ticker"]).zfill(6)
name = row.get("Name", "")
print(f"▶ [{idx+1}/{total}] [{code}] {name} ROE 조회 중...")
url = f"https://finance.naver.com/item/main.naver?code={code}"
try:
res = requests.get(url, headers=HEADERS, timeout=10)
res.encoding = res.apparent_encoding or 'utf-8'
soup = BeautifulSoup(res.text, "html.parser")
except Exception as e:
print(f" ✖ HTTP/네트워크 오류: {e}")
roe_list.append(None)
continue
# 함수 호출 시 종목 코드(code)를 인자로 전달
roe_value = extract_roe_from_soup(soup, code)
if roe_value is None:
print(" → ROE: None (찾지 못함)")
if SAVE_DEBUG_HTML:
debug_name = f"debug_{code}.html"
with open(debug_name, "w", encoding="utf-8") as f:
f.write(res.text)
print(f" (디버그 HTML 저장: {os.path.abspath(debug_name)})")
roe_list.append(None)
else:
print(f" → ROE: {roe_value}")
roe_list.append(roe_value)
time.sleep(0.3) # 서버 부담 완화
elapsed = time.time() - start_time
found = sum(1 for v in roe_list if v is not None)
print(f"\n완료: {found}/{total} 개 항목에서 ROE를 찾음 (소요 {elapsed:.1f}초)")
# --- H열만 갱신 ---
book = load_workbook(FILE_PATH)
ws = book[SHEET_NAME]
# H2부터 ROE 값 기록, 데이터가 없는 경우 빈 셀 유지
for i, val in enumerate(roe_list, start=2):
if val is not None:
ws[f"H{i}"] = val
book.save(FILE_PATH)
print("✅ ROE 크롤링 완료 후 H열만 갱신하여 저장되었습니다.")
※ 2025년 09월 15일 네이버 증권 사이트에서 종목별 ROE 다운로드
