newhaneul

[Advanced Python Programming] Lecture 15. Web Crawling 본문

4. University Study/Advanced Python Programming

[Advanced Python Programming] Lecture 15. Web Crawling

뉴하늘 2026. 5. 27. 01:13
728x90

포스팅은 인하대학교 허혜선 교수님의 [202601-EEC3408-001] 고급파이썬프로그래밍을 수강하고 공부한 내용을 정리하기 위한 포스팅입니다.

 

 

1. HTML 소스 분석 및 BeautifulSoup 활용법

 

웹 브라우저에서 소스 보기

  • 웹 브라우저의 빈 곳에서 마우스 오른쪽 버튼을 클릭하여 [페이지 원본 보기](크롬은 페이지 소스 보기) 또는 [검사]를 선택하면 HTML 코드를 확인할 수 있다.
  • 브라우저의 [개발자 도구]를 활용하면 원하는 웹페이지 요소의 정확한 HTML 태그 위치와 정보를 손쉽게 찾아낼 수 있다.

 

핵심 추출 함수와 속성 활용

HTML을 알아볼 수 있는 형태로 변경해주고 더불어 필요한 내용들을 추출하기 위해서는 BeautifulSoup을 사용해야 한다.

import urllib.request
import bs4
nateUrl =  "https://www.nate.com"
htmlObject = urllib.request.urlopen(nateUrl)
bsObject = bs4.BeautifulSoup(htmlObject, "html.parser")

print(bsObject)

<!DOCTYPE html>

<html lang="ko">
<head>
<meta content="IE=Edge" http-equiv="X-UA-Compatible"/>
<meta content="//www.nate.com/" name="msapplication-starturl"/>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<meta content="" name="nate:title"/>
<meta content="네이트 이슈UP" name="nate:description"/>
<meta content="네이트 홈" name="nate:site_name"/>
<meta content="https://www.nate.com/" name="nate:url"/>
<meta content="" name="nate:image"/>
<meta content="새로워진 네이트에서 당신의 오늘을 만나보세요" name="description"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<meta content="네이트" property="og:title"/>
<meta content="https://www.nate.com/" property="og:url"/>
<meta content="https://main.nateimg.co.kr/img/v7/OpenGraphTag_nate_240x240.png" property="og:image"/>
<meta content="새로워진 네이트에서 당신의 오늘을 만나보세요" property="og:description"/>
<title>네이트</title>
<link href="/css/common.min.css?v=202601291101_02" rel="stylesheet" type="text/css"/>
<link href="//main.nateimg.co.kr/img/v7/favicon_32.ico" rel="shortcut icon" type="image/x-icon">
<link href="https://www.nate.com" rel="canonical"/>
<!-- script type="text/javascript" src="/js/common/jquery-1.9.1.min.js"></script-->
<!-- jquery 3.x 적용을 테스트를 위한 migrate 테스트 적용 -->
...
<!--natemain-->
<script type="text/javascript">//<![CDATA[
 CFN_type = ''; CFN_id = ''; CFN_cn = ''; CFN_cmn = ''; CFN_link = ''; CFN_link_id = ''; id = (CFN_type == 'N') ? CFN_id : CFN_link_id; cyid = (CFN_type == 'C') ? CFN_id : CFN_link_id; name = CFN_cn; isEmpas = ('' == '1'); refererDomain = ''; CFN_cust_tp_cd = ''; CFN_sex_cd = ''; loginType = ''; isMobile = 'F'; CFN_link_cust_tp_cd = ''; CFN_link_stat_cd = ''; var ndrLoginStatus = (isNateLogin) ? 'login' : ((bLogout) ? 'byebye' : 'logout'); try { divReferer(refererDomain); } catch (e) { } try { if (isNateLogin) fillPersonInfo();} catch (e) { } pageEnd('', '/');//]]>
</script></body></html>
Output is truncated. View as a scrollable element or open in a text editor. Adjust cell output settings...

 

긴 HTML 코드에서 필요한 부분을 추출하여 사용할 때는 BeautifulSoup 객체를 생성한 후, 다음과 같은 함수를 사용하여 데이터를 탐색한다.

# Sample02.html

<html>
      <head>
      </head>
      <body>
            <div> 요기를 클릭하세요 </div>
            <ul>
                  <li> 한빛출판네트워크 </li>
                  <li> 비기너 </li>
                  <li> 데이터 분석 </li>
            </ul>
      </body>
</html>
import bs4

webPage = open('Sample02.html', 'rt', encoding='utf-8').read()
bsObject = bs4.BeautifulSoup(webPage, "html.parser")
print(bsObject)

<html>
<head>
</head>
<body>
<div> 요기를 클릭하세요 </div>
<ul>
<li> 한빛출판네트워크 </li>
<li> 비기너 </li>
<li> 데이터 분석 </li>
</ul>
</body>
</html>

 

  • find('태그명'): 조건에 맞는 태그 중 가장 먼저 나오는 1개만 추출한다.
import bs4

webPage = open('Sample02.html', 'rt', encoding='utf-8').read()
bsObject = bs4.BeautifulSoup(webPage, "html.parser")

