01 웹크롤링
-01 웹의 구성요소
02 HTML
-01 구성
-02 test1 : 요소 접근 (순차적)
-03 test2. find 메서드를 사용한 요소 직접 접근
-04 test3. find_all을 사용한 속성값 추출
# 실전연습1 : 책 이름과 가격정보 가져오기
# 실전연습2 : 위키북스의 책 목록 가져오기
# 실전연습3 : 날씨 가져오기
-05 test5. 선택자 사용하기 (순서)
01 웹크롤링
- 웹스크래핑 : 웹에 있는 정보(사진, 영상, 텍스트)를 가져오는 행위
- 웹크롤링 : 웹에 있는 정보를 자동으로 가져오도록 하는 행위
웹스크래핑 / 웹크롤링이 구분되긴하나 (자격증 기준), 현업에서는 크게 가리지 않는다.
-01 웹의 구성요소
1) HTML
웹페이지의 뼈대를 구성하는 마크업 언어이다.
웹페이지에 들어가 F12로 개발자 도구로 접속하면 다음과 같은 화면을 확인할 수 있다.

HTML 뒤에 있는 것은 태그로, 해당 태그 역시 규칙과 언어가 정해져 있다.
<head>로 시작하면 </head>로 끝나고, <body>로 시작하면 </body>로 끝나는 식의 구조로, 그 안에 태그들이 트리 구조로 구성되어 있다.
따라서 내가 추출하고자하는 영상, 이미지 등이 어디에 속해있는지를 밝히는 것이 크롤링의 핵심이다.

또한 여러가지의 식별자를 사용할수도 있는데, 일종의 ID인 class를 사용하기도 한다.

따라서 텍스트를 추출하고 싶은 상황이라면, a.Home_link_title__W6Auj가 식별자임을 알 수 있다.
그러나 개발자들은 주기적으로 html의 class명 등을 바꾸게 된다.
웹 부하 때문이다. 따라서 많은 사람들이 수집하고 싶어하는 증권 정보같은 경우에는 크롤링 자체를 막아두기도 한다.
2) CSS
스타일 시트 (글꼴, 사이즈, 색 등의 디자인적 요소)
3) javascript
동적 표현을 위한 언어 (DB 접속 등)
02 HTML
-01 구성
1) 태그
<>로 구성된 정보(태그)이다.
<html> ... </html> 과 같이 짝을 이루는 구조를 가진다.

