데이터 사이언스 04 웹자료 가져오기(2)-Selenium
1. Selenium
- BeautifulSoup는 requests 모듈 등으로 접근한 웹주소(URL)에서 웹자료를 쉽게 가져올 수 있지만,
1) Beautiful Soup의 한계 및 Selenium 소개
- 접근할 웹주소를 알 수 없거나
- 자바스크립트를 사용하는 경우
- 웹 브라우저로만 접근해야하는 경우(예를들어, 로그인 등이 필요한 경우)
위의 경우에는 원격 웹브라우저인 selenium이 필요하다. selenium은,
- 웹 브라우저를 원격 조작하는 도구로서, 자동으로 URL을 열고, 클릭/스크롤/문자입력(로그인 시도)/화면캡쳐 등이 가능하다.
2) Selenium 설치
➡ pip install selenium
Downloading selenium-3.8.1-py2.py3-none-any.whl (942kB)
100% |████████████████████████████████| 952kB 851kB/s
Installing collected packages: selenium
Successfully installed selenium-3.8.1
3) 크롬 드라이버 설치
- 최신 버전은 2.35이나 안정된 버전인 2.30설치함
2. 예제 1 - 네이버 메일 보낸 사람 리스트
- 네에버 로그인
from selenium import webdriver
# 크롬 창 열기
driver = webdriver.Chrome('../crawler/driver/chromedriver')
# 네이버 : 이 창은 건들지 않는다.
## 별도의 네이버 창을 연다.
driver.get('http://naver.com')
# 화면 캡쳐
driver.save_screenshot('./data_science/04img/1-3-naver.png')
True
# 네이버 로그인
## id
naver_login = driver.find_element_by_id("id")
naver_login.clear()
naver_login.send_keys("ehfgk")
## pw
naver_login = driver.find_element_by_id("pw")
naver_login.clear()
naver_login.send_keys("@naver2559")
## 로그인버튼 클릭
### Copy Xpath : //*[@id="frmNIDLogin"]/fieldset/span/input
driver.find_element_by_xpath("""//*[@id="frmNIDLogin"]/fieldset/span/input""").click()
# naver 메일
driver.get("http://mail.naver.com")
from bs4 import BeautifulSoup
#현재 페이지 주소
mail_page = driver.page_source
# Soup
soup = BeautifulSoup(mail_page, 'lxml')
# 메일을 보낸 사람 태그
raw_list = soup.find_all('div', 'name _ccr(lst.from) ')
raw_list
[<div class="name _ccr(lst.from) "><span class="blind">보낸 이:</span><a class="_c1(myContextMenu|showSenderContextLayer|list|1931) _stopDefault" href="#" title='"Google" <no-reply@accounts.google.com>'>Google</a></div>,
<div class="name _ccr(lst.from) "><span class="blind">보낸 이:</span><a class="_c1(myContextMenu|showSenderContextLayer|list|1928) _stopDefault" href="#" title='"Google" <no-reply@accounts.google.com>'>Google</a></div>,
<div class="name _ccr(lst.from) "><span class="blind">보낸 이:</span><a class="_c1(myContextMenu|showSenderContextLayer|list|1836) _stopDefault" href="#" title='"네이버웹툰 주식회사" <nv_webtoon_noreply@naver.com>'>네이버웹툰 주식회사</a></div>,
<div class="name _ccr(lst.from) "><span class="blind">보낸 이:</span><a class="_c1(myContextMenu|showSenderContextLayer|list|1717) _stopDefault" href="#" title='"!한국IT전문학교 입시정보!" <navercafe@naver.com>'>!한국IT전문학교 입..</a></div>,
<div class="name _ccr(lst.from) "><span class="blind">보낸 이:</span><a class="_c1(myContextMenu|showSenderContextLayer|list|1662) _stopDefault" href="#" title='"2030산악회『저알콜서울경기/20대30대/하늘마루2030등산동호회』" <navercafe@naver.com>'>2030산악회『저알..</a></div>,
<div class="name _ccr(lst.from) "><span class="blind">보낸 이:</span><a class="_c1(myContextMenu|showSenderContextLayer|list|1627) _stopDefault" href="#" title='"워드프레스앤(홈페이지,wordpress,테마,설치,쇼핑몰,강의,강좌)" <navercafe@naver.com>'>워드프레스앤(홈페..</a></div>,
<div class="name _ccr(lst.from) "><span class="blind">보낸 이:</span><a class="_c1(myContextMenu|showSenderContextLayer|list|1534) _stopDefault" href="#" title='"한국사내변호사회" <navercafe@naver.com>'>한국사내변호사회</a></div>,
<div class="name _ccr(lst.from) "><span class="blind">보낸 이:</span><a class="_c1(myContextMenu|showSenderContextLayer|list|1472) _stopDefault" href="#" title='"hwang sungyeon" <latarte7@hanmail.net>'>hwang sungyeon</a></div>,
<div class="name _ccr(lst.from) "><span class="blind">보낸 이:</span><a class="_c1(myContextMenu|showSenderContextLayer|list|1404) _stopDefault" href="#" title='"achilles7" <achilles7@naver.com>'>achilles7</a></div>,
<div class="name _ccr(lst.from) "><span class="blind">보낸 이:</span><a class="_c1(myContextMenu|showSenderContextLayer|list|1393) _stopDefault" href="#" title='"안나현" <anh2002@hanmail.net>'>안나현</a></div>,
<div class="name _ccr(lst.from) "><span class="blind">보낸 이:</span><a class="_c1(myContextMenu|showSenderContextLayer|list|1387) _stopDefault" href="#" title='"노영빈" <lawyb@naver.com>'>노영빈</a></div>,
<div class="name _ccr(lst.from) "><span class="blind">보낸 이:</span><a class="_c1(myContextMenu|showSenderContextLayer|list|1383) _stopDefault" href="#" title='"김경우" <think810525@hotmail.com>'>김경우</a></div>,
<div class="name _ccr(lst.from) "><span class="blind">보낸 이:</span><a class="_c1(myContextMenu|showSenderContextLayer|list|1357) _stopDefault" href="#" title="<sada111@naver.com>">sada111@naver.com</a></div>,
<div class="name _ccr(lst.from) "><span class="blind">보낸 이:</span><a class="_c1(myContextMenu|showSenderContextLayer|list|1354) _stopDefault" href="#" title='"낭만시대" <newflag@naver.com>'>낭만시대</a></div>,
<div class="name _ccr(lst.from) "><span class="blind">보낸 이:</span><a class="_c1(myContextMenu|showSenderContextLayer|list|1353) _stopDefault" href="#" title='"낭만시대" <newflag@naver.com>'>낭만시대</a></div>]
send_list = [ raw_list[n].find('a').get_text() for n in range(0, len(raw_list)) ]
send_list
['Google',
'Google',
'네이버웹툰 주식회사',
'!한국IT전문학교 입..',
'2030산악회『저알..',
'워드프레스앤(홈페..',
'한국사내변호사회',
'hwang sungyeon',
'achilles7',
'안나현',
'노영빈',
'김경우',
'sada111@naver.com',
'낭만시대',
'낭만시대']
driver.close()
3. 셀프 주유소가 저렴한가?
1) 대상
# 크롬 창 열기
driver = webdriver.Chrome('../crawler/driver/chromedriver')
# opinet/지역별 주유소 찾기
driver.get("http://www.opinet.co.kr/searRgSelect.do")
# 주유소 목록 구하기: 서울: 구
## 자바스크립트 부분 - xpath : //*[@id="SIGUNGU_NM0"]
gu_list_raw = driver.find_element_by_xpath( """//*[@id="SIGUNGU_NM0"]""" )
### option들: elements
gu_list = gu_list_raw.find_elements_by_tag_name("option")
### 확인
gu_names = [ option.get_attribute("value") for option in gu_list ]
gu_names[:5]
['', '강남구', '강동구', '강북구', '강서구']
gu_names.remove('')
gu_names[:5]
['강남구', '강동구', '강북구', '강서구', '관악구']
2) 데이터 얻기
자바스크립트가 작동함.
- 리스트 박스에서 지역을 선택한다.
- '조회' 버튼을 누른다. ➔ xpath: //[@id="searRgSelect"] : seaRch gu Select import time '엑셀 저장'버튼을 누른다
import time
# 주유소 - 지역 - 서울 - id: 자치 구 표시 칸
element = driver.find_element_by_id("SIGUNGU_NM0")
# 강남구 이름 입력
element.send_keys(gu_names[0])
time.sleep(2)
# 조회
element_select_gu = driver.find_element_by_xpath("""//*[@id="searRgSelect"]""")
time.sleep(2)
# 엑셀버튼 누르기
element_get_excel = driver.find_element_by_xpath("""//*[@id="glopopd_excel"]""").click()
for gu in gu_names:
# 주유소 - 지역 - 서울 - id: 자치 구 표시 칸
element = driver.find_element_by_id("SIGUNGU_NM0")
# 구 이름 입력
element.send_keys(gu)
time.sleep(2)
# 조회
element_select_gu = driver.find_element_by_xpath("""//*[@id="searRgSelect"]""")
time.sleep(2)
# 엑셀버튼 누르기
element_get_excel = driver.find_element_by_xpath("""//*[@id="glopopd_excel"]""").click()
time.sleep(2)
print(gu)
강남구
강동구
강북구
강서구
관악구
광진구
구로구
금천구
노원구
도봉구
동대문구
동작구
마포구
서대문구
서초구
성동구
성북구
송파구
양천구
영등포구
용산구
은평구
종로구
중구
중랑구
3) 데이터 읽기 : glob
- 파일 집합 다루기
- 데이터 다듬기
데이터 자료구조 파악하기 - [data].info()
from glob import glob
import pandas as pd
glob('./data_ouput/04.oil_stations_in_Seoul/지역*.xls')
['./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (11).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (20).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (10).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (4).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (19).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (8).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (9).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (21).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (13).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (12).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (3).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (23).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (2).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (22).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (24).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (5).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (1).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (6).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (14).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (17).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (7).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (16).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (18).xls',
'./data_ouput/04.oil_stations_in_Seoul/지역_위치별(주유소) (15).xls']
stations_files = glob('./data_ouput/04.oil_stations_in_Seoul/지역*.xls')
tmp_raw = []
for file_name in stations_files:
tmp = pd.read_excel(file_name, header=2)
tmp_raw.append(tmp)
station_raw = pd.concat(tmp_raw)
station_raw.tail()
지역 | 상호 | 주소 | 상표 | 전화번호 | 셀프여부 | 고급휘발유 | 휘발유 | 경유 | 실내등유 | 셀프 | |
---|---|---|---|---|---|---|---|---|---|---|---|
13 | 서울특별시 | SK세원2주유소 | 서울 성동구 광나루로 184 (성수동1가) | SK에너지 | 02-462-5665 | N | 1890 | 1660 | 1465 | - | False |
14 | 서울특별시 | 금호주유소 | 서울 성동구 금호로 39 (금호동4가) | GS칼텍스 | 02-2297-0066 | N | - | 1698 | 1478 | 1260 | False |
15 | 서울특별시 | 동일주유소 | 서울 성동구 광나루로 254 (성수동2가) | 현대오일뱅크 | 02-461-7100 | N | 1998 | 1745 | 1538 | 1149 | False |
16 | 서울특별시 | 청계로주유소 | 서울 성동구 청계천로 454 (하왕십리동) | SK에너지 | 02-2294-4225 | N | - | 1848 | 1647 | 1095 | False |
17 | 서울특별시 | (주)옥수하이웨이스테이션 | 서울 성동구 독서당로 168 (옥수동) | GS칼텍스 | 02-2282-5151 | N | 2151 | 1856 | 1685 | - | False |
- 셀프여부 데이터 유형 바꾸기 : string('N'/'Y') ➔ boolean(True/False)
station_raw['셀프'] = station_raw['셀프여부'] == 'Y'
station_raw.head()
지역 | 상호 | 주소 | 상표 | 전화번호 | 셀프여부 | 고급휘발유 | 휘발유 | 경유 | 실내등유 | 셀프 | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 서울특별시 | 매일주유소 | 서울특별시 동작구 상도로 139 (상도동) | S-OIL | 02-817-4085 | N | - | 1529 | 1369 | 1000 | False |
1 | 서울특별시 | 서경주유소 | 서울 동작구 대림로 46 (신대방동) | 현대오일뱅크 | 02-843-5130 | N | - | 1529 | 1329 | - | False |
2 | 서울특별시 | 대방주유소 | 서울특별시 동작구 여의대방로214 (대방동) | GS칼텍스 | 02-826-5145 | N | 1755 | 1538 | 1338 | 950 | False |
3 | 서울특별시 | 한원케미칼(주) | 서울특별시 동작구 동작대로 135 (사당동) | GS칼텍스 | 02-2183-3824 | Y | - | 1539 | 1329 | - | True |
4 | 서울특별시 | 금성주유소 | 서울특별시 동작구 사당로 59 (상도동) | 현대오일뱅크 | 02-825-5151 | N | - | 1539 | 1329 | - | False |
stations = pd.DataFrame(
{
'Oil_store' : station_raw['상호'].values,
'주소' : station_raw['주소'].values,
'가격' : station_raw['휘발유'].values,
'셀프' : station_raw['셀프'].values,
'상표' : station_raw['상표'].values
}
)
stations.head()
Oil_store | 가격 | 상표 | 셀프 | 주소 | |
---|---|---|---|---|---|
0 | 매일주유소 | 1529 | S-OIL | False | 서울특별시 동작구 상도로 139 (상도동) |
1 | 서경주유소 | 1529 | 현대오일뱅크 | False | 서울 동작구 대림로 46 (신대방동) |
2 | 대방주유소 | 1538 | GS칼텍스 | False | 서울특별시 동작구 여의대방로214 (대방동) |
3 | 한원케미칼(주) | 1539 | GS칼텍스 | True | 서울특별시 동작구 동작대로 135 (사당동) |
4 | 금성주유소 | 1539 | 현대오일뱅크 | False | 서울특별시 동작구 사당로 59 (상도동) |
stations['구'] = [ eachAddress.split()[1] for eachAddress in stations['주소'] ]
stations.head()
Oil_store | 가격 | 상표 | 셀프 | 주소 | 구 | |
---|---|---|---|---|---|---|
0 | 매일주유소 | 1529 | S-OIL | False | 서울특별시 동작구 상도로 139 (상도동) | 동작구 |
1 | 서경주유소 | 1529 | 현대오일뱅크 | False | 서울 동작구 대림로 46 (신대방동) | 동작구 |
2 | 대방주유소 | 1538 | GS칼텍스 | False | 서울특별시 동작구 여의대방로214 (대방동) | 동작구 |
3 | 한원케미칼(주) | 1539 | GS칼텍스 | True | 서울특별시 동작구 동작대로 135 (사당동) | 동작구 |
4 | 금성주유소 | 1539 | 현대오일뱅크 | False | 서울특별시 동작구 사당로 59 (상도동) | 동작구 |
stations.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 536 entries, 0 to 535
Data columns (total 6 columns):
Oil_store 536 non-null object
가격 536 non-null object
상표 536 non-null object
셀프 536 non-null bool
주소 536 non-null object
구 536 non-null object
dtypes: bool(1), object(5)
memory usage: 21.5+ KB
stations['구'].unique()
array(['동작구', '용산구', '동대문구', '강서구', '영등포구', '노원구', '도봉구', '특별시', '은평구',
'서대문구', '마포구', '강북구', '중구', '강동구', '종로구', '중랑구', '관악구', '강남구',
'광진구', '구로구', '서초구', '송파구', '금천구', '성북구', '양천구', '성동구', '서울특별시'], dtype=object)
# 서울특별시, 특별시
stations[ stations['구']=='서울특별시' ]
Oil_store | 가격 | 상표 | 셀프 | 주소 | 구 | |
---|---|---|---|---|---|---|
529 | SK네트웍스(주)효진주유소 | 1654 | SK에너지 | False | 1 서울특별시 성동구 동일로 129 (성수동2가) | 서울특별시 |
stations[ stations['구']=='특별시' ]
Oil_store | 가격 | 상표 | 셀프 | 주소 | 구 | |
---|---|---|---|---|---|---|
144 | 서현주유소 | 1524 | S-OIL | True | 서울 특별시 도봉구 방학로 142 (방학동) | 특별시 |
# '서울특별시'가 쓰여진 raw의 '구'부분
stations.loc[ stations['구']=='서울특별시', '구' ] = '성동구'
# '특별시'가 쓰여진 raw의 '구' 부분
stations.loc[ stations['구']=='특별시', '구' ] = '도봉구'
stations['구'].unique()
array(['동작구', '용산구', '동대문구', '강서구', '영등포구', '노원구', '도봉구', '은평구', '서대문구',
'마포구', '강북구', '중구', '강동구', '종로구', '중랑구', '관악구', '강남구', '광진구', '구로구',
'서초구', '송파구', '금천구', '성북구', '양천구', '성동구'], dtype=object)
# 가격 부분 : 실수형으로 정하기
stations['가격'] = [float(values) for values in stations['가격']]
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-68-f062bc7c80cf> in <module>()
1 # 가격 부분
----> 2 stations['가격'] = [float(values) for values in stations['가격']]
<ipython-input-68-f062bc7c80cf> in <listcomp>(.0)
1 # 가격 부분
----> 2 stations['가격'] = [float(values) for values in stations['가격']]
ValueError: could not convert string to float: '-'
stations[ stations['가격'] == '-' ]
Oil_store | 가격 | 상표 | 셀프 | 주소 | 구 | |
---|---|---|---|---|---|---|
118 | 하나주유소 | - | S-OIL | False | 서울특별시 영등포구 도림로 236 (신길동) | 영등포구 |
217 | (주)에이앤이청담주유소 | - | SK에너지 | True | 서울특별시 강북구 도봉로 155 (미아동) | 강북구 |
248 | 명진석유(주)동서울주유소 | - | GS칼텍스 | True | 서울특별시 강동구 천호대로 1456 (상일동) | 강동구 |
339 | SK서광그린주유소 | - | SK에너지 | False | 서울특별시 강남구 봉은사로 222(역삼동) | 강남구 |
# 위 부분은 지우기 : 가격 정보가 없으므로, 데이터 분석을 할 수 없음
stations = stations[ stations['가격'] != '-' ]
stations[ stations['가격'] == '-' ]
Oil_store | 가격 | 상표 | 셀프 | 주소 | 구 |
---|
# 가격 부분 : 실수형으로 정하기
stations['가격'] = [float(values) for values in stations['가격']]
stations.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 532 entries, 0 to 535
Data columns (total 6 columns):
Oil_store 532 non-null object
가격 532 non-null float64
상표 532 non-null object
셀프 532 non-null bool
주소 532 non-null object
구 532 non-null object
dtypes: bool(1), float64(1), object(4)
memory usage: 25.5+ KB
4) 시각화
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
# 한글 문제
import matplotlib.font_manager as fm
## 폰트 적용
font_location = '/usr/share/fonts/truetype/nanum/NanumBarunGothic.ttf'
font_name = fm.FontProperties(fname=font_location).get_name()
from matplotlib import rc
rc('font', family=font_name)
plt.figure( figsize=(12, 8) )
sns.boxplot(
x='상표',
y='가격',
hue='셀프',
data=stations,
palette="Set3"
)
sns.swarmplot(
x='상표',
y='가격',
data=stations,
color='.6'
)
plt.show()