tag_div = bsObject.find('div')
print(tag_div)

<div> 요기를 클릭하세요 </div>

 

  • find_all('태그명'): 조건에 맞는 모든 태그를 추출하여 리스트 형태로 반환한다.
import bs4

webPage = open('Sample02.html', 'rt', encoding='utf-8').read()
bsObject = bs4.BeautifulSoup(webPage, "html.parser")

tag_ul = bsObject.find('ul')
print(tag_ul)
print()

tag_li = bsObject.find('li')
print(tag_li)
print()

tag_li_all = bsObject.find_all('li')
print(tag_li_all)
<ul>
<li> 한빛출판네트워크 </li>
<li> 비기너 </li>
<li> 데이터 분석 </li>
</ul>

<li> 한빛출판네트워크 </li>

[<li> 한빛출판네트워크 </li>, <li> 비기너 </li>, <li> 데이터 분석 </li>]

 

 

  • 속성 지정 추출: find('태그명', {'속성명': '속성값'}) 형식을 사용하여 특정 id나 class를 가진 태그만 골라낼 수 있다.
# Sample03.html

<html>
      <head>
      </head>
      <body>
            <div id='myId1'> 아기공룡 </div>
            <div class='myClass1'> 내 친구 </div>
            <ul class='myClass2'>
                  <li> 한빛아카데미 </li>
                  <li> 한빛미디어 </li>
            </ul>
            <a href="www.daum.net"> 다음 바로가기 </a>
            <div class='myClass1'> 둘리 </div>
            <ul>
                  <li class='myClass3'> 비기너 </li>
                  <li class='myClass3'> 시리즈 </li>
            </ul>
            <a href="www.nate.com"> 네이트 바로가기 </a>
            <a href="www.naver.com"> 네이버 바로가기 </a>
       </body>
</html>
import bs4

webPage = open('Sample03.html', 'rt', encoding='utf-8').read()
bsObject = bs4.BeautifulSoup(webPage, "html.parser")

tag = bsObject.find('div', {'id': 'myId1'})
print(tag)
print()

tag = bsObject.find('div', {'class': 'myClass1'})
print(tag)
print()

tag = bsObject.find_all('div', {'class': 'myClass1'})
print(tag)

<div id="myId1"> 아기공룡 </div>

<div class="myClass1"> 내 친구 </div>

[<div class="myClass1"> 내 친구 </div>, <div class="myClass1"> 둘리 </div>]
import bs4

webPage = open('Sample03.html', 'rt', encoding='utf-8').read()
bsObject = bs4.BeautifulSoup(webPage, "html.parser")

ul_value = bsObject.find('ul', {'class': 'myClass2'})
print(ul_value)
print()

li_list = bsObject.find('li', {'class': 'myClass3'})
print(li_list)
print()

<ul class="myClass2">
<li> 한빛아카데미 </li>
<li> 한빛미디어 </li>
</ul>

<li class="myClass3"> 비기너 </li>

 

 

  • tag['속성명'] (예: aTag['href']) 형식을 사용하면 태그가 가진 속성의 값(URL 등)을 가져올 수 있다.
import bs4

webPage = open('Sample03.html', 'rt', encoding='utf-8').read()
bsObject = bs4.BeautifulSoup(webPage, "html.parser")

a_list = bsObject.find_all('a')

for aTag in a_list:
    print(aTag['href'])

www.daum.net
www.nate.com
www.naver.com

 

  • 텍스트 및 속성값 가져오기: * .text를 사용하면 태그 내부의 순수한 문자열만 추출된다.
import bs4

webPage = open('Sample03.html', 'rt', encoding='utf-8').read()
bsObject = bs4.BeautifulSoup(webPage, "html.parser")

tag_li_all = bsObject.find_all('li')
print(tag_li_all)

for tag_li in tag_li_all:
    print(tag_li.text)

print()

for i in range(len(tag_li_all)):
    print(tag_li_all[i].text)
    
    
[<li> 한빛아카데미 </li>, <li> 한빛미디어 </li>, <li class="myClass3"> 비기너 </li>, <li class="myClass3"> 시리즈 </li>]
 한빛아카데미 
 한빛미디어 
 비기너 
 시리즈 

 한빛아카데미 
 한빛미디어 
 비기너 
 시리즈

 

 

LAB 1: 웹사이트 정보 추출

  • 개발자 도구를 사용해서 웹 페이지의 소스 위치를 찾을 수 있다.

 

  • nate 사이트의 로고를 클릭할 때 연결되는 주소 및 로고에 지정된 글자를 추출하는 코드
import urllib.request
import bs4
nateUrl = "https://www.nate.com"
htmlObject = urllib.request.urlopen(nateUrl)
webPage = htmlObject.read()
bsObject = bs4.BeautifulSoup(webPage, "html.parser")

tag = bsObject.find('div', {'id': 'NateBi'})
print(tag, '\n')

a_tag = tag.find("a")
print(a_tag, '\n')

href = a_tag['href']
print(href, '\n')

