※제로베이스 데이터 취업스쿨 11기 수강 중
📗25일차 공부 내용 요약
파이썬을 활용해 웹데이터를 수집하고, 정리하여, 시각화하는 법을 학습했다
1. BeautifulSoup : BeautifulSoup으로 html 불러오고, 원하는 태그를 찾는 법을 학습했다.
2. 네이버 금융 데이터 수집/정리 : urllib의 requests 모듈을 활용해 웹주소에 접근하는 법을 학습하고, 네이버 금융데이터를 수집해보았다.
3. 위키백과 데이터 수집/정리 : URL값이 깨질 때 인코딩하며 불러오는 법과, 동일한 태그가 많을 때 원하는 값을 찾는 법을 학습하고 위키백과에서 데이터를 수집했다.
4. 시카고 맛집 데이터 수집/정리 : fake_useragent를 활용하는 법, Regular Expression을 학습하고, 시카고 맛집 데이터를 수집하고 시각화하였다.
5. 네이버 영화 평점 데이터 수집/정리 : format(),date_range,query를 활용하는 법을 학습하고, 네이버 영화 평점 데이터를 수집하고 정리하였다.
📖 25일차 공부 내용 자세히
1. BeautifulSop
■ BeautifulSoup으로 html 불러오기
from bs4 import BeautifulSoup
page = open("../data/03. zerobase.html", "r").read()
soup = BeautifulSoup(page, "html.parser") #BeautifulSoup 객체로 html을 불러온다
print(soup.prettify()) #prettify메소드로 보기 편하게 표현한다
■ 원하는 태그 확인
1) 원하는 태그 확인
① 단일 선택
- 객체.태그명
- find()
- find(”태그”,”속성값”)
- find(’태그’, 속성 = “값”)
- find(’태그’, {”속성” = “값”})
- select_one()
- select
② 다중 선택
- find_all()
- find_all(속성=’값’)[n]
- 리스트로 반환되기 때문에 offset index로 선택 필요
- find_all(속성=’값’)[n]
- select()
- slect(”#id값”)
- slect(”태그”)
- slect(”.속성(class)값”)
- 바로 아래라는 의미
2) 텍스트 추출
- find().text
- find().stirng
- find().get_text
- find().text.strip()
# find
soup.find("p")
'''
<p class="inner-text first-item" id="first">
Happy ZeroBase.
<a href="http://www.zero-base.co.kr" id="pw-link">ZeroBase</a>
</p>
'''
soup.find("p",{"class":"outer-text first-item"}).text.strip()
'''
'Data Science is funny.']
# find_all
soup.find_all(class_="outer-text")
'''
[<p class="outer-text first-item" id="second">
<b>Data Science is funny.</b>
</p>,
<p class="outer-text">
<i>All I need is Love.</i>
</p>]
'''
#리스트로 반환되기 때문에, text로 뽑기 위해서는 offset index로 가져와야 한다
print(soup.find_all("p")[0].text)
print(soup.find_all("p")[1].string)
print(soup.find_all("p")[2].get_text())
'''
Happy ZeroBase.
ZeroBase
None
Data Science is funny.
'''
2. 네이버 금융데이터 수집/정리
■ urllib 라이브러리, request 모듈 ⇒ 웹 주소(URL) 접근
웹 주소(URL)에 접근할 때는 urllib의 request 모듈이 필요
from urllib.request import urlopen
from bs4 import BeautifulSoup
url = "https://finance.naver.com/marketindex/"
page = urlopen(url)
soup = BeautifulSoup(page, "html.parser")
print(soup.prettify()) #불러온 url 출력
###
import requests
#from urllib.request.Request
from bs4 import BeautifulSoup
url = "https://finance.naver.com/marketindex/"
response = requests.get(url)
soup = BeautifulSoup(response.text, "html.parser")
response.status
# response = urlopen(url) => request 모듈을 통해 요청하고, response로 받는다
# response.status => 200이 나오면 정상적으로 받았다는 뜻
# 이러한 숫자를 http 상태 코드라고 한다. 숫자에 땨른 http 상태 표현
■ 네이버 금융 데이터 수집하기
import requests
#from urllib.request.Request
from bs4 import BeautifulSoup
url = "https://finance.naver.com/marketindex/"
response = requests.get(url)
soup = BeautifulSoup(response.text, "html.parser")
#환전 고시 환율들이 있는 태그 값들 리스트
exchangeList = soup.select("#exchangeList > li")
exchange_datas = []
baseUrl = "https://finance.naver.com"
for item in exchangeList:
#상승, 하락일 때 클래스값이 다르기 때문에 조건부로 걸어줌
if item.select_one(".head_info.point_up") == None:
updown = item.select_one(".head_info.point_dn > .blind")
else:
updown = item.select_one(".head_info.point_up > .blind")
data = {
"title" : item.select_one(".h_lst").text,
"exchange" : item.select_one(".value").text,
"change" : item.select_one(".change").text,
"updown" : updown.text,
"link" : baseUrl + item.select_one("a").get("href")
}
exchange_datas.append(data)
df = pd.DataFrame(exchange_datas)
df.to_excel("./naverfinance.xlsx", encoding='utf-8-sig')
3. 위키백과 데이터 수집/정리
■ URL 값이 깨질 때 인코딩하며 불러오기
import urllib
from urllib.request import urlopen, Request
html = "https://ko.wikipedia.org/wiki/{search_words}"
#글자를 URL로 인코딩
req = Request(html.format(search_words = urllib.parse.quote("여명의_눈동자")))
response = urlopen(req)
soup = BeautifulSoup(response, "html.parser")
print(soup.prettify())
■ 위키백과 '여명의눈동자'데이터 수집
동일한 태그가 많을 때 반복문으로 원하는 값 찾기
#1. find_all이 반환하는 리스트에서 몇 번째 인덱스에 원하는 값이 있는지 반복문으로 출력해 확인
n = 0
for each in soup.find_all("ul"):
print("=>" + str(n) +"=================")
print(each.get_text())
n += 1
#2. 반복문으로 찾은 인덱스의 값에서 원하는 내용 추출
#strip() 공백삭제
#replace("원하는 값", "바꿀내용")
soup.find_all("ul")[32].text.strip().replace("\xa0","").replace("\n","")
'''
'채시라: 윤여옥 역 (아역: 김민정)박상원: 장하림(하리모토 나츠오) 역 (아역: 김태진)최재성: 최대치(사카이) 역 (아역: 장덕수)'
'''
4. 시카고 맛집 데이터 수집/정리
■ fake_useragent 라이브러리 활용해 사이트 불러오기
- fake_useragent 설치 필요(pip 활용)
- hear의 user-agent 확인 하는 법
- 개발자도구 > 네트워크 > 첫번째 페이지 > header > user-agent 확인
# !pip install fake-useragent
from urllib.request import Request, urlopen
from fake_useragent import UserAgent
from bs4 import BeautifulSoup
url_base = "https://www.chicagomag.com/"
url_sub = "chicago-magazine/november-2012/best-sandwiches-chicago/"
url = url_base + url_sub
#원래는 html = urlopen(url)만 해도 되는데, 403에러가 떠서 아래처럼 해준다
ua = UserAgent()
req = Request(url, headers = {"user-agent" : ua.ie}) #ua.ie = 임의의 user-agent 값 생성
html = urlopen(req)
html.status
#임의의 user-agemt 값 없이 "Chrome"dmfheh rksmd
'''
req = Request(url, headers={"User-agent":"Chrome"})
html = urlopen(req)
'''
■ Regular Expression
■ 시카고 맛집 데이터 불러오기 / 메인 페이지 / 순위, 식당명, 메뉴, url까지
- re 모듈 : split() 메서드
- splet(”기준”, 대상) ⇒ 대상을 기준으로 나눔
- 리스트로 반환
- urlib모듈 : urljoin() 메서드
- 상대주소를 절대주소로 변환하는 함수
- urljoin(a, b)
- 절대 경로를 기준으로 상대 경로가 잡힘
- a에 url_base을 절대 주소로 지정, b에 상대 주소를 지정하면 a를 기준으로 b를 합쳐 절대주소로 변환해줌
#list 자료형으로 데이터 뽑아내기
soup = BeautifulSoup(html, "html.parser")
from urllib.parse import urljoin
import re #split() 메서드 활용
#상대주소, 절대주소 대응을 위한 명령
#시카고 매거진의 페이지에서 연결하는 하위 50 페이지의 주소가 상대주소와 절대주소가 혼용되어 있음
url_base = "https://www.chicagomag.com/"
# 필요한 내용을 담을 빈 리스트
# 리스트로 하나씩 컬럼을 만들고, DataFrame으로 합칠 예정
rank = []
main_menu = []
cafe_name = []
url_add = []
#div의 sammy 태그 가져오기
list_soup = soup.find_all("div", "sammy")
for item in list_soup:
rank.append(item.find(class_="sammyRank").get_text())
tmp_string = item.find(class_="sammyListing").get_text() #메뉴와 식당이 있는 구간
main_menu.append(re.split(("\n|\r\n"),tmp_string)[0]) #regular expression
cafe_name.append(re.split(("\n|\r\n"),tmp_string)[1])
url_add.append(urljoin(url_base, item.find("a")["href"])) #urljoin 활용
#데이터 프레임으로 만들기
import pandas as pd
data = {
"Rank" : rank,
"Menu" : main_menu,
"Cafe" : cafe_name,
"URL" : url_add
}
df = pd.DataFrame(data)
df = pd.DataFrame(data, columns=["Rank", "Cafe", "Menu", "URL"]) #컬럼 순서 변경
#데이터 저장
df.to_csv(
"../data/03. best_sandwiches_list_chicago.csv", sep=",", encoding="utf-8"
)
■ 시카고 맛집 데이터 불러오기 / 하위 페이지 / 순위, 식당명, 메뉴, url까지
하나의 데이터로 테스트
- 가격과 주소가 한 줄에 있음, 구분해 추출이 필요
- re.split(), re.search(), regular expression 활용
price_tmp = soup_tmp.find("p","addy").text
# '\n$10. 2109 W. Chicago Ave., 773-772-0406, theoldoaktap.com'
import re
re.split(".,", price_tmp)
# ['\n$10. 2109 W. Chicago Ave', ' 773-772-040', ' theoldoaktap.com']
price_tmp = re.split(".,", price_tmp)[0]
re.search("\$\d+\.(\d+)?", price_tmp)
# <re.Match object; span=(1, 5), match='$10.'>
# serach로 찾은 값이 match object로 반됨
re.search("\$\d+\.(\d+)?", price_tmp).group()
# '$10.'
# #grpup()을 통해 search로 찾은 문자열을 반환함
tmp = re.search("\$\d+\.(\d+)?", price_tmp).group()
price_tmp[len(tmp) + 2 : ]
# '2109 W. Chicago Ave'
# 가격이 끝나는 지점에서 주소가 시작된다는 점을 활용,가격의 문자열 길이를 알아내서 그 뒤부터 주소로 추출
반복문으로 모든 데이터 불러오기 ⇒ itterow + tqdm 활용
#conda install -c conda-forge tqdm
from tqdm import tqdm
price = []
address = []
for idx,row in tqdm(df.iterrows()):
req = Request(row["URL"], headers={"user-agent":"Chrome"}) #임의의 user-agent값에서 오류가 나서, chrom으로
html = urlopen(req).read()
soup_tmp = BeautifulSoup(html, "html.parser")
#하위페이지에서 가격이 기재된 태그 부분 찾기
gettings = soup_tmp.find("p","addy").get_text()
#가격부분만 추출하기
price_tmp = re.split(".,", gettings)[0]
tmp = re.search("\$\d+\.(\d+)?", price_tmp).group()
#가격 추가
price.append(tmp)
#주소 추가
address.append(price_tmp[len(tmp) + 2 : ])
print(idx)
#데이터 프레임 만들기
df["Price"] = price
df["Address"] = address
df.set_index("Rank", inplace=True)
df.to_csv("../data/03. best_sandwiches_list_chicago2.csv", sep=",", encoding="utf-8")
■ 시카고 맛집 데이터 지도 시각화
#requirements
import folium
import pandas as pd
import numpy as np
import googlemaps
from tqdm import tqdm
gmaps_key = 'AIzaSyAs5Gs6qCzAaqwKaZq9RiaBkL3FW6EwV_Y'
gmaps = googlemaps.Client(key=gmaps_key)
#주소로 위도 경도 찾기
lat = []
lng = []
for idx, row in tqdm(df.iterrows()):
if not row["Address"] == "Multiple location":
target_name = row["Address"] + ", " + "Chicago"
#구글맵에서 각 주소 위도 경도값 찾기
gmaps_output = gmaps.geocode(target_name)
location_output = gmaps_output[0].get("geometry")
lat.append(location_output["location"]["lat"])
lng.append(location_output["location"]["lng"])
else:
lat.append(np.nan)
lng.append(np.nan)
#데이터 프레임에 위도, 경도 추가하기
df["lat"] = lat
df["lng"] = lng
#지도 표기하기
mapping = folium.Map(location = [41.8781136, -87.6297982], zoom_start = 11)
for idx, row in df.iterrows():
if not row["Address"] =="Multiple location":
folium.Marker(
location = [row["lat"], row["lng"]],
popup = row["Cafe"],
tooltip = row["Menu"],
icon = folium.Icon(
icon = "coffee",
color = "orange",
prefix = "fa"
)
).add_to(mapping)
mapping
5. 네이버 영화 평점 데이터 수집/정리
■ format() 함수
- 문자열 포맷을 지정 가능
- {이름}포함한 포맷팅할 내용.formant(이름=값)
- {0,1}포함한 포맷팅할 내용.format(값,값)
⇒ {}에 들어가는 0,1,2,,,숫자는 인덱스에 해당. format함수의 값과 차례로 대응
#이름으로 넣기
test_string = "Hi, I'm {name}"
test_string.format(name = "ZeroBase")
#"Hi, I'm ZeroBase"
#인덱스로 넣기
number = 10
day = "three"
"I ate {0} apples. so I was sick for {1} days.".format(number, day)
#'I ate 10 apples. so I was sick for three days.'
■ date와 strftime()메서드 활용
- pd.date_range(”시작날짜”, perids=100, freq =”D”)
- 시작날짜를 기준으로 100개의 기간을 D(하루)기준으로 만들어 리스트로 반환
- 반환되는 데이터 타입은 dtype='datetime64[ns]
- strftime()
- date, datetime, time 객체에서 활용 가능
- 주어진 포맷에 따라 객체를 문자열로 변환
- 객체.strftime()
import pandas as pd
date = pd.date_range("2022.09.01",periods=100, freq="D")
date[0]
#Timestamp('2022-09-01 00:00:00', freq='D')
date[0].strftime("%Y-%m-%d")
#'2022-09-01'
#%Y 연도를 10진수로, #m 월을 0으로 채운 10진수로, %d 일을 10으로 채운 10진수로
date[0].strftime("%Y.%m.%d")
#'2022.09.01'
■ 날짜별 영화 평점 데이터 불러오기
날짜별 영화 평점 불러와 데이터프레임 만들기
import pandas as pd
from urllib.request import urlopen
from bs4 import BeautifulSoup
import time
from tqdm import tqdm
#100개의 날짜 생성
date = pd.date_range("2022.09.01",periods=100, freq="D")
#반복문으로 리스트에 날짜, 영화제목, 평점 담기
movie_date = []
movie_name = []
movie_point = []
for today in tqdm(date):
#각 날짜에 따른 사이트 불러오기
url = "https://movie.naver.com/movie/sdb/rank/rmovie.naver?sel=cur&date={date}"
response = urlopen(url.format(date=today.strftime("%Y%m%d")))
soup = BeautifulSoup(response, "html.parser")
#길이 구하기
end = len(soup.find_all("td", "point"))
#영화 제목, 평점 구하기
movie_date.extend([today for _ in range(0,end)])
movie_name.extend([soup.select("div.tit5")[n].a.text for n in range(0,end)])
movie_point.extend([soup.select("td.point")[n].text for n in range(0,end)])
time.sleep(0.5)
#데이터 프레임으로 만들기
movie = pd.DataFrame({
"date" : movie_date,
"name" : movie_name,
"point": movie_point
})
평점 문자열 데이터 타입으로 바꾸기
movie["point"] = movie["point"].astype(float)
■ 베스트10 & 워스트10
movie_unique = pd.pivot_table(
data=movie,
index = 'name',
values = 'point',
aggfunc = np.sum)
movie_best = movie_unique.sort_values(by="point", ascending=False)#내림차순
#베스트 10
movie_best.head(10)
#워스트10
movie_best.tail(10)
■ 특정 영화의 날짜별 평점 변화 시각화
query()로 특정 영화 데이터 추출
tmp = movie.query("name == ['코다']")
한 영화의 날짜별 데이터 시각화
import matplotlib.pyplot as plt
from matplotlib import rc
rc("font", family = "Malgun Gothic")
%matplotlib inline
plt.figure(figsize = (20,8))
plt.plot(tmp["date"], tmp["point"])#선 그래프 x축 날짜, y축 평점 => 날짜에 따른 평점 변화를 선그래프로 표현(시계열)
plt.title("날짜별 평점")
plt.xlabel("날짜")
plt.ylabel("평점")
plt.xticks(rotation ="vertical") #x축 라벨을 세로로 표시
plt.legend(labels=["평점 추이"], loc="best")
plt.grid(True)
plt.show()
■ 여러 영화의 날짜별 평점 변화 시각화
날짜를 인덱스로 피봇테이블
movie_pivot = pd.pivot_table(data=movie, index="date", columns="name", values="point")
여러 영화 날짜별 데이터 시각화
import platform
import seaborn as sns
from matplotlib import font_manager, rc
#한글 환경 설정
path = "C:/Windows/Fonts/malgun.ttf"
if platform.system() == "Darwin":
rc("font", family = "Arial Unicode MS")
elif platform.system() == "Windows":
font_name = font_manager.FontProperties(fname=path).get_name()
rc("font", family=font_name)
else:
print("Unknown system.sorry")
#시각화할 영화만 지정
target_col = ["인생은 아름다워", "탑건: 매버릭", "헌트", "고양이를 부탁해", "하우스 오브 구찌"]
#그래프 그리기
plt.figure(figsize = (20,8))
plt.title("날짜별 평점")
plt.xlabel("날짜")
plt.ylabel("평점")
#plt.xticks(rotation = 'vertical')
plt.tick_params(bottom="off", labelbottom="off")
plt.plot(movie_pivot[target_col])
plt.legend(target_col, loc = 'best')
#plt.grid(True)
plt.show()
➰ 25일차 후기
웹데이터를 크롤링해보는 게 신기하고 재미있었는데,
새롭게 학습하는 내용들이라, 완벽하게 이해가 어려운 부분들이 있어 검색하며 진도를 나가느라 속도가 조금 더뎠다.
그래도 하나의 개념을 반복적으로 코드를 작성하며 해서 조금은 익숙해질 수 잇었다.
네이버 금융 데이터를 불러올 때, 강의와는 다르게 환율이 상승/하락이 섞여있어서 상승/하락에 따라 클래스가 달라 조건문을 활용해 스스로 해결해보았고, 시카고 맛집 데이터를 불러올 때도 user-agent값을 임의로 설정했을 때 중간에 자꾸 수집을 못해서 그냥 "Chrome"으로 설정을 바꾸는 등 혼자 이것저것 해결해 보려 했던 게 나름 뿌듯했다.
※본 내용은 제로베이스 데이터 취업 스쿨에서 제공하는 학습 내용에 기반합니다.
'제로베이스 데이터 스쿨 > 일일 스터디 노트' 카테고리의 다른 글
27일차 스터디노트 / 파이썬 네이버 API, 인구 데이터 분석, 시각화 (0) | 2023.02.08 |
---|---|
26일차 스터디노트 / 파이썬 웹데이터 수집, 파이썬 Selenium, 주유 가격 정보 시각화, boxplot, folium (0) | 2023.02.07 |
24일차 스터디노트 / 파이썬 Seaborn, Folium, 지도 시각화, 데이터 시각화 (0) | 2023.02.03 |
23일차 스터디노트 / 판다스 데이터 분석, Google Maps 활용, 피봇테이블, 서울시 범죄 데이터 정리 (1) | 2023.02.02 |
22일차 스터디노트 / 판다스 자율 학습 - [Do it! 데이터 분석을 위한 판다스 입문] (0) | 2023.02.02 |