uL, li, a... 등 많은 규칙이 있다.
참고) a - HTML5 태그 사전
2) 요소
태그의 시작과 끝까지의 한 세트를 말한다.
(현업에서는 태그와 요소를 크게 구분하지는 않는다.)
3) 속성
특징을 부여하기 위해 만든 식별자이다.
속성은 태그 안에 삽입된다.
> id : 전체 문서에서 중복되지 않아야 함
> class : 중복될 수 있으며 같은 특징을 가지고 있는 요소들에 이름을 붙이기 위해 사용
> <li class = 'a1' ...> (여기서 a1이 속성값)
4) 속성값
속성에 매칭되는 이름이다.
-02 test1 : 요소 접근 (순차적)
# h1 ~ h6 : 각 세션의 제목 (header)을 표현하는 태그
# p : 하나의 문단 구성
html = """
<html><body>
<h1>웹크롤링 강의</h1>
<p>문단입니다</p>
<p>문단입니다2</p>
</body></html>"""
# 1. 라이브러리 로딩
from bs4 import BeautifulSoup
# 2. 파싱 (구조 변경)
soup = BeautifulSoup(html, 'html.parser')
BeautifulSoup(html, 'html.parser')
Out[14]:
<html><body>
<h1>웹크롤링 강의</h1>
<p>문단입니다</p>
<p>문단입니다2</p>
</body></html>
# 3. 추출
# h1 추출
dir(soup)
soup.html
soup.html.body
h1 = soup.html.body.h1
soup.html
Out[18]:
<html><body>
<h1>웹크롤링 강의</h1>
<p>문단입니다</p>
<p>문단입니다2</p>
</body></html>
soup.html.body
Out[19]:
<body>
<h1>웹크롤링 강의</h1>
<p>문단입니다</p>
<p>문단입니다2</p>
</body>
soup.html.body.h1
Out[20]: <h1>웹크롤링 강의</h1>
h1.text
h1.string
h1.text
Out[22]: '웹크롤링 강의'
h1.string
Out[23]: '웹크롤링 강의'
따라서 원하는 문자를 추출하기 위해서는 어떤 태그로 감싸져있는지를 확인해야 한다.
트리 구조가 단순한 경우에는 이렇게 순차적 접근으로 진행해도 무관하나, 트리가 복잡한 경우는 쉽지 않은 경우가 많다.
# p 추출
p1 = soup.html.body.p
soup.html.body.p
Out[24]: <p>문단입니다</p>
p1.text
문제는 하나만 추출이 된다.
같은 p로 감싸져 있는 다른 형제를 가지고 와야 하는 경우 다음과 같이 진행한다.
p2 = p1.next_sibling.next_sibling
p1.text
Out[30]: '문단입니다'
같은 형제여도 엔터로 구분되어 있기 때문에 사이에 /n가 있는 상황이다.
따라서 next_sibling을 한 번 더 써야한다.
p1.next_sibling.next_sibling
Out[26]: <p>문단입니다2</p>
p2.text
p2.text
Out[29]: '문단입니다2'
-03 test2. find 메서드를 사용한 요소 직접 접근
html = """
<html><body>
<h1 id="title">웹크롤링 강의</h1>
<p id="body">웹 스크래핑이란?</p>
<p>배워볼까요?</p>
</body></html>
"""
# 1. 라이브러리 로딩
from bs4 import BeautifulSoup
# 2. 파싱 (구조 변경)
soup = BeautifulSoup(html, 'html.parser')
# 3. 추출
body1 = soup.find(id = 'body')
soup.find(id = 'body')
Out[32]: <p id="body">웹 스크래핑이란?</p>
body1.text
body1.text
Out[36]: '웹 스크래핑이란?'
# p 요소 출력 (find는 첫번쨰 요소만 출력)
soup.find('p')
soup.find('p')
Out[37]: <p id="body">웹 스크래핑이란?</p>
# p 요소 출력 (findall은 전체 요소 출력)
soup.find_all('p')
soup.find_all('p')
Out[38]: [<p id="body">웹 스크래핑이란?</p>, <p>배워볼까요?</p>]
soup.find_all('p')[0].text
soup.find_all('p')[1].text
soup.find_all('p')[0].text
Out[39]: '웹 스크래핑이란?'
soup.find_all('p')[1].text
Out[40]: '배워볼까요?'
[ i.text for i in soup.find_all('p')]
[ i.text for i in soup.find_all('p')]
Out[42]: ['웹 스크래핑이란?', '배워볼까요?']
-04 test3. find_all을 사용한 속성값 추출
ul(unordered list) : 순서없는 리스트를 작성하는 요소
ol(ordered list) : 순서있는 리스트를 작성하는 요소
li : 리스트 목록을 표현하는 요소
a : 하이퍼링크를 표현하는 요소 (href : 링크를 표현하는 속성)
html = """
<html><body>
<ul>
<li><a href="http://www.naver.com">naver</a></li>
<li><a href="http://www.daum.net">daum</a></li>
<li><a href="http://www.google.net">google</a></li>
<li><a href="http://www.yahoo.net">yahoo</a></li>
</ul>
</body></html>
"""
# 1. 라이브러리 로딩
from bs4 import BeautifulSoup
# 2. 파싱 (구조 변경)
soup = BeautifulSoup(html, 'html.parser')
# 3. 추출
soup.find_all('a')
soup.find_all('a')
Out[44]:
[<a href="http://www.naver.com">naver</a>,
<a href="http://www.daum.net">daum</a>,
<a href="http://www.google.net">google</a>,
<a href="http://www.yahoo.net">yahoo</a>]
리스트의 형태로 출력됨을 확인할 수 있다.
soup.find_all('a')[0].text
soup.find_all('a')[0].text
Out[45]: 'naver'
soup.find_all('a')[0]['href']
soup.find_all('a')[0]['href']
Out[48]: 'http://www.naver.com'
# ** 전체 링크 출력
[ i.text for i in soup.find_all('a')]
[ i.text for i in soup.find_all('a')]
Out[54]: ['naver', 'daum', 'google', 'yahoo']
[ i['href'] for i in soup.find_all('a')]
[ i['href'] for i in soup.find_all('a')]
Out[57]:
['http://www.naver.com',
'http://www.daum.net',
'http://www.google.net',
'http://www.yahoo.net']
-05 test4. 선택자 사용하기
# 선택자 : 요소들의 관계를 표현하는 기법 (부모/자식, 형제 등)
# select, select_one 선택자를 사용하여 요소를 추출
html = """
<html><body>
<div id="meigen">
<h1>위키북스 도서</h1>
<ul class="items">
<li>그레이스와 함께하는 파이썬 활용편1</li>
<li>그레이스와 함께하는 즐거운 생활 코딩1</li>
<li>그레이스와 함께하는 배드민턴의 세계1</li>
</ul>
</div>
<div id="meige2">
<h1>위키북스 도서</h1>
<h2>위키북스 도서2</h2>
<ul class="items">
<li>그레이스와 함께하는 파이썬 활용편2</li>
<li>그레이스와 함께하는 즐거운 생활 코딩2</li>
<li>그레이스와 함께하는 배드민턴의 세계2</li>
</ul>
</div>
</body></html>
"""
ul 하의 li들만 추출하려고 하는데, 하단에도 ul로 시작하는 li들이 있는 상황이다.
이렇게 식별자만으로 접근하기 어려울 때 그 윗단계(부모)를 확인한다.
다행히 div의 id가 다른 상황이다. (meigen, meige2)
# ** 선택자 정의
1) 태그명 단독 전달 (find / find_all로도 가능)
2) 속성과 함께 태그 전달
- 태그명.클래스명
- 태그명#id명
예) div#meigen, ul.items
# ** 선택자 관계 정의
1) 선택자 선택자 : 상하관계를 표현 (자손 : 자기 아래를 포함하여 그 아래까지 모두 포함)
예) div#meigen li
2) 선택자>선택자 : 상하관계를 표현(자식 : 자기 아래만 포함)
예) div#meigen > ul.tiems > li
3) 선택자 , 선택자 : 여러 선택자 선택
예) div#meigen, div#meige2
4) 선택자 ~ 선택자 : 선택자 범위 선택
5) 선택자 + 선택자 : 다른 계층에 있는 형제들 선택
# 1. 라이브러리 로딩
from bs4 import BeautifulSoup
# 2. 파싱 (구조 변경)
soup = BeautifulSoup(html, 'html.parser')
# 3. 추출
soup.select('li')
soup.select('li')
Out[64]:
[<li>그레이스와 함께하는 파이썬 활용편1</li>,
<li>그레이스와 함께하는 즐거운 생활 코딩1</li>,
<li>그레이스와 함께하는 배드민턴의 세계1</li>,
<li>그레이스와 함께하는 파이썬 활용편2</li>,
<li>그레이스와 함께하는 즐거운 생활 코딩2</li>,
<li>그레이스와 함께하는 배드민턴의 세계2</li>]
# 1편들 (첫번째 단락 li 추출)
soup.select('ul.items')
soup.select('div#meigen li')
soup.select('div#meigen > li') # 부모가 아니기 때문에 그 어떤 것도 출력되지 않는다.
soup.select('div#meigen > ul > li')
soup.select('div#meigen > ul.itmes > li')
soup.select('ul.items')
Out[88]:
[<ul class="items">
<li>그레이스와 함께하는 파이썬 활용편1</li>
<li>그레이스와 함께하는 즐거운 생활 코딩1</li>
<li>그레이스와 함께하는 배드민턴의 세계1</li>
</ul>,
<ul class="items">
<li>그레이스와 함께하는 파이썬 활용편2</li>
<li>그레이스와 함께하는 즐거운 생활 코딩2</li>
<li>그레이스와 함께하는 배드민턴의 세계2</li>
</ul>]
soup.select('div#meigen li')
Out[89]:
[<li>그레이스와 함께하는 파이썬 활용편1</li>,
<li>그레이스와 함께하는 즐거운 생활 코딩1</li>,
<li>그레이스와 함께하는 배드민턴의 세계1</li>]
soup.select('div#meigen > li') # 부모가 아니기 때문에 그 어떤 것도 출력되지 않는다.
Out[90]: []
soup.select('div#meigen > ul > li')
Out[91]:
[<li>그레이스와 함께하는 파이썬 활용편1</li>,
<li>그레이스와 함께하는 즐거운 생활 코딩1</li>,
<li>그레이스와 함께하는 배드민턴의 세계1</li>]
soup.select('div#meigen > ul.items > li')
Out[92]:
[<li>그레이스와 함께하는 파이썬 활용편1</li>,
<li>그레이스와 함께하는 즐거운 생활 코딩1</li>,
<li>그레이스와 함께하는 배드민턴의 세계1</li>]
# 실전연습1 : 책 이름과 가격정보 가져오기
All products | Books to Scrape - Sandbox
All products | Books to Scrape - Sandbox
£20.66 In stock
books.toscrape.com
#class 이름에 공백이 있는 경우 . 으로 치환해야 한다.
# 1. 필요 라이브러리 로딩
from bs4 import BeautifulSoup
from urllib.request import urlopen
# 2. url html 소스 가져오기
url = 'https://books.toscrape.com/'
html = urlopen(url)
* 만약 사이트에서 방어를 했다면 메시지가 따로 출력된다.
soup = BeautifulSoup(html, 'html.parser')
books = soup.select('li.col-xs-6.col-sm-4.col-md-3.col-lg-3 a')
[i.text for i in books]
[i.text for i in books]
Out[145]:
['',
'A Light in the ...',
'',
'Tipping the Velvet',
'',
'Soumission',
'',
'Sharp Objects',
'',
'Sapiens: A Brief History ...',
'',
'The Requiem Red',
'',
'The Dirty Little Secrets ...',
'',
'The Coming Woman: A ...',
'',
'The Boys in the ...',
'',
'The Black Maria',
'',
'Starving Hearts (Triangular Trade ...',
'',
"Shakespeare's Sonnets",
'',
'Set Me Free',
'',
"Scott Pilgrim's Precious Little ...",
'',
'Rip it Up and ...',
'',
'Our Band Could Be ...',
'',
'Olio',
'',
'Mesaerion: The Best Science ...',
'',
'Libertarianism for Beginners',
'',
"It's Only the Himalayas"]
books = soup.select('li.col-xs-6.col-sm-4.col-md-3.col-lg-3 h3 > a')
book_name = [i.text for i in books]
[i.text for i in books]
Out[153]:
['A Light in the ...',
'Tipping the Velvet',
'Soumission',
'Sharp Objects',
'Sapiens: A Brief History ...',
'The Requiem Red',
'The Dirty Little Secrets ...',
'The Coming Woman: A ...',
'The Boys in the ...',
'The Black Maria',
'Starving Hearts (Triangular Trade ...',
"Shakespeare's Sonnets",
'Set Me Free',
"Scott Pilgrim's Precious Little ...",
'Rip it Up and ...',
'Our Band Could Be ...',
'Olio',
'Mesaerion: The Best Science ...',
'Libertarianism for Beginners',
"It's Only the Himalayas"]
# 책 가격 찾기
price = soup.select('div.product_price > p.price_color')
book_price = [i.text for i in price]
[i.text for i in price]
Out[191]:
['£51.77',
'£53.74',
'£50.10',
'£47.82',
'£54.23',
'£22.65',
'£33.34',
'£17.93',
'£22.60',
'£52.15',
'£13.99',
'£20.66',
'£17.46',
'£52.29',
'£35.02',
'£57.25',
'£23.88',
'£37.59',
'£51.33',
'£45.17']
import pandas as pd
pd.DataFrame({'name':book_name, 'price':book_price})
pd.DataFrame({'name':book_name, 'price':book_price})
Out[197]:
name price
0 A Light in the ... £51.77
1 Tipping the Velvet £53.74
2 Soumission £50.10
3 Sharp Objects £47.82
4 Sapiens: A Brief History ... £54.23
5 The Requiem Red £22.65
6 The Dirty Little Secrets ... £33.34
7 The Coming Woman: A ... £17.93
8 The Boys in the ... £22.60
9 The Black Maria £52.15
10 Starving Hearts (Triangular Trade ... £13.99
11 Shakespeare's Sonnets £20.66
12 Set Me Free £17.46
13 Scott Pilgrim's Precious Little ... £52.29
14 Rip it Up and ... £35.02
15 Our Band Could Be ... £57.25
16 Olio £23.88
17 Mesaerion: The Best Science ... £37.59
18 Libertarianism for Beginners £51.33
19 It's Only the Himalayas £45.17
# 실전연습2 : 위키북스의 책 목록 가져오기
# 1. 필요 라이브러리 로딩
from bs4 import BeautifulSoup
from urllib.request import urlopen
# 2. url html 소스 가져오기
url = 'https://ko.wikisource.org/wiki/%EC%A0%80%EC%9E%90:%EC%9C%A4%EB%8F%99%EC%A3%BC'
html = urlopen(url)
HTTPError: HTTP Error 403: Forbidden
사이트 접근이 막혀있는 것을 확인할 수 있다.
urlopen 함수가 user-agent (유저의 접속환경 정보) 정보 없이 웹 접속을 시도했기 때문이다.
따라서 해당 홈페이지에서 정상 접속 여부를 확인할 수 없으므로 접속을 차단하게 된다.
-> user-agent 기본적인 정보를 제공하는 request 요청
url = 'https://ko.wikisource.org/wiki/%EC%A0%80%EC%9E%90:%EC%9C%A4%EB%8F%99%EC%A3%BC'
html = urlopen(url)
from urllib.request import Request
html = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
html = urlopen(html)
# 3. 파싱
soup = BeautifulSoup(html, 'html.parser')
books = soup.select('div.mw-content-ltr.mw-parser-output ul li')
* 또는 books = soup.select('div.mw-content-ltr.mw-parser-output ul li a')
book_name = [i.text for i in books]
book_name
Out[250]:
['하늘과 바람과 별과 시: 1948년 초판',
'하늘과 바람과 별과 시: 1955년 10주기 증보판',
'하늘과 바람과 별과 시: 1979년 미공개작 추가본',
'서시',
'자화상',
'소년',
'눈 오는 지도',
'돌아와 보는 밤',
...
len(book_name)
len(book_name)
Out[251]: 96
# 실전연습3 : 날씨 가져오기
# ** request 함수 차이(urllib.request vs requests)
# 1) urllib.request
# - 파이썬 내장
# - 403 에러 발생 시 에러 발생
# - 한글 url 인코딩 문제 존재
# - 코드 불편(Request -> urlopen)
# 2) requests
# - 별도 설치(아나콘다 내장)
# - 403 에러 발생 시 에러 발생 X
# - 한글 url 인코딩 문제 없음
# - 코드 간결(requests.get)
# 1. 라이브러리 로딩
url1 = 'https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=%EA%B0%95%EB%82%A8+%EB%82%A0%EC%94%A8&ackey=7mohqdjy'
url2 = 'https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query=강남+날씨&ackey=7mohqdjy'
# url1 불러오기
html = Request(url1, headers={'User-Agent':'Mozilla/5.0'})
html = urlopen(html)
soup = BeautifulSoup(html, 'html.parser')
# url2 불러오기(오류발생 -> requests 사용!)
html = Request(url2, headers={'User-Agent':'Mozilla/5.0'})
html = urlopen(html)
soup = BeautifulSoup(html, 'html.parser')
# 2. url html 소스 가져오기
import requests
html = requests.get(url2)
html.status_code # 200(OK)
# 3. 파싱
soup = BeautifulSoup(html.text, 'html.parser')
# 4. 추출
# 기온
a1 = soup.select('div.temperature_text strong')[0].text[5:]
# 맑음여부
a2 = soup.select('span.weather.before_slash')[0].text
# 미세먼지, 초미세먼지, 자외선, 일몰
b1 = soup.select('span.txt')[:4][0].text
b2 = soup.select('span.txt')[:4][1].text
b3 = soup.select('span.txt')[:4][2].text
b4 = soup.select('span.txt')[:4][3].text
# 출력
print('='*10, '강남 날씨 정보','='*10)
print(f'현재온도 : {a1}')
print(f'날씨상태 : {a2}')
print(f'미세먼지 : {b1}')
print(f'초미세먼지: {b2}')
print(f'자외선지수: {b3}')
print(f'일몰시간 : {b4}')
print('='*38)
print('='*10, '강남 날씨 정보','='*10)
print(f'현재온도 : {a1}')
print(f'날씨상태 : {a2}')
print(f'미세먼지 : {b1}')
print(f'초미세먼지: {b2}')
print(f'자외선지수: {b3}')
print(f'일몰시간 : {b4}')
print('='*38)
========== 강남 날씨 정보 ==========
현재온도 : 26.9°
날씨상태 : 흐림
미세먼지 : 좋음
초미세먼지: 좋음
자외선지수: 좋음
일몰시간 : 19:56
======================================
# 5. 함수 생성
import requests
def craw_weather(area) :
url = f'https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=0&ie=utf8&query={area}+날씨&ackey=7mohqdjy'
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
a1 = soup.select('div.temperature_text strong')[0].text[5:]
a2 = soup.select('span.weather.before_slash')[0].text
b1 = soup.select('span.txt')[:4][0].text
b2 = soup.select('span.txt')[:4][1].text
b3 = soup.select('span.txt')[:4][2].text
b4 = soup.select('span.txt')[:4][3].text
print('='*10, f'{area} 날씨 정보','='*10)
print(f'현재온도 : {a1}')
print(f'날씨상태 : {a2}')
print(f'미세먼지 : {b1}')
print(f'초미세먼지: {b2}')
print(f'자외선지수: {b3}')
print(f'일몰시간 : {b4}')
print('='*38)
craw_weather('평양')
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
Cell In[398], line 23
20 print(f'일몰시간 : {b4}')
21 print('='*38)
---> 23 craw_weather('평양')
Cell In[398], line 6, in craw_weather(area)
3 html = requests.get(url)
4 soup = BeautifulSoup(html.text, 'html.parser')
----> 6 a1 = soup.select('div.temperature_text strong')[0].text[5:]
7 a2 = soup.select('span.weather.before_slash')[0].text
8 b1 = soup.select('span.txt')[:4][0].text
IndexError: list index out of range
-05 test5. 선택자 사용하기 (순서)
# 형제(같은 depth의 태그)들끼리의 순서를 정의하는 방식
1) 선택자 : nth-child(n) :
태그명을 구분하지 않고 depth가 같으면 같은 형제로 취급한다.
- 1을 쓰면 첫 번째 자식, 2를 쓰면 두 번째 자식
2) 선택자 : nth-of-type(n) :
태그명이 같은 경우만 형제로 취급한다.
html = '''
<div class="a1">
<p>태그 1</p>
<span>span태그 1</span>
<p>p태그 2</p>
<span>span태그 2</span>
<p>p태그 3</p>
</div>
'''
# 1. 라이브러리 로딩
from bs4 import BeautifulSoup
# 2. 파싱 (구조 변경)
soup = BeautifulSoup(html, 'html.parser')
# 3. 선택
soup.select('p:nth-child(1)')
soup.select('p:nth-child(1)')
Out[206]: [<p>태그 1</p>]
soup.select('span:nth-child(2)')
'아이티윌_데이터 분석 55기 > 강의내용 필기_분석(Python)' 카테고리의 다른 글
| #15 15일차_웹크롤링 실습2 (0) | 2026.07.13 |
|---|---|
| #14 14일차_웹크롤링 실습 (0) | 2026.07.09 |
| #12 12일차_군집분석, 연관분석 (0) | 2026.06.19 |
| #10 회귀모형과 패널티 모형, 부스팅 이론 (0) | 2026.06.18 |
| #8 클래스 불균등 처리 (0) | 2026.06.15 |