01 웹크롤링 실습
-01 실전_여러 페이지 정보 모두 가져오기
-02 실전_뉴트리원 영양제 정보 (url 공개되지 않는 케이스)
-03 실전_아디다스 운동화 정보 가져오기 (url 공개되지 않는 케이스)
-04 실전_공공데이터포털 데이터 가져오기(API 사용)
01 웹크롤링 실습
-01 실전_여러 페이지 정보 모두 가져오기
case1) 페이지 번호가 바뀔 때마다 url이 공개되는 케이스
case2) 페이지 번호가 바뀔 때마다 url이 공개되지 않는 케이스
** 페이지 번호가 잘못된 경우 (마지막 페이지 이후) 처리
1) status_code 처리 -> requests 사용
2) HTTPError exception 처리 -> urlopen, urlilib.request 사용
-> 에러가 난 상황에서 사용자가 별도의 조치를 취하고 싶을 때 하는 것을 exception 처리라고 한다.
# 1. 전달받은 페이지에 있는 책 목록을 가져오는 함수 생성
from bs4 import BeautifulSoup
from urllib.request import urlopen
def craw_page(page_num) :
url = f'https://books.toscrape.com/catalogue/page-{page_num}.html'
html = urlopen(url)
soup = BeautifulSoup(html, 'html.parser')
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]
price = soup.select('div.product_price > p.price_color')
book_price = [i.text for i in price]
return book_name, book_price
craw_page(50)
def craw_page(page_num) :
url = f'https://books.toscrape.com/catalogue/page-{page_num}.html'
html = urlopen(url)
soup = BeautifulSoup(html, 'html.parser')
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]
price = soup.select('div.product_price > p.price_color')
book_price = [i.text for i in price]
return book_name, book_price
craw_page(50)
Out[2]:
(['Frankenstein',
'Forever Rockers (The Rocker ...',
'Fighting Fate (Fighting #6)',
'Emma',
'Eat, Pray, Love',
'Deep Under (Walker Security ...',
'Choosing Our Religion: The ...',
'Charlie and the Chocolate ...',
"Charity's Cross (Charles Towne ...",
'Bright Lines',
"Bridget Jones's Diary (Bridget ...",
'Bounty (Colorado Mountain #7)',
'Blood Defense (Samantha Brinkman ...',
'Bleach, Vol. 1: Strawberry ...',
'Beyond Good and Evil',
"Alice in Wonderland (Alice's ...",
'Ajin: Demi-Human, Volume 1 ...',
"A Spy's Devotion (The ...",
"1st to Die (Women's ...",
'1,000 Places to See ...'],
['£38.00',
'£28.80',
'£39.24',
'£32.93',
'£51.32',
'£47.09',
'£28.42',
'£22.85',
'£41.24',
'£39.07',
'£29.82',
'£37.26',
'£20.30',
'£34.65',
'£43.38',
'£55.53',
'£57.06',
'£16.97',
'£53.98',
'£26.08'])
# 모든 페이지 크롤링하기 (status_code 처리 -> requests 사용)
from bs4 import BeautifulSoup
from urllib.request import urlopen
import requests
def craw_page() :
all_book_name = []
page_num = 1
while True :
url = f'https://books.toscrape.com/catalogue/page-{page_num}.html'
html = requests.get(url)
if html.status_code != 200:
break
soup = BeautifulSoup(html.text, 'html.parser')
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]
all_book_name = all_book_name + book_name
print(f'{page_num} 페이지 완료, 누적건수 : {len(all_book_name)}권')
page_num = page_num + 1
return all_book_name
craw_page()
print(type(html))
"""
Created on Thu Jul 9 09:47:41 2026
@author: itwill
"""
# 실전연습1 : 책 이름과 가격정보 가져오기
#class 이름에 공백이 있는 경우 . 으로 치환해야 한다.
# 1. 전달받은 페이지에 있는 책 목록을 가져오는 함수 생성
from bs4 import BeautifulSoup
from urllib.request import urlopen
import requests
def craw_page() :
all_book_name = []
page_num = 1
while True :
url = f'https://books.toscrape.com/catalogue/page-{page_num}.html'
html = requests.get(url)
if html.status_code != 200:
break
soup = BeautifulSoup(html.text, 'html.parser')
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]
all_book_name = all_book_name + book_name
print(f'{page_num} 페이지 완료, 누적건수 : {len(all_book_name)}권')
page_num = page_num + 1
return all_book_name
craw_page()
print(type(html))
1 페이지 완료, 누적건수 : 20권
2 페이지 완료, 누적건수 : 40권
3 페이지 완료, 누적건수 : 60권
4 페이지 완료, 누적건수 : 80권
5 페이지 완료, 누적건수 : 100권
6 페이지 완료, 누적건수 : 120권
7 페이지 완료, 누적건수 : 140권
8 페이지 완료, 누적건수 : 160권
9 페이지 완료, 누적건수 : 180권
10 페이지 완료, 누적건수 : 200권
11 페이지 완료, 누적건수 : 220권
12 페이지 완료, 누적건수 : 240권
13 페이지 완료, 누적건수 : 260권
14 페이지 완료, 누적건수 : 280권
15 페이지 완료, 누적건수 : 300권
16 페이지 완료, 누적건수 : 320권
17 페이지 완료, 누적건수 : 340권
18 페이지 완료, 누적건수 : 360권
19 페이지 완료, 누적건수 : 380권
20 페이지 완료, 누적건수 : 400권
21 페이지 완료, 누적건수 : 420권
22 페이지 완료, 누적건수 : 440권
23 페이지 완료, 누적건수 : 460권
24 페이지 완료, 누적건수 : 480권
25 페이지 완료, 누적건수 : 500권
26 페이지 완료, 누적건수 : 520권
27 페이지 완료, 누적건수 : 540권
28 페이지 완료, 누적건수 : 560권
29 페이지 완료, 누적건수 : 580권
30 페이지 완료, 누적건수 : 600권
31 페이지 완료, 누적건수 : 620권
32 페이지 완료, 누적건수 : 640권
33 페이지 완료, 누적건수 : 660권
34 페이지 완료, 누적건수 : 680권
35 페이지 완료, 누적건수 : 700권
36 페이지 완료, 누적건수 : 720권
37 페이지 완료, 누적건수 : 740권
38 페이지 완료, 누적건수 : 760권
39 페이지 완료, 누적건수 : 780권
40 페이지 완료, 누적건수 : 800권
41 페이지 완료, 누적건수 : 820권
42 페이지 완료, 누적건수 : 840권
43 페이지 완료, 누적건수 : 860권
44 페이지 완료, 누적건수 : 880권
45 페이지 완료, 누적건수 : 900권
46 페이지 완료, 누적건수 : 920권
47 페이지 완료, 누적건수 : 940권
48 페이지 완료, 누적건수 : 960권
49 페이지 완료, 누적건수 : 980권
50 페이지 완료, 누적건수 : 1000권
<class 'requests.models.Response'>
# HTTPError exception 처리
# 2. HTTPError
from bs4 import BeautifulSoup
from urllib.request import urlopen
from urllib.error import HTTPError
# 페이지번호 입력 -> 정보 가져오는 함수 생성
def f_page(page_num) :
url = f'https://books.toscrape.com/catalogue/page-{page_num}.html'
try :
html = urlopen(url)
except HTTPError as e:
return
else :
soup = BeautifulSoup(html, 'html.parser')
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]
return book_name
f_page(1) # 정상 출력
f_page(51) # 아무것도 출력되지 않음 (None) 출력
Out[32]:
['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"]
# 모든 페이지 크롤링하기 (HTTPError exception 처리 -> urlopen, urlilib.request 사용)
all_book_name = []
page_num = 1
while True :
result = f_page(page_num)
if result is None :
print(f'{page_num} 페이지는 존재하지 않습니다 -> 종료!!')
break
all_book_name = all_book_name + result
print(f'{page_num} 페이지 완료, 누적건수 : {len(all_book_name)}권')
page_num = page_num + 1
1 페이지 완료, 누적건수 : 20권
2 페이지 완료, 누적건수 : 40권
3 페이지 완료, 누적건수 : 60권
4 페이지 완료, 누적건수 : 80권
5 페이지 완료, 누적건수 : 100권
6 페이지 완료, 누적건수 : 120권
7 페이지 완료, 누적건수 : 140권
8 페이지 완료, 누적건수 : 160권
9 페이지 완료, 누적건수 : 180권
10 페이지 완료, 누적건수 : 200권
11 페이지 완료, 누적건수 : 220권
12 페이지 완료, 누적건수 : 240권
13 페이지 완료, 누적건수 : 260권
14 페이지 완료, 누적건수 : 280권
15 페이지 완료, 누적건수 : 300권
16 페이지 완료, 누적건수 : 320권
17 페이지 완료, 누적건수 : 340권
18 페이지 완료, 누적건수 : 360권
19 페이지 완료, 누적건수 : 380권
20 페이지 완료, 누적건수 : 400권
21 페이지 완료, 누적건수 : 420권
22 페이지 완료, 누적건수 : 440권
23 페이지 완료, 누적건수 : 460권
24 페이지 완료, 누적건수 : 480권
25 페이지 완료, 누적건수 : 500권
26 페이지 완료, 누적건수 : 520권
27 페이지 완료, 누적건수 : 540권
28 페이지 완료, 누적건수 : 560권
29 페이지 완료, 누적건수 : 580권
30 페이지 완료, 누적건수 : 600권
31 페이지 완료, 누적건수 : 620권
32 페이지 완료, 누적건수 : 640권
33 페이지 완료, 누적건수 : 660권
34 페이지 완료, 누적건수 : 680권
35 페이지 완료, 누적건수 : 700권
36 페이지 완료, 누적건수 : 720권
37 페이지 완료, 누적건수 : 740권
38 페이지 완료, 누적건수 : 760권
39 페이지 완료, 누적건수 : 780권
40 페이지 완료, 누적건수 : 800권
41 페이지 완료, 누적건수 : 820권
42 페이지 완료, 누적건수 : 840권
43 페이지 완료, 누적건수 : 860권
44 페이지 완료, 누적건수 : 880권
45 페이지 완료, 누적건수 : 900권
46 페이지 완료, 누적건수 : 920권
47 페이지 완료, 누적건수 : 940권
48 페이지 완료, 누적건수 : 960권
49 페이지 완료, 누적건수 : 980권
50 페이지 완료, 누적건수 : 1000권
51 페이지는 존재하지 않습니다 -> 종료!!
-02 실전_뉴트리원 영양제 정보 (url 공개되지 않는 케이스)
import requests
url = 'https://www.nutrione.co.kr/item/allItem'
html = requests.get(url)
html.status_code
soup = BeautifulSoup(html.text, 'html.parser')
영양제 정보가 전혀 작성되어있지 않다.
개발자도구에서 영양제 정보의 위치를 확인한다 하더라도, url 자체에 제품명과 가격정보가 작성되어 있지 않기 때문에 빈 리스트가 출력되는 문제가 발생한다.

따라서 개발자 도구 > 네트워크 탭 > page 2를 찾는 식으로 원래 url을 찾을 수 있다.
import requests
url = 'https://www.nutrione.co.kr/item/dispAllList?sort=1&page=2&cate=&brand=&price=&fml=&oneday=&type=list'
html = requests.get(url)
html.status_code
soup = BeautifulSoup(html.text, 'html.parser')
ga_item = {
item_id: "1000217741",
item_variant: "8809514649451",
item_name: "저분자 콜라겐 S",
discount: 0,
index: "40",
item_brand: "비비랩",
item_category: "관심",
item_category2: "이너뷰티",
item_category3: "",
item_category4: "",
item_category5: "",
affiliation: '일반',
price: 24800,
quantity: 1
};
temp_ga_items.push(ga_item);
제품정보에 관련된 url을 설정하자 원하는 정보들이 크롤링되었음을 확인할 수 있다.
soup.select('div.price-info > span.price.b6-700')
soup.select('div.price-info > span.price.b6-700')
Out[40]:
[<span class="price b6-700">66,600</span>,
<span class="price b6-700">48,800</span>,
<span class="price b6-700">13,900</span>,
<span class="price b6-700">45,790</span>,
<span class="price b6-700">27,500</span>,
<span class="price b6-700">141,980</span>,
...
# ** 뉴트리원 사이트 페이징 처리 -> 스크롤을 내리면 페이지가 자동 전환 되는 케이스(url에 페이지 번호 노출되지 X)
# ** 진짜 url 찾는 방법
# 개발자 도구 -> 네트워크 탭 -> Fetch/XHR 선택 -> Reload page 버튼 눌러서 실행되는 스크립트 확인
# -> 왼쪽 화면에서 스크롤을 내려서 화면이 바뀌는 경우 네트워크 탭 name에 page=2 와 같은 형태의 이름 확인
# -> 클릭 후 header에 있는 URL 정보 확인
# 1. URL 확인
url = 'https://www.nutrione.co.kr/item/dispAllList?sort=1&page=1&cate=&brand=&price=&fml=&oneday=&type=list'
# 2. URL 가져오기 / 파싱
html = requests.get(url)
html.status_code # 200
soup = BeautifulSoup(html.text, 'html.parser')
soup.select('a > div.info > div.name-volume > h3')
soup.select('div.price-info > span.price.b6-700') # 정상
# 3. 모든 페이지에 있는 상품명/가격 정보 가져오기
from bs4 import BeautifulSoup
from urllib.request import urlopen
from urllib.error import HTTPError
import requests
import time
# 페이지별 정보 가져오기
def f_nutrione_page(pagenum):
url = f'https://www.nutrione.co.kr/item/dispAllList?sort=1&page={pagenum}&cate=&brand=&price=&fml=&oneday=&type=list'
html = requests.get(url)
soup = BeautifulSoup(html.text, 'html.parser')
name_tags = soup.select('a > div.info > div.name-volume > h3')
names = []
for h3 in name_tags:
h3.find('strong').decompose() # strong 부분 제외
names.append(h3.get_text(strip=True)) # 요소에서 모든 글자만 빼서 나욜 -> 공백 제거
price_tags = soup.select('div.price-info > span.price.b6-700')
prices = [p.text for p in price_tags]
return names, prices
f_nutrione_page(1)
# 모든 페이지 정보 가져오기
def craw_nutrione_page():
all_names = []
all_prices = []
page_num = 1
while True:
url = f'https://www.nutrione.co.kr/item/dispAllList?sort=1&page={page_num}&cate=&brand=&price=&fml=&oneday=&type=list'
html = requests.get(url)
if html.status_code != 200:
print(f'{page_num} 페이지 없음 -> 완료!!!')
break
soup = BeautifulSoup(html.text, 'html.parser')
name_tags = soup.select('a > div.info > div.name-volume > h3')
if not name_tags:
print(f'{page_num} 페이지에 상품 없음 -> 완료!!!')
break
names = []
for h3 in name_tags:
h3.find('strong').decompose()
names.append(h3.get_text(strip=True))
price_tags = soup.select('div.price-info > span.price.b6-700')
prices = [p.text for p in price_tags]
all_names += names
all_prices += prices
print(f'{page_num} 페이지 완료, 누적건수 : {len(all_names)}건')
page_num += 1
time.sleep(3)
return all_names, all_prices
# 정형 데이터로 가공
import pandas as pd
prod_name, prod_price = craw_nutrione_page()
df = pd.DataFrame({'name':prod_name, 'price':prod_price})
-03 실전_아디다스 운동화 정보 가져오기 (url 공개되지 않는 케이스)
# pip install undetected-chromedriver --break-system-packages
# ** undetected_chromedriver
# 일반 selenium의 크롬 드라이버를 감싸서, Cloudflare 같은 봇 탐지 시스템이 확인하는 흔적들(navigator.webdriver 값 등)을 숨겨주는 라이브러리
from bs4 import BeautifulSoup
import requests
import httpx
# 1. url 확인
url = 'https://sneakernews.com/wp-admin/admin-ajax.php?action=release_date_load_more&nextpage=10&category_name=sneaker-release&start_from=0&page_id=225762&last_month_box=&type=undefined'
# 2. URL 가져오기 / 파싱
import undetected_chromedriver as uc
from bs4 import BeautifulSoup
import time
driver = uc.Chrome(headless=False, version_main=149) # headless=False로 먼저 테스트 (headless가 감지 요인일 수 있음)
driver.get(url)
time.sleep(8) # Cloudflare 체크 통과 대기 시간을 넉넉히
soup = BeautifulSoup(driver.page_source, 'html.parser')
driver.quit()
# 3. 운동화 이름 / 가격 가져오기
soup.select()[0]
a = soup.select('div.release-date-and-rating h2 a')
[i.text for i in a]
b = soup.select('p.release-price')
[i.text for i in b]
price = [int(i.text.replace('Retail Price: $', '')) for i in b]
import undetected_chromedriver as uc
from bs4 import BeautifulSoup
import time
import re
def craw_sneaker_page():
all_names = []
all_prices = []
driver = uc.Chrome(headless=False, version_main=149)
page_num = 1
while True:
url = f'https://sneakernews.com/wp-admin/admin-ajax.php?action=release_date_load_more&nextpage={page_num}&category_name=sneaker-release&start_from=0&page_id=225762&last_month_box=&type=undefined'
driver.get(url)
time.sleep(5)
soup = BeautifulSoup(driver.page_source, 'html.parser')
name_tags = soup.select('div.release-date-and-rating h2 a')
if not name_tags:
print(f'{page_num} 페이지에 상품 없음 -> 완료!!!')
break
names = []
for tag in name_tags:
strong = tag.find('strong')
if strong:
strong.decompose()
names.append(tag.get_text(strip=True))
price_tags = soup.select('p.release-price')
prices = []
for tag in price_tags:
num = re.sub(r'[^0-9]', '', tag.get_text())
prices.append(int(num) if num else None)
all_names += names
all_prices += prices
print(f'{page_num} 페이지 완료, 누적건수: {len(all_names)}건')
page_num += 1
time.sleep(2)
driver.quit()
return all_names, all_prices
names, prices = craw_sneaker_page()
# 정형 데이터 생성
df = pd.DataFrame({'names':names, 'prices':prices})
len(names)
len(names)
Out[66]: 4923
-04 실전_공공데이터포털 데이터 가져오기(API 사용)
** API를 사용한 데이터 수집은 반드시 사전 승인을 받아야 함!
회원가입 > 원하는 데이터 찾기 > 활용 신청 > 일반 인증키 확인(복사)
** 국경일 데이터
연도 / 월 정보 전달 -> 해당 기간의 국경일 정보 다운
** api_key
api_key = '인증키 입력'
1. URL 확인
* 기본 URL : http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService
* 하위 URL : getHoliDeInfo
* 실제 URL : http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getHoliDeInfo
2. 정보 요청 방식
1) URL에 KEY값 함께 전달
url = 'http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getRestDeInfo?solYear=2019&solMonth=03&ServiceKey=서비스키'
2) 딕셔너리를 통해 KEY값 전달
url = 'http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getHoliDeInfo'
solYear
solMonth
ServiceKey
_type : default(XML)
numOfRows
params = {'solYear' : 2026,
'_type' : 'json',
'numOfRows' : 20,
'ServiceKey' : api_key}
3. 추출
1) json
import httpx
html = httpx.get(url, params=params)
html.status_code # 200(OK)
text = html.json()
text['response']['body']['items']['item'][0]['locdate']
text['response']['body']['items']['item'][0]['dateName']
2) XML
html = httpx.get(url, params=params)
soup = BeautifulSoup(html.text, 'lxml-xml')
rows = soup.select('item')
rows[0].find_all()[1].text
rows[0].find_all()[3].text
'아이티윌_데이터 분석 55기 > 강의내용 필기_분석(Python)' 카테고리의 다른 글
| #15 15일차_웹크롤링 실습2 (0) | 2026.07.13 |
|---|---|
| #13 13일차_웹크롤링 (0) | 2026.07.08 |
| #12 12일차_군집분석, 연관분석 (0) | 2026.06.19 |
| #10 회귀모형과 패널티 모형, 부스팅 이론 (0) | 2026.06.18 |
| #8 클래스 불균등 처리 (0) | 2026.06.15 |