플라스크 & HTML & 웹 크롤링 — 이론 & 실습 정리

syeom·2026년 4월 21일

멀티캠퍼스 데이터분석 4월 21일 수업 내용 — Flask, HTML/CSS, BeautifulSoup, 주식 데이터 크롤링


📌 목차

이론
1. 플라스크 (Flask)
2. 웹 서버
3. 정적 / 동적 웹 페이지
4. HTML 기초
5. HTML 주요 태그
6. Hyperlink & 경로
7. list / table / image / form 태그
8. CSS 셀렉터

실습

  1. 웹 크롤링 개요
  2. requests & BeautifulSoup 기초
  3. BeautifulSoup 내장 함수
  4. 네이버 증권 크롤링
  5. 주식 종목 데이터 크롤링 & CSV 저장
  6. wise.py — 크롤링 모듈화

이론

1. 플라스크 (Flask)

Flask는 파이썬 기반의 웹 프레임워크로, micro 프레임워크를 강조합니다.

  • 최소한의 구성 요소와 요구사항만 제공 → 시작하기 쉬움
  • 필요에 따라 유연하게 확장 가능

2. 웹 서버

웹 서버는 하드웨어소프트웨어 두 가지 측면으로 구분합니다.

구분설명
하드웨어HTML, Image, CSS, JS 등 컴포넌트 파일을 저장하고 클라이언트에게 전달하는 컴퓨터
소프트웨어사용자의 파일 접근을 관리, HTTP 프로토콜로 요청을 처리하고 응답

💡 브라우저가 파일을 필요로 할 때 → HTTP로 요청 → 올바른 서버에 도달 → HTTP 서버가 파일을 HTTP로 응답


3. 정적 / 동적 웹 페이지

구분설명
정적 웹 페이지서버에 미리 저장된 파일이 그대로 전달, 데이터가 변경되지 않는 한 고정된 페이지를 보게 됨
동적 웹 페이지서버의 데이터를 스크립트로 가공 처리 후 생성·전달, 상황·시간·요청에 따라 페이지가 달라짐


4. HTML 기초

HTML은 웹페이지의 내용과 구조를 담당하는 마크업 언어입니다.

마크업 언어 — 특별한 기호나 표기를 사용하여 글의 서식과 스타일을 정해주는 언어 (프로그래밍 언어 X)

기본 구조

<!DOCTYPE html>
<html>
  <head>
    <!-- 브라우저에 표시되지 않는 영역: title, 외부 파일 참조, 메타데이터 -->
  </head>
  <body>
    <!-- 브라우저에 표시되는 영역 -->
  </body>
</html>

HTML 문법

  • 요소 — 시작 태그 + 콘텐츠 + 종료 태그

  • 속성 — 요소의 성질·특징을 정의, 시작 태그에 위치, 이름 = "값" 쌍으로 구성

  • 주석 — 브라우저에 표시되지 않음, 개발자용 설명


5. HTML 주요 태그

head 태그

태그설명
<title>문서 제목, 브라우저 탭에 표시
<style>HTML 문서의 CSS 스타일 정의
<link>외부 CSS 파일 연결 등 외부 리소스 연계
<script>JS 코드 포함 또는 src로 외부 JS 파일 로드

body 태그

태그설명
<h1> ~ <h6>제목 태그, h1이 가장 크고 중요
<p>단락 지정, 본문에 자주 사용
<b>, <strong>굵은 글씨


하이퍼링크는 문서 안에서 다른 문서나 자료로 즉시 접근할 수 있는 참조 고리입니다.
누르면 웹 사이트나 프로그램으로 이동합니다.

디렉토리

파일 경로

href 속성에서 사용 가능한 값


7. list / table / image / form 태그

list 태그

  • 비순서형 리스트

  • 순서형 리스트

table 태그


image 태그

<img src="경로" alt="설명" width="너비" height="높이">

form 태그

유저가 입력한 데이터를 수집하기 위해 사용합니다.
입력 방식: input, textarea, button, select, checkbox, radio button, submit button

전송 방식설명
GETURL에 ?key=value 형태로 데이터 노출, 최대 255자 제한, 보안 취약
POSTrequest body에 데이터 담아 전송, URL에 노출되지 않아 보안 우수, GET보다 느림

input 태그

서버에 전송되는 데이터는 name을 키, value를 값으로 하여 key=value 형태로 전송됩니다.

select 태그


8. CSS 셀렉터

셀렉터설명
*문서 내 모든 요소
태그명지정된 태그명을 가진 요소
#id값id 속성이 일치하는 요소 (id는 문서에서 유일)
.class값class 값이 일치하는 요소 (여러 개 지정 가능)





실습

9. 웹 크롤링 개요

주요 라이브러리

라이브러리역할
requests웹 서버에 요청을 보내고 응답 수신 (bytes / string 형태)
bs4 (BeautifulSoup)HTML 문자 데이터를 파싱하여 태그 기준으로 데이터 추출
selenium웹 브라우저를 파이썬 코드로 제어 (버튼 클릭, 데이터 입력 등)

⚠️ 웹 크롤링은 사이트별로 허용 범위가 다릅니다.
상업적 용도 사용 시 법적 문제가 생길 수 있으며, 수업에서는 학습 목적으로만 활용합니다.

# !pip install bs4 selenium
import requests
from bs4 import BeautifulSoup as bs

10. requests & BeautifulSoup 기초

HTML 문서 요청

res = requests.get('https://www.naver.com')

res.content   # bytes 형태
res.text      # 문자열 형태

BeautifulSoup으로 파싱

html_text = res.text
soup = bs(html_text, 'html.parser')

태그명으로 접근

soup.a              # 문서에서 첫 번째 a 태그
soup.a.string       # 첫 번째 a 태그의 콘텐츠(텍스트)
soup.a['href']      # 첫 번째 a 태그의 href 속성값

💡 soup.태그명 은 문자열의 find() 처럼 첫 번째 항목만 반환합니다.


11. BeautifulSoup 내장 함수

함수반환 타입설명
find(태그)Tag첫 번째 태그 반환
find(태그, attrs={속성: 값})Tag특정 속성을 가진 첫 번째 태그
find_all(태그)ResultSet모든 태그 반환 (리스트 형태)
find_all(태그, limit=n)ResultSet최대 n개 반환

⚠️ find(), find_all()Tag 타입에서만 사용 가능합니다.
ResultSet(리스트 형태)에서는 직접 사용 불가 → 반복문으로 각 Tag에 접근해야 합니다.

soup.find('a')              # 첫 번째 a 태그 (Tag 타입)
soup.find_all('a')          # 모든 a 태그 (ResultSet 타입)

a_tag = soup.find('a')
a_tag.find('span')          # Tag에서 find() 가능 ✅

a_list = soup.find_all('a')
# a_list.find('span')       # ResultSet에서 find() 불가 ❌

모든 a 태그 콘텐츠 추출 — 3가지 방법

a_list = soup.find_all('a')

# 방법 1 — for문
a_datas = []
for tag in a_list:
    a_datas.append(tag.string)

# 방법 2 — map() + lambda
list(map(lambda x: x.string, a_list))

# 방법 3 — 리스트 컴프리헨션 ✅ 권장
[data.string for data in a_list]


12. 네이버 증권 크롤링

특정 태그 찾기 — attrs 조건

# requests 라이브러리를 이용해서 html 문서를 받는다
result = requests.get('https://finance.naver.com')
# 응답 데이터를 문자형으로 변환 -> BeautifulSoup 파싱
soup = bs(result.text, 'html.parser')

# html문서 안에서 태그명이 div이고 class 속성의 값이 section_sise_top인 태그 찾기
div_data = soup.find('div', attrs={'class': 'section_sise_top'})

# div 안에서 class가 'tbl_home'인 table 모두 찾기 (8개 나옴)
tables = div_data.find_all('table', attrs={'class': 'tbl_home'})

테이블 1개 → DataFrame으로 변환

table_data = tables[0]

# thead → 컬럼명 추출
head_data = table_data.find('thead')
th_list   = head_data.find_all('th')
cols      = [th.string for th in th_list]

# tbody → 값 추출
body_data = table_data.find('tbody')
tr_list   = body_data.find_all('tr')

values = []
for tr in tr_list:
    row_list   = tr.find_all(['th', 'td'])          # th, td 동시에 찾기
    value_data = [data.get_text().strip() for data in row_list]
    values.append(value_data)

import pandas as pd
pd.DataFrame(values, columns=cols)

💡 string vs get_text()

  • string — 태그 안 콘텐츠가 단일 텍스트일 때만 반환
  • get_text() — 태그 안 모든 콘텐츠 추출 (중첩 태그 포함)
  • 크롤링에서는 get_text().strip() 이 더 안전합니다.




8개 테이블 전체 → 전역변수에 저장

# 8개의 테이블 중 1개 완성
# 8번 반복을 하는 반복문을 생성 -> 각각의 데이터프레임이 생성 -> 각각 다른 전역 변수에 저장( globals()['변수명] )

# 전역변수의 이름을 반복 실행할때마다 변경하기 위한 기본값
vari_num = 1

for table_data in tables:
	# thead를 추출 -> columns 데이터 추출하기 위해서
    head_data = table_data.find('thead')
    # thead 데이터에서 각각 콘텐츠 데이터를 추출하여 리스트화
    cols      = [th.string for th in head_data.find_all('th')]
	
    # tbody를 추출
    body_data = table_data.find('tbody')
    # 여러개의 행이 존재 -> tr_list를 생성
    tr_list   = body_data.find_all('tr')
	
    # 각각의 tr_data를 추출하여 콘텐츠 데이터를 리스트화 하고 새로운 리스트에 추가
    values = []
    for tr in tr_list:
        row_list   = tr.find_all(['th', 'td'])
        value_data = [data.get_text().strip() for data in row_list]
        values.append(value_data)
	
    # 데이터프레임을 생성하고 전역변수에 저장
    globals()[f'df{vari_num}'] = pd.DataFrame(values, columns=cols)
    vari_num += 1

# df1 ~ df8 생성됨



pd.read_html() — 빠른 테이블 크롤링

# !pip install html5lib
dfs = pd.read_html(str(div_data))   # div 안의 table을 바로 DataFrame 리스트로 반환
dfs[1]

💡 pd.read_html()은 HTML 안의 <table> 태그를 자동으로 찾아 DataFrame 리스트로 반환합니다.
BeautifulSoup으로 직접 파싱하는 것보다 훨씬 간결합니다.


13. 주식 종목 데이터 크롤링 & CSV 저장

주식 종목 데이터 확인

import pandas as pd
import requests
from bs4 import BeutifulSoup as bs

dfs = pd.read_html('https://comp.wisereport.co.kr/company/c1010001.aspx?cmp_cd=005930', encoding = 'cp949')

dfs[2][0][0].split()[0]
dfs[3]

종목 코드 5개 크롤링 + CSV 저장

import pandas as pd
import requests
# 크롤링에서 반복문을 이용해서 사이트에 접속 -> 딜레이 기능 부여
import time
# 현재 시간을 로드
from datetime import datetime

# 오늘 날짜 — 파일명에 사용
now     = datetime.now()
# 시계열 데이터에서 원하는 데이터만 문자로 추출하는 방법 : strftime(format)
# 2자리년도수-월-일
now_str = now.strftime('%y-%m-%d')

code_list = ['005930', '009150', '000660', '080220', '096770']
base_url  = 'https://comp.wisereport.co.kr/company/c1010001.aspx?cmp_cd='

for code in code_list:
    df = pd.read_html(base_url + code, encoding='cp949')[3]   # 4번째 표 선택
    df.to_csv(f'./{code} {now_str}.csv')                       # 종목코드 날짜.csv로 저장
    time.sleep(1)                                              # 딜레이 — 서버 부하 방지

💡 time.sleep(n) — n초 딜레이
크롤링 시 서버에 과도한 요청을 보내지 않기 위해 반드시 딜레이를 추가합니다.

💡 파일명에 now_str을 포함하면 날짜별로 새로운 파일이 생성됩니다.
포함하지 않으면 같은 파일에 데이터가 덮어씌워집니다.

사용자 입력으로 종목 코드 동적 수집

code_list = []

# 반복문을 생성을 하는데 반복 횟수는 명확하지 않다.
while True:
    input_code = input('종목 코드를 입력하시오 (종료하려면 ENTER)')

    if len(input_code) == 6:       # 종목 코드는 6자리
        code_list.append(input_code)

    if not bool(input_code):       # 빈 입력이면 종료
        break

# 입력된 종목 코드로 크롤링 실행
now     = datetime.now()
now_str = now.strftime('%y-%m-%d')

for code in code_list:
	 # read_html을 이용해서 table데이터를 크롤링 -> 4번째 표를 선택
    df = pd.read_html(base_url + code, encoding='cp949')[3]
    # 데이터프레임을 csv 파일로 저장 (파일의 이름은 종목코드.csv)
    # 데이터프레임 타입에서 csv 타입으로 변경 -> 타입이 변경될때 사용하는 키워드(to)
    # 같은 파일에 새로운 데이터가 입혀지는 형태
    # 날짜별로 실행할때마다 새로운 파일이 생성(현재 시간을 로드 파일명으로 사용)
    df.to_csv(f'./{code} {now_str}.csv')
    # 시간의 딜레이 생성
    time.sleep(1)

14. wise.py — 크롤링 모듈화

반복적으로 사용하는 크롤링 코드를 별도의 .py 파일로 분리하면 재사용이 편합니다.

import pandas as pd
import time
from datetime import datetime

# 종목 코드를 사용자 입력으로 수집
code_list = []

while True:
    input_code = input('종목 코드를 입력하시오 ( 입력값 종료 시 ENTER )')

    if len(input_code) == 6:        # 종목 코드는 6자리
        code_list.append(input_code)

    if not bool(input_code):        # 빈 입력 → 반복 종료
        break

# 오늘 날짜 — 파일명에 사용
now     = datetime.now()
now_str = now.strftime('%y-%m-%d')

base_url = 'https://comp.wisereport.co.kr/company/c1010001.aspx?cmp_cd='

for code in code_list:
    # 4번째 표(index 3)를 크롤링
    df = pd.read_html(base_url + code)[3]

    # '종목코드 날짜.csv' 형태로 저장
    # → 같은 종목을 다른 날짜에 실행하면 새로운 파일이 생성됨
    df.to_csv(f'./{code} {now_str}.csv')

    # 서버 부하 방지를 위한 딜레이
    time.sleep(1)

실행 흐름

사용자 입력 (while True)
    ↓
6자리 코드 → code_list에 추가
엔터 입력 → 반복 종료
    ↓
오늘 날짜 문자열 생성 (now_str)
    ↓
for code in code_list:
    pd.read_html(URL)[3]   → 4번째 테이블 크롤링
    df.to_csv(파일명)      → CSV 저장
    time.sleep(1)          → 1초 딜레이

💡 모듈화의 장점

  • Jupyter Notebook이 아닌 터미널에서 바로 실행 가능 (python wise.py)
  • 다른 프로젝트에서 import wise 로 재사용 가능
  • 코드를 기능별로 분리해 유지보수가 편해짐

📎 핵심 함수 요약

함수설명
requests.get(url, params)웹 서버에 GET 요청
res.text응답 데이터를 문자열로 반환
res.content응답 데이터를 bytes로 반환
bs(html, 'html.parser')HTML 문자열 → BeautifulSoup 파싱
soup.태그명첫 번째 태그 반환
soup.태그명.string첫 번째 태그의 텍스트 반환
soup.태그명['속성']첫 번째 태그의 속성값 반환
soup.find(태그, attrs={})조건에 맞는 첫 번째 태그 반환 (Tag)
soup.find_all(태그)조건에 맞는 모든 태그 반환 (ResultSet)
태그.get_text().strip()태그 안 전체 텍스트 추출 + 공백 제거
pd.read_html(url)URL의 table 태그를 DataFrame 리스트로 반환
df.to_csv('파일명.csv')DataFrame → CSV 파일 저장
time.sleep(n)n초 딜레이
datetime.now().strftime(포맷)현재 시간을 문자열로 변환
profile
공부 기록

0개의 댓글