text = a_tag.text
print(text)

<div class="area_bi" id="NateBi" role="banner">
<h1 class="bi" title="네이트"><a href="//www.nate.com/?f=bi" onmousedown="nc('NBI01');">네이트</a></h1>
</div> 

<a href="//www.nate.com/?f=bi" onmousedown="nc('NBI01');">네이트</a> 

//www.nate.com/?f=bi 

네이트

 

LAB 2: 웹 사이트 메뉴 목록 가져오기

  • 네이트 뉴스 사이트에 접속한 후, 메뉴 영역에 해당하는 <div class="snbArea">를 찾아 그 내부의 모든 하위 메뉴 텍스트를 한 줄로 가져오는 실습이다.

  • 웹 페이지 메뉴 알아내기
import urllib.request
import bs4
nateUrl = "https://news.nate.com/"
htmlObject = urllib.request.urlopen(nateUrl)
webPage = htmlObject.read()
bsObject = bs4.BeautifulSoup(webPage, "html.parser")

tag = bsObject.find('div', {'class': 'snbArea'})

print("## 네이트 뉴스의 메뉴 목록 ##")
li_list = tag.find_all('li')
for li in li_list:
    print(li.text, end = ' ')

## 네이트 뉴스의 메뉴 목록 ##
홈 최신뉴스 정치 경제 사회 세계 IT/과학 칼럼 포토 TV 라디오 랭킹뉴스 투데이댓글

 

LAB 3: 시간 간격 크롤러 생성 (날씨 정보 수집)

  • 시간에 따라 실시간으로 변하는 데이터를 주기적으로 수집하여 저장하는 심화 응용이다.
    • 사용 모듈: 파일 저장을 위한 csv, 지연 시간을 주기 위한 time, 현재 시간을 기록하기 위한 datetime 모듈을 함께 사용한다.  
    • 동작 방식: while True: 무한 루프를 활용하여 데이터를 요청하고, time.sleep(3600)을 통해 1시간마다 반복 동작하도록 설정한다.  
    • 동작 방식: while True: 무한 루프를 활용하여 데이터를 요청하고, time.sleep(3600)을 통해 1시간마다 반복 동작하도록 설정한다.

import csv
import time
import datetime
import bs4
import urllib.request

csvName = 'sokcho_weather.csv'
with open(csvName, 'w', newline='', encoding='utf-8') as csvFp:
    csvWriter = csv.writer(csvFp)
    csvWriter.writerow(['연월일', '시분초', '온도', '습도', '강수량', '풍향'])

nateUrl = "https://news.nate.com/weather?areaCode=11D20401"
while True :
    htmlObject = urllib.request.urlopen(nateUrl)
    webPage = htmlObject.read()
    bsObject = bs4.BeautifulSoup(webPage, "html.parser")
    tag = bsObject.find('div', {'class': 'right_today'})
    temper = tag.find('p', {'class': 'celsius'}).text
    humid = tag.find('p', {'class': 'humidity'}).text
    rain = tag.find('p', {'class': 'rainfall'}).text
    wind = tag.find('p', {'class': 'wind'}).text

    now = datetime.datetime.now()
    yymmdd = now.strftime('%Y-%m-%d')
    hhmmss = now.strftime('%H:%M:%S')

    weather_list = [yymmdd, hhmmss, temper, humid, rain, wind]
    with open(csvName, 'a', newline='', encoding='utf-8') as csvFp:
        csvWriter = csv.writer(csvFp)
        csvWriter.writerow(weather_list)
    
    time.sleep(3600)
# sokcho_weather.csv

연월일,시분초,온도,습도,강수량,풍향
2026-05-27,00:06:58,18℃,습도93%,강수량0.0mm,풍향 북북서 0.8 m/s

 

 

2025-1 기말고사

import urllib.request
import bs4
naverUrl = "https://finance.naver.com/marketindex/"
htmlObject = urllib.request.urlopen(naverUrl)
webPage = htmlObject.read().decode('cp949')
bsObject = bs4.BeautifulSoup(webPage, "html.parser")

h_list = bsObject.find_all('h3', {'class': 'h_lst'})
h_texts = []

p_list = bsObject.find_all('span', {'class': 'value'})
p_texts = []

result = {}

for h in h_list:
    h_texts.append(h.text)

for p in p_list:
    p_texts.append(p.text)

for i in range(0, 4):
    result[h_texts[i]] = float(p_texts[i].replace(',', ''))

print(result)

nation = input("원화 입력: ")
price = float(input("금액 입력: "))

src_weight = 100.0 if "100" in nation else 1.0

krw_value = price * (result[nation] / src_weight)

print(f"{nation} {price}")

for key in result.keys():
    if key != nation:
        print(f"{key} {float(krw_value / result[key]):.4f}")
{'미국 USD': 1506.0, '일본 JPY(100엔)': 945.21, '유럽연합 EUR': 1750.27, '중국 CNY': 221.91}
중국 CNY 1000.0 원
미국 USD 147.3506
일본 JPY(100엔) 234.7732
유럽연합 EUR 126.7862
728x90