Skip to main content

05. 웹 스크래핑 기초 익히기

About 2 minPythoncrashcoursepythonpygooglegoogle-colabjupyter-notebooknumpypandasipython

05. 웹 스크래핑 기초 익히기 관련


05. 웹 스크래핑 기초 익히기

금융 데이터 분석을 위한 파이썬 - WikiDocs

01. 크롤링 기초

크롤링

크롤링이란 웹 페이지로부터 데이터를 추출하는 행위를 말합니다. 그래서 크롤링하는 소프트웨어는 크롤러(crawler)라고 부르지요. 크롤링에 대해서 본격적으로 들어가기 전에, 우리가 데이터를 수집할 웹에 대해서 먼저 이해해보겠습니다.

우리는 인터넷 익스플로러, 크롬 등과 같은 '인터넷 브라우저'를 사용하여 인터넷 세상을 돌아다닙니다. 인터넷 브라우저를 통해서 NAVER, FACEBOOK과 같은 깔끔하고 예쁜 웹 사이트들을 방문할 수 있지요. 그런데 이 웹 사이트들은 실은 HTML이라는 웹 문서로 구성되어져 있습니다.

HTML

HTML이란 웹 페이지를 만들기 위한 언어로 웹 브라우저에서 동작하는 언어입니다.

  • H: HyperText, 문서와 문서가 링크로 연결되어져 있다.
  • M: Markup, 태그로 이루어져있다.
  • L: Language

아래는 간단하게 작성된 HTML 문서의 예제입니다

<!DOCTYPE html>
<html>
  <head>
    <title> HTML 문서 </title>
  </head>
  <body>
    <h1> 이것은 HTML 문서입니다! </h1>
  </body>
</html>

HTML 문서는 정해진 문법에 의해서 작성되어져 있습니다. 물론, 네이버와 같은 웹 사이트들의 HTML 문서는 위의 간단한 예제보다는 훨씬 복잡한 구조를 가지고 있겠지요? 웹 페이지에서 마우스 우클릭 후에 '소스 보기'를 클릭하면 HTML 문서의 소스 코드를 확인할 수 있습니다.

http://www.naver.com 으로 이동하여 소스 코드를 확인해봅시다. 아래는 네이버의 HTML 소스 코드 중 일부를 발췌했습니다.

<div class="direct_area">
  <a href="http://news.naver.com/" class="link_news" data-clk="newshome">네이버뉴스</a>
  <a href="http://entertain.naver.com/home" class="link_direct" data-clk="entertainment">연예</a>
  <a href="http://sports.news.naver.com/" class="link_direct" data-clk="sports">스포츠</a>
</div>

위와 같이 수많은 꺽쇠들의 조합으로 구성된 것들을 태그(Tag) 라고 합니다. 위의 HTML 코드는 여러 개의 중첩된 태그로 구성되어져 있는 셈이죠. <div> 태그, <a> 태그 등과 같이요.

태그(Tag)

태그란 정보를 정의하는 형식을 말해요. 일반적인 태그의 형식을 봅시다.

<태그명 속성명1="속성값1" 속성명2="속성값2"> 콘텐츠 </태그명>

태그는 콘텐츠를 감싸서 그 정보의 성격과 의미를 정의합니다.


02. BeautifulSoup

간단한 실습을 통해 BeautifulSoup4의 사용법을 익혀봅시다. 설치할 때는 beautifulSoup4라는 이름으로 설치했지만, 임포트할 때는 from bs4라고 적어주셔야 합니다. 설치할 때의 이름과 임포트할 때의 패키지 이름이 항상 동일하지는 않습니다. 이는 BeautifulSoup라는 패키지에서 정한 규칙이니까 따라주세요.

pip install beautifulSoup4
# bs4라는 패키지로부터 BeautifulSoup라는 모듈을 임포트
from bs4 import BeautifulSoup

# HTML 문서를 문자열 html로 저장
html = '''
<html> 
    <head> 
    </head> 
    <body> 
        <h1> 장바구니
            <p id='clothes' class='name' title='라운드티'> 라운드티
                <span class = 'number'> 25 </span> 
                <span class = 'price'> 29000 </span> 
                <span class = 'menu'> 의류</span> 
                <a href = 'http://www.naver.com'> 바로가기 </a> 
            </p> 
            <p id='watch' class='name' title='시계'> 시계
                <span class = 'number'> 28 </span>
                <span class = 'price'> 32000 </span> 
                <span class = 'menu'> 악세서리 </span> 
                <a href = 'http://www.facebook.com'> 바로가기 </a> 
            </p> 
        </h1> 
    </body> 
</html>
'''

# BeautifulSoup 인스턴스 생성. 두번째 매개변수는 분석할 분석기(parser)의 종류.
soup = BeautifulSoup(html, 'html.parser')

맨 아랫 줄에 있는 코드를 볼까요? BeautifulSoup(입력 문자열, 'html.parser')라는 코드는 이 입력은 HTML 문법으로 작성되어져 있으니 HTML 문법을 기반으로 파싱하라는 의미입니다. 이렇게 인스턴스를 생성하고나면 본격적으로 정보를 가져오는 것은 select()를 가지고 수행합니다.

인스턴스의 이름은 soup이므로 soup.select()를 통해서 정보를 가져옵니다. select()의 사용 방법은 요약하자면 soup.select('찾는 정보')입니다. 구체적인 예시는 다음과 같습니다.

  • soup.select('태그명'): 태그를 입력으로 사용할 경우
  • soup.select('.클래스명'): 클래스를 입력으로 사용할 경우
  • soup.select('#아이디'): ID를 입력으로 사용할 경우
  • soup.select('상위태그 하위태그'): 자손 관계 (띄어쓰기)
  • soup.select('상위태그 > 하위태그'): 자식 관계 ( > )

실습을 통해 이해해볼까요? 태그명 <body>를 입력으로 내부 정보를 가져와보겠습니다.

# <body> 태그를 입력
print(soup.select('body'))
#
# [<body>
# <h1> 장바구니
#             <p class="name" id="clothes" title="라운드티"> 라운드티
#                 <span class="number"> 25 </span>
# <span class="price"> 29000 </span>
# <span class="menu"> 의류</span>
# <a href="http://www.naver.com"> 바로가기 </a>
# </p>
# <p class="name" id="watch" title="시계"> 시계
#                 <span class="number"> 28 </span>
# <span class="price"> 32000 </span>
# <span class="menu"> 악세서리 </span>
# <a href="http://www.facebook.com"> 바로가기 </a>
# </p>
# </h1>
# </body>]

내부의 정보들을 모두 가져온 것을 확인할 수 있습니다.

이번에는 자손 관계를 입력으로 하여 classmenu인 경우만을 출력해보겠습니다. 태그명 h1 자손인 클래스 .name의 자손인 클래스 .menu의 정보를 찾으면?

print(soup.select('h1 .name .menu'))
#
# [<span class="menu"> 의류</span>, <span class="menu"> 악세서리 </span>]

classmenu인 부분의 정보를 잘 가져온 것을 확인할 수 있습니다.

이번에는 의도적으로 잘못된 입력을 넣어볼게요. 태그명 h1은 태그명 html의 자손이기는 하지만, 자식은 아닙니다. 만약, html 자식인 h1의 정보를 가져오라고 하면 어떻게 될까요?

print(soup.select('html > h1'))
# []

아무런 정보도 출력되지 않습니다.


03. pd.read_html()을 통한 테마주 추출

pd.read_html은 웹 페이지로부터 테이블을 추출하여 데이터프레임으로 저장하는 코드입니다.

  • pd.read_html('테이블이 존재하는 URL')

금융 관련 사이트 하나를 선택하여 예시를 들어서 설명해보겠습니다.

링크: http://m.infostock.co.kr/sector/sector.asp?mode=w

예를 들어 위 사이트는 테마와 해당 테마들에 대한 종목을 알려주는 사이트입니다. 해당 사이트에서 '2차 전지 테마주 링크'로 이동하면 다음과 같습니다.

링크: http://m.infostock.co.kr/sector/sector_detail.asp?code=64&theme=2%uCC28%uC804%uC9C0&mode=w

해당 링크에 대해서 pd.read_html()을 수행해볼까요?

import pandas as pd

pd.read_html('http://m.infostock.co.kr/sector/sector_detail.asp?code=64&theme=2%uCC28%uC804%uC9C0&mode=w')
#
# [    0   1   2     3   4
#  0 NaN NaN NaN  회원가입 NaN,
#                         0                                                  1
#  0                    테마명                                               2차전지
#  1                   테마개요  2차전지(Secondary battery)란 한 번 쓰고 버리는 배터리가 아닌 재충...
#  2                테마 히스토리  (2022-07-22) 테슬라, 실적 호조에 따른 주가 급등 영향 등에 상승(주도주...
#  3                   관련종목                                             테마편입사유
#  4    - LG에너지솔루션 (373220)  LG그룹 계열의 전지사업 전문 업체. LG화학의 전지사업 부문이 물적분할되어 설립됐...
#  ..                   ...                                                ...
#  96         - 엔켐 (348370)  2차전지 및 EDLC용 전해액, 첨가제 제조 및 판매업체. 소형 2차전지용 전해액(...
#  97      - 엘아이에스 (138690)  22년1월 2차전지 시장 진출을 위해 전기차 배터리팩 회사 티엔디 지분 인수. 티엔...
#  98     - 세아메카닉스 (396300)  알류미늄 다이캐스팅 전문 업체. LG에너지솔루션과 협력 관계를 맺으며 미래형 전기차...
#  99      - 이엔플러스 (074610)  21년4월 그래핀과 CNT를 결합한 복합 도전재 개발 성공. 22년5월 그리너지와 ...
#  100        - 레몬 (294140)  22년6월 고강도, 열 안정성, 박막화의 전기자동차용 고체 전지 전해질 지지체 개발...
# 
#  [101 rows x 2 columns]]

해당 주소에 있는 테이블들을 가져온 것처럼 보입니다. pd.read_html() 수행 후에는 해당 주소에 있는 테이블들을 모두 수집하여 테이블들의 리스트를 반환하므로 테이블을 몇 개 가져왔는지 확인해야 합니다.

len(pd.read_html('http://m.infostock.co.kr/sector/sector_detail.asp?code=64&theme=2%uCC28%uC804%uC9C0&mode=w'))
#
# 2

2개를 가져왔다고 나옵니다. 첫번째 테이블을 확인해봅시다.

pd.read_html('http://m.infostock.co.kr/sector/sector_detail.asp?code=64&theme=2%uCC28%uC804%uC9C0&mode=w')[0]
테마주테이블
테마주테이블

첫번째 테이블은 별 다른 의미가 없습니다. 두번째 테이블을 출력해봅시다.

pd.read_html('http://m.infostock.co.kr/sector/sector_detail.asp?code=64&theme=2%uCC28%uC804%uC9C0&mode=w')[1]
테마주테이블2
테마주테이블2

실질적으로 우리가 원하는 테이블에 해당됩니다. 테마주 테이블이 수집되었습니다. 간단한 전처리를 통해서 2차 전지에 대한 테이블. 즉, 깔끔한 데이터프레임을 얻어봅시다. 우선 위 테이블에서 위의 3개의 행은 제거해보겠습니다. 불필요한 테마명, 테마개요, 테마 히스토리에 대한 행을 날리려고 하는 것입니다.

df = pd.read_html('http://m.infostock.co.kr/sector/sector_detail.asp?code=64&theme=2%uCC28%uC804%uC9C0&mode=w')[1]
df = df[3:] # 상위 3개의 행 제거
df
테마주테이블3
테마주테이블3

위의 상위 3개의 행이 제거되었습니다. 첫번째 행을 데이터프레임의 열 제목으로 쓰면 좋을 것 같습니다. df.iloc[인덱스]를 통해서 특정 행을 추출 가능합니다. 첫번째 행. 즉, 0번 인덱스를 통해 첫번째 행을 추출해봅시다.

df.iloc[0]
#
# 0      관련종목
# 1    테마편입사유
# Name: 3, dtype: object

이를 파이썬 리스트로 변환해줍니다.

df.iloc[0].to_list()
#
# ['관련종목', '테마편입사유']

df.columns = 리스트를 사용하면 데이터프레임의 열 이름들을 변경할 수 있습니다.

df.columns = df.iloc[0].to_list()
df
테마주테이블5
테마주테이블5

이제 첫번째 행은 열 제목이 되었으므로 아직 남아있는 첫번째 행은 날립니다.

df = df[1:]
df
테마주테이블5
테마주테이블5

데이터프레임에 앞에 붙은 인덱스가 4부터 시작하고 있습니다. 이는 우리가 앞에서 총 4개의 행을 날렸기 때문인데요. 데이터프레임의 인덱스의 숫자의 순서가 정렬 과정, 또는 일부 행을 삭제하는 과정에서 순서가 뒤죽박죽이 되었다면 인덱스를 처음부터 다시 재할당하는 방법이 있습니다.

df = df.reset_index(drop=True)
df
테마주테이블8
테마주테이블8

이찬희 (MarkiiimarK)
Never Stop Learning.