ASAC-SK플래닛 T아카데미 데이터 엔지니어

25.11.24 33일차 [데이터 ETL(Extract, Transform, Load) Transform(변환,전처리), LOAD(적재) 1차 정리 | 데이터수집 레벨3]

Datadesigner 2025. 11. 24. 17:49

오늘은 저번주에 이어서 데이터 변환, 전처리와 적재, 그리고 제공되는 소스가 아닌 직접 웹 사이트를 크롤링하는

다음레벨로 넘어갈 예정이다, 실습 예정은 네이버 증권의 환율을 데이터베이스에 저장하는것이다.


Transform ( 변환 ) -> 전처리, 데이터 준비

적재를 위한 최적 형태로 반환

 

# 데이터 클린 작업
sample = "亞 장초반...<b>한국증시</b> &amp; 호주증시 '급등', <b>엔화는 '절하'"

def clean( src ):
  '''
    - 결측치, 이상치 처리
    - 노이즈 처리 (제거 OR 대체)
  '''
  # <b> 제거
  # replace() => 모두 대체 가능 => 종류(패턴이 다양)이 많으면 점점 복잡해짐(여러번기술)
  src = src.replace('<b>', '').replace('</b>', '') # 계속 추가됨
  return src

clean(  sample )

亞 장초반...한국증시 &amp; 호주증시 '급등', 엔화는 '절하'

 

# 1. 정규식 라이브러리(혹은 패키지) 가져오기
import re

# 2. 패턴 정의 -> html을 특정하는 졍규식
# a-z : 알파벳 소문자 특정
# 0-9 : 숫자를 특정 (10진법)
# [표현] : 문자 클레스, 문자 1개를 특정
# [a-z0-9] : 문자 1개가 올 수 있는데, 그 문자는 a-z0-9(알파벳과 숫자만 올수 있다) 이다
# + : 1~무한대로 반복해서 등장할 수 있다
# | : or 이것든 저것든 다 OK
# [a-z0-9]+ : 문자 1개가(a-z0-9) 1~무한대로 반복 등장 가능
# re.IGNORECASE : 대소문자 구분 x
# re.compile("<[a-zA-Z0-9]+>|</[a-zA-Z0-9]+>") => 아래와 같은 결과
pattern = re.compile("<[a-z0-9]+>|</[a-z0-9]+>", re.IGNORECASE)

# 데이터 클린 작업
sample = "亞 장초반...<b>한국증시</b> &amp; <h1>호주증시</h1> '급등', <b>엔화는 '절하'"

def clean( src ):
  '''
    - 결측치, 이상치 처리
    - 노이즈 처리 (제거 OR 대체)
  '''
  # <b> 제거 -> 정규식
  src = pattern.sub("", src) # src가 가진 텍스트에 패턴이 잡히면 ""로 대체
  return src

clean(  sample )

亞 장초반...한국증시 &amp; 호주증시 '급등', 엔화는 '절하'

 

 

# &amp; -> & 대체처리
import html
import re
pattern = re.compile("<[a-z0-9]+>|</[a-z0-9]+>", re.IGNORECASE)
sample = "亞 장초반...<b>한국증시</b> &amp; &quot; <h1>호주증시</h1> '급등', <b>엔화는 '절하'"

def clean( src ):
  '''
    - 결측치, 이상치 처리
    - 노이즈 처리 (제거 OR 대체)
  '''
  src = html.unescape( src ) # &amp; -> &
  src = pattern.sub("", src)
  return src

clean(  sample )

亞 장초반...한국증시 & " 호주증시 \'급등\', 엔화는 \'절하\'

 

 

# news 자료구조에서 제목만 추출하시오 -> [ "", "", "", ..]
# 리스트 컴프리핸션(내포)
[ clean(item['title']) for item in news ]

# 데이터에 노이즈 존재 -> 검색어 표기 <b>, </b>, &quot;->" -> clean 처리
# <b>, </b> => replace(), 정규식
# &quot;->" => html 라이브러리 사용

 

 

  • 실습
    • 최종 추출한 데이터 형태
        [
          {
            "title":"내용",
            "description":"내용",
            "pubDate":"내용"
          },
          {
            ...
          }
          , ...
        ]

 

# 실습 순서 정리

# 임시로 10개만 추출 -> 슬라이싱
news = news[:10]

# 1. 형태부터 구성
[ {} for item in news ]

[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]


# 2. 형태 채움
[ {
    "title"      : "내용",
    "description": "내용",
    "pubDate"    : "내용"
} for item in news ]

[{'title': '내용', 'description': '내용', 'pubDate': '내용'},
 {'title': '내용', 'description': '내용', 'pubDate': '내용'},
 {'title': '내용', 'description': '내용', 'pubDate': '내용'},
 {'title': '내용', 'description': '내용', 'pubDate': '내용'},
 {'title': '내용', 'description': '내용', 'pubDate': '내용'},
 {'title': '내용', 'description': '내용', 'pubDate': '내용'},
 {'title': '내용', 'description': '내용', 'pubDate': '내용'},
 {'title': '내용', 'description': '내용', 'pubDate': '내용'},
 {'title': '내용', 'description': '내용', 'pubDate': '내용'},
 {'title': '내용', 'description': '내용', 'pubDate': '내용'}]
 
 # 3. 데이터 채움
final_news = [ {
    "title"      : clean( item['title']       ),
    "description": clean( item['description'] ),
    "pubDate"    : item['pubDate']
} for item in news ]

# 1개만 샘플링
final_news[:1]

[{'title': '[리스트] 시장 약세..큰손의 선택은?',
  'description': '12월 연방공개시장위원회(FOMC)를 앞둔 가운데 당초 기대와 달리 기준금리 인하 가능성이 낮아졌다는 우려, AI 버블 논란 등이 시장 약세에 영향을 준 것으로 보입니다. 수급 주체 측면에선 외국인 투자자가 국내... ',
  'pubDate': 'Mon, 24 Nov 2025 09:16:00 +0900'}]

 

 

Load (적재)

 

 AWS상 데이터베이스에 저장

 

[ {} , . . ] ->  DataFrame으로 변환

 

# 1. [{},..] 형태의 데이터를 pandas의 DataFrame으로 변환
import pandas as pd

df = pd.DataFrame.from_dict( final_news )

# 상위값 2개만 확인
df.head(2)

 

출력값

 

데이터베이스에 입력

 

!pip install pymysql -q # 코랩에서 해서 이렇게 한다.

import pymysql
from sqlalchemy import create_engine

# 2. 접속 정보 준비
HOST = "***.***.***.***"
USER = "*****"
PW   = "****"
PORT = 3306
DB_NAME    = "memo"
TABLE_NAME = "tbl_news"       # 없으면 생성함
PROTOCAL   = "mysql+pymysql"

# 3. 접속 URL 준비
db_url = f'{PROTOCAL}://{USER}:{PW}@{HOST}:{PORT}/{DB_NAME}'
db_url

# 디비 입력 처리
# 4. 엔진생성
engine = create_engine( db_url )

# 5. 접속
conn = engine.connect()

# 6. 데이터를 데이터베이스에 적제 -> Load
#    데이터는 계속 뒤쪽으로 추가(누적적제), 인덱스 정보 제거(데이터 아님)
df.to_sql( name=TABLE_NAME, con=conn, if_exists='append', index=False)

# 7. 접속 해제
conn.close()

#

1차 정리

 

 

 

  • 소규모
    • 파이썬 + 자동화
    • 파이프라인 구성 시각화 및 처리
      • KNIME툴 활용 (자바 베이스라서 전처리시 표현이 와일드함(간결하지 않음))
  • 대규모
    • AWS Glue
    • Apache Airflows
    • Kinesis
    • ...
  •  

최종 코드

 

import json  # 문자열 -> 역직렬화 -> 리스트, 딕셔너리 구성, 반대도 제공(직렬화)
import urllib.request
# 편의상 해당값을 세팅하고 수행 -> git 등 공유 금지
client_id     = "************"
client_secret = "**********"

# 결과 : [ {}, {}, {}, ....]
def get_news( keyword : str ) -> list:
  encText   = urllib.parse.quote( keyword )
  url       = "https://openapi.naver.com/v1/search/news.json?display=100&query=" + encText
  request   = urllib.request.Request(url)
  request.add_header("X-Naver-Client-Id",client_id)
  request.add_header("X-Naver-Client-Secret",client_secret)
  response  = urllib.request.urlopen(request)
  rescode   = response.getcode()
  if(rescode==200):
      # json 모듈을 이용하여 원래 자료구조 형태로 복원(역직렬화)
      res = json.load( response )
      return res
  else:
      return [] # 에러 -> 검색 못한것임 -> 결과 없음

today_news = get_news('AI 버블')
news = today_news.get("items")

import re
import html
pattern = re.compile("<[a-z0-9]+>|</[a-z0-9]+>", re.IGNORECASE)
def clean( src ):
  '''
    - 결측치, 이상치 처리
    - 노이즈 처리 (제거 OR 대체)
  '''
  src = html.unescape( src ) # &amp; -> &
  src = pattern.sub("", src)
  return src

# 3. 데이터 채움
final_news = [ {
    "title"      : clean( item['title']       ),
    "description": clean( item['description'] ),
    "pubDate"    : item['pubDate']
} for item in news ]

# 1. [{},..] 형태의 데이터를 pandas의 DataFrame으로 변환
import pandas as pd

df = pd.DataFrame.from_dict( final_news )

import pymysql
from sqlalchemy import create_engine

# 2. 접속 정보 준비
HOST = "************"
USER = "*****"
PW   = "****"
PORT = 3306
DB_NAME    = "memo"
TABLE_NAME = "tbl_news100"       # 없으면 생성함
PROTOCAL   = "mysql+pymysql"

# 3. 접속 URL 준비
db_url = f'{PROTOCAL}://{USER}:{PW}@{HOST}:{PORT}/{DB_NAME}'
db_url

# 디비 입력 처리
# 4. 엔진생성
engine = create_engine( db_url )

# 5. 접속
conn = engine.connect()

# 6. 데이터를 데이터베이스에 적제 -> Load
#    데이터는 계속 뒤쪽으로 추가(누적적제), 인덱스 정보 제거(데이터 아님)
df.to_sql( name=TABLE_NAME, con=conn, if_exists='append', index=False)

# 7. 접속 해제
conn.close()

"""# 1차 정리

- 소규모
  - 파이썬 + 자동화
  - 파이프라인 구성 시각화 및 처리
    - KNIME툴 활용 (자바 베이스라서 전처리시 표현이 와일드함(간결하지 않음))

- 대규모
  - `AWS Glue`
  - Apache Airflows
  - Kinesis
  - ...
"""

 

이 코드를 vs 코드상에서 수행하면 네이버 api에서 뉴스 관련 글들을 긁어와서 통신하고, 데이터를 json형태로 역직렬화하고, <b> 태그, &amp등의 글자들을 전처리해주고, 디에터를 가져오고, DataFrame으로 변환시켜주고, aws로 생성한 데이터베이스상에 데이터를 적재해준다.

 

이 과정이 ETL(Extract, Transform, Load)의 과정인것이다.

 

이제 레벨 3로 넘어간다잉

 


 

데이터 추출 변환 level3 

 

개요

 

 
  • workflow
    • 대상
      • 원하는 데이터에 대해 openapi 미지원
      • 웹페이지 접속하면 바로 정보가 노출됨.
    • part 1
      • 사이트 접속 (요청)
      • 응답(html 로 구성됨)
    • part 2
      • html -> parsing(파싱) -> DOM(Document Object Model)을 생성 -> 메모리 접근 가능함.
        • html에서 데이터를 추출할때 기본 형태
        • 웹브라우저는 항상 이렇게 처리함(랜더링)
        • 파싱을 하기위해서는?
          • 파서가 필요함(parser)
            • html5lib, html.parser, ...
        • 이런 모든 행위를 지원 라이브러리
          • bs4
            • DOM 트리 구성
            • DOM 트리 검색 (css selector, xpath)
            • DOM 트리상에 데이터 추출

BS4

 

 

 

Part 1

 

 
네이버 증권 > 환율정보 > 창에 오른쪽 마우스 클릭 > 프레임 소스 보기 > url https 앞부분 지우기
이거 앞에 지워주면 된다.

 

part 1

import urllib.request as req

TARGET_SITE ='https://finance.naver.com/marketindex/exchangeList.naver'

# 요청 및 응답
res = req.urlopen(TARGET_SITE)

 

코드 설명

part 2

 

from bs4 import BeautifulSoup

# parser 를 가지고 DOM tree 구성, 좀 느리지만 꼼꼼히 체크하는 parser 사용 공홈에서
soup = BeautifulSoup( res, "html5lib" ) # html5lib은 코랩에 이미 설치되어있음
soup

# 이 코드의 결과값은 2000행이 넘기 때문에 직접 해보길 권장한다

이런 식으로 모든 html 정보를 읽어온다.

 

매매 기준율 추출

 

# 브라우저상 데이터 부분 > 우클릭 > 검사 > css selector (직관적)
# 브라우저상 데이터 부분 > 우클릭 > 검사 > 해당 요소 > 우클릭 > 복사 > css selector (기계적)
for exchange in soup.select("td.sale"):
  # 특정 요소의 value 추출
  print(exchange.string,exchange.text)
  break
  
 1,476.70 1,476.70

개발자 모드에서 왼쪽 위 화살표 클릭
마우스를 올리면 해당 html 정보가 나온다

이렇게 구해온 id를 사용해서 특정 요소의 value를 dict 문법을 사용해서 출력한다.

 

# 실습 -> [ 1.473.80, xxx, xxx , xxx . . .] 58개국의 매매기준율 추출하시오

# 리스트컴프리헨션
[float(exchange.text.replace(',','')) for exchange in soup.select("td.sale")]
# 특정 요소의 value 추출

[1476.7,
 1704.04,
 941.02,
 207.85,
 189.76,
 46.98,
 1935.07,
 3835.58,
 1047.27,
 1828.84,
 154.88,
 953.14,
 828.28,
 70.49,
 1.57,
 34.81,
 0.41,
 451.14,
 228.14,
 144.49,
 393.72,
 4805.25,
 3916.87,
 402.05,
 2082.79,
 30.92,
 45.53,
 1131.96,
 356.56,
 8.86,
 405.21,
 2.84,
 1131.96,
 16.56,
 5.23,
 12.07,
 25.1,
 79.98,
 273.37,
 5.61,
 85.33,
 18.78,
 4.46,
 402.7,
 4.8,
 11.3,
 11.38,
 0.39,
 0.6,
 10.34,
 334.96,
 270.02,
 184.13,
 0.7,
 9.61,
 0.12,
 0.37,
 644.36]

 

리스트컴프리핸션을 사용하고, 부동소수이기때문에 float를 추가해준다,

그리고 replace로 콤마를 제거해주어야 정상적으로 출력된다.

 

# 통화명 [ "", "", "", . . .]
# 웹 문서 => 문자열 추출 => 양쪽 공백 제거(노이즈)
[exchange.string for exchange in soup.select("td.tit")]


['\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t미국 USD\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t유럽연합 EUR\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t일본 JPY (100엔)\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t중국 CNY\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t홍콩 HKD\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t대만 TWD\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t영국 GBP\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t오만 OMR\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t캐나다 CAD\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t스위스 CHF\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t스웨덴 SEK\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t호주 AUD\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t뉴질랜드 NZD\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t체코 CZK\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t칠레 CLP\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t튀르키예 TRY\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t몽골 MNT\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t이스라엘 ILS\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t덴마크 DKK\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t노르웨이 NOK\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t사우디아라비아 SAR\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t쿠웨이트 KWD\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t바레인 BHD\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t아랍에미리트 AED\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t요르단 JOD\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t이집트 EGP\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t태국 THB\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t싱가포르 SGD\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t말레이시아 MYR\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t인도네시아 IDR 100\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t카타르 QAR\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t카자흐스탄 KZT\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t브루나이 BND\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t인도 INR\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t파키스탄 PKR\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t방글라데시 BDT\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t필리핀 PHP\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t멕시코 MXN\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t브라질 BRL\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t베트남 VND 100\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t남아프리카 공화국 ZAR\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t러시아 RUB\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t헝가리 HUF\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t폴란드 PLN\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t스리랑카 LKR\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t알제리 DZD\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t케냐 KES\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t콜롬비아 COP\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t탄자니아 TZS\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t네팔 NPR\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t루마니아 RON\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t리비아 LYD\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t마카오 MOP\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t미얀마 MMK\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t에티오피아 ETB\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t우즈베키스탄 UZS\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t캄보디아 KHR\n\t\t\t\t\n\t\t\t\t',
 '\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t피지 FJD\n\t\t\t\t\n\t\t\t\t']

 

지금 코드의 경우도 /n , /t 등은 공백을 표기한 문자이다.

strip을 이용해서 공백을 제거해주어야한다.

이전에 배운 파이썬 문법들이 다시 나오는 모

# 통화명 [ "", "", "", . . .]
# 웹 문서 => 문자열 추출 => 양쪽 공백 제거(노이즈)
[exchange.string.strip() for exchange in soup.select("td.tit")]

['미국 USD',
 '유럽연합 EUR',
 '일본 JPY (100엔)',
 '중국 CNY',
 '홍콩 HKD',
 '대만 TWD',
 '영국 GBP',
 '오만 OMR',
 '캐나다 CAD',
 '스위스 CHF',
 '스웨덴 SEK',
 '호주 AUD',
 '뉴질랜드 NZD',
 '체코 CZK',
 '칠레 CLP',
 '튀르키예 TRY',
 '몽골 MNT',
 '이스라엘 ILS',
 '덴마크 DKK',
 '노르웨이 NOK',
 '사우디아라비아 SAR',
 '쿠웨이트 KWD',
 '바레인 BHD',
 '아랍에미리트 AED',
 '요르단 JOD',
 '이집트 EGP',
 '태국 THB',
 '싱가포르 SGD',
 '말레이시아 MYR',
 '인도네시아 IDR 100',
 '카타르 QAR',
 '카자흐스탄 KZT',
 '브루나이 BND',
 '인도 INR',
 '파키스탄 PKR',
 '방글라데시 BDT',
 '필리핀 PHP',
 '멕시코 MXN',
 '브라질 BRL',
 '베트남 VND 100',
 '남아프리카 공화국 ZAR',
 '러시아 RUB',
 '헝가리 HUF',
 '폴란드 PLN',
 '스리랑카 LKR',
 '알제리 DZD',
 '케냐 KES',
 '콜롬비아 COP',
 '탄자니아 TZS',
 '네팔 NPR',
 '루마니아 RON',
 '리비아 LYD',
 '마카오 MOP',
 '미얀마 MMK',
 '에티오피아 ETB',
 '우즈베키스탄 UZS',
 '캄보디아 KHR',
 '피지 FJD']

 

# 국가명, 통화코드를 분리해서 추출
# 통화코드만 추출하시오 ["", "", "", "", . . .]
# 인덱싱으로 나눠서 국가명과 통화코드 나눴다
[exchange.string.strip().split(" ")[0] for exchange in soup.select("td.tit")]

['미국',
 '유럽연합',
 '일본',
 '중국',
 '홍콩',
 '대만',
 '영국',
 '오만',
 '캐나다',
 '스위스',
 '스웨덴',
 '호주',
 '뉴질랜드',
 '체코',
 '칠레',
 '튀르키예',
 '몽골',
 '이스라엘',
 '덴마크',
 '노르웨이',
 '사우디아라비아',
 '쿠웨이트',
 '바레인',
 '아랍에미리트',
 '요르단',
 '이집트',
 '태국',
 '싱가포르',
 '말레이시아',
 '인도네시아',
 '카타르',
 '카자흐스탄',
 '브루나이',
 '인도',
 '파키스탄',
 '방글라데시',
 '필리핀',
 '멕시코',
 '브라질',
 '베트남',
 '남아프리카',
 '러시아',
 '헝가리',
 '폴란드',
 '스리랑카',
 '알제리',
 '케냐',
 '콜롬비아',
 '탄자니아',
 '네팔',
 '루마니아',
 '리비아',
 '마카오',
 '미얀마',
 '에티오피아',
 '우즈베키스탄',
 '캄보디아',
 '피지']

split을 이용해서 공백을 기준으로 양 옆으로 나눠주고, 인덱싱 문법을 사용해서 앞 글자 부분만 출력했다.

 


현재 국가 코드만 출력하고싶다. 그런데 각 나라마다 마지막 부분에 100엔, 100 등 국가코드가 아닌 문자열이 들어가있다, 그러면 어떻게 해야 할까?

for exchange in soup.select("td.tit"):
  print(exchange)
  break
  
  
<td class="tit"><a href="/marketindex/exchangeDetail.naver?marketindexCd=FX_USDKRW" onclick="parent.clickcr(this, 'exl.exlist', 'FX_USDKRW', '1', event);" target="_parent">
				
					
					
					
					미국 USD
				
				</a></td>

 

이 코드로 <a href>의 값만 가져온다, 이후 인덱싱을 통해서 국가코드부분만 가져온다/

 

 

# 답, td요소를 찾고 하위 자식에서 <a>요소를 찾고,
# 거기서 get 을 통해서 속성 href 값을 추출하고,
# 인덱싱을 이용해서 문자열에서 원하는 코드를 출력함

# for exchange in soup.select("td.tit"):
#   print(exchange.select_one('a').get('href')[-6:-3])


[exchange.select_one('a').get('href')[-6:-3] for exchange in soup.select("td.tit")]


['USD',
 'EUR',
 'JPY',
 'CNY',
 'HKD',
 'TWD',
 'GBP',
 'OMR',
 'CAD',
 'CHF',
 'SEK',
 'AUD',
 'NZD',
 'CZK',
 'CLP',
 'TRY',
 'MNT',
 'ILS',
 'DKK',
 'NOK',
 'SAR',
 'KWD',
 'BHD',
 'AED',
 'JOD',
 'EGP',
 'THB',
 'SGD',
 'MYR',
 'IDR',
 'QAR',
 'KZT',
 'BND',
 'INR',
 'PKR',
 'BDT',
 'PHP',
 'MXN',
 'BRL',
 'VND',
 'ZAR',
 'RUB',
 'HUF',
 'PLN',
 'LKR',
 'DZD',
 'KES',
 'COP',
 'TZS',
 'NPR',
 'RON',
 'LYD',
 'MOP',
 'MMK',
 'ETB',
 'UZS',
 'KHR',
 'FJD']

 

 

이 부분이다

데이터를 가져오는데 정해진 방법이 없이 이렇게 머리를 써서 특정한 부분에서 어떻게 데이터를 긁어올 지에 대한 고민을 하고 그 업무를 수행하는것이 기본적인 데이터엔지니어의 업무라고 생각하면 된다.

 

정해진 방법이 없이 자유롭게 데이터를 추출하고, 분석할 수 있는 방식이 정말 신기하다. html에서 긁어오는 이런 방법도 있을줄이야.

 

금일 수업은 여기까지, 이 파트는 내일 이어서 진행한다.