G2G 페이지를 크롤링하면서 원하는 html 구문이 requests 방식으로는 스크래핑이 안되서 이유를 찾아봤다. g2g 페이지는 javascript를 활용, 동적 페이지로 구성된 녀석이라 페이지를 열어서 활성된 html 구문을 가져와야 원하는 정보를 받아올 수 있는 경우였다.
Selenium
직접 페이지를 열기 위한 라이브러리 selenium의 파이썬(or 아나콘다) 설치를 진행한다.
1 2
$ pip install selenium $ conda install selenium
나의 경우 venv 환경을 이용할 예정이라 venv 경로에서 bash를 이용해 pip install 해줬다.
브라우저 webdriver 설치
selenium을 받으면 끝이 아니라 selenium으로 html 구문을 받아오기 위해 webdriver를 받아와서 지정해줘야한다.
특정 조건이 만족할 때까지 지정된 시간 동안 대기하고, timeout된 경우 TimeoutException이 발생한다.
expected_conditions 함수의 조건이 만족할 때까지 기다리는 코드를 사용할 수 있다.
1 2 3 4 5 6 7 8 9
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 30) # 특정 요소를 찾을 수 있을 때까지 element = EC.presence_of_element_located((By.CLASS_NAME , "offers-bottom-attributes.offer__content-lower-items")) wait.until(element)
동적 페이지의 경우 하나의 URL에서 구조가 바뀌는 경우도 있다.
이런 경우 클릭이 가능할 때까지 기다리는 함수를 사용할 수 있다.
1 2
# 클릭이 가능할 때까지 element = EC.element_to_be_clickable((By.XPATH, '//a[@href="#!-1"]'))
아래의 조건들을 활용할 수 있다.
title_is
title_contains
presence_of_element_located
visibility_of_element_located
visibility_of
presence_of_all_elements_located
text_to_be_present_in_element
text_to_be_present_in_element_value
frame_to_be_available_and_switch_to_it
invisibility_of_element_located
element_to_be_clickable
staleness_of
element_to_be_selected
element_located_to_be_selected
element_selection_state_to_be
element_located_selection_state_to_be
alert_is_present
try ~ finally 구문을 활용하면 true인 경우 다음 단계로 넘어가는 코드를 활용할 수 있다.
## Q7(Program_Language): 칼럼번호 8~20 - others df21_Jp_PL = pd.DataFrame() df21_Jp_PL['Program_Language'] = [df21_Jp[col][1:].value_counts().index[0] for col in df21_Jp.columns[7:20]] df21_Jp_PL['counts'] = [df21_Jp[col][1:].value_counts().values[0] for col in df21_Jp.columns[7:20]]
## Q7(Program_Language): 칼럼번호 8~20 - others df21_Ch_PL = pd.DataFrame() df21_Ch_PL['Program_Language'] = [df21_Ch[col][1:].value_counts() .index[0] for col in df21_Ch.columns[7:20]] df21_Ch_PL['counts'] = [df21_Ch[col][1:].value_counts() .values[0] for col in df21_Ch.columns[7:20]]
## 제거된 나라 칼럼과 value를 각각 삽입 및 통합 df21_Jp_PL.insert(0, 'Country', 'Japan') df21_Ch_PL.insert(0, 'Country', 'China')
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
<ipython-input-5-89d86f0a4d0b> in <module>
11 ## Q7(Program_Language): 칼럼번호 8~20 - others
12 df21_Ch_PL = pd.DataFrame()
---> 13 df21_Ch_PL['Program_Language'] = [df21_Ch[col][1:].value_counts() .index[0] for col in df21_Ch.columns[7:20]]
14 df21_Ch_PL['counts'] = [df21_Ch[col][1:].value_counts() .values[0] for col in df21_Ch.columns[7:20]]
15
<ipython-input-5-89d86f0a4d0b> in <listcomp>(.0)
11 ## Q7(Program_Language): 칼럼번호 8~20 - others
12 df21_Ch_PL = pd.DataFrame()
---> 13 df21_Ch_PL['Program_Language'] = [df21_Ch[col][1:].value_counts() .index[0] for col in df21_Ch.columns[7:20]]
14 df21_Ch_PL['counts'] = [df21_Ch[col][1:].value_counts() .values[0] for col in df21_Ch.columns[7:20]]
15
E:\Sadness\anaconda3\lib\site-packages\pandas\core\indexes\base.py in __getitem__(self, key)
4295 if is_scalar(key):
4296 key = com.cast_scalar_indexer(key, warn_float=True)
-> 4297 return getitem(key)
4298
4299 if isinstance(key, slice):
IndexError: index 0 is out of bounds for axis 0 with size 0
결측 column 식별 및 제거
*IndexError: index 0 is out of bounds for axis 0 with size 0 오류가 식별됐다. 아마 China, Program_Language의 특정 응답이 없어서 발생한거같다.
## Q7(Program_Language): 칼럼번호 8~20 - others df21_Jp_PL = pd.DataFrame() df21_Jp_PL['Program_Language'] = [df21_Jp[col][1:].value_counts().index[0] for col in df21_Jp.columns[7:19]] df21_Jp_PL['counts'] = [df21_Jp[col][1:].value_counts().values[0] for col in df21_Jp.columns[7:19]]
## Q7(Program_Language): 칼럼번호 8~20 - others - Q7_Part12(None) df21_Ch_PL = pd.DataFrame() df21_Ch_PL['Program_Language'] = [df21_Ch_rmQ07P12[col][1:].value_counts() .index[0] for col in df21_Ch_rmQ07P12.columns[7:18]] df21_Ch_PL['counts'] = [df21_Ch_rmQ07P12[col][1:].value_counts() .values[0] for col in df21_Ch_rmQ07P12.columns[7:18]]
## 제거된 나라 칼럼과 value를 각각 삽입 및 통합 df21_Jp_PL.insert(0, 'Country', 'Japan') df21_Ch_PL.insert(0, 'Country', 'China')
## Q7(Program_Language): 칼럼번호 8~20 - others df21_Jp_PL = pd.DataFrame() df21_Jp_PL['Program_Language'] = [df21_Jp[col][1:].value_counts().index[0] for col in df21_Jp.columns[7:19]] df21_Jp_PL['counts'] = [df21_Jp[col][1:].value_counts().values[0] for col in df21_Jp.columns[7:19]]
## Q7(Program_Language): 칼럼번호 8~20 - others - Q7_Part12(None) df21_Ch_PL = pd.DataFrame() df21_Ch_PL['Program_Language'] = [df21_Ch_rmQ07P12[col][1:].value_counts() .index[0] for col in df21_Ch_rmQ07P12.columns[7:18]] df21_Ch_PL['counts'] = [df21_Ch_rmQ07P12[col][1:].value_counts() .values[0] for col in df21_Ch_rmQ07P12.columns[7:18]]
## 제거된 나라 칼럼과 value를 각각 삽입 및 통합 df21_Jp_PL.insert(0, 'Country', 'Japan') df21_Ch_PL.insert(0, 'Country', 'China')
## Q18(Program_Language): 칼럼번호 83~95 - others & other(text) df19_Jp_PL = pd.DataFrame() df19_Jp_PL['Program_Language'] = [df19_Jp[col][1:].value_counts().index[0] for col in df19_Jp.columns[82:93]] df19_Jp_PL['counts'] = [df19_Jp[col][1:].value_counts().values[0] for col in df19_Jp.columns[82:93]]
## 2019 China Q18_Part11(None) 결측값 제거 df19_Ch_rmQ18P11 = df19_Ch.drop(['Q18_Part_11'], axis='columns')
## Q18(Program_Language): 칼럼번호 83~95 - others & other(text) - Q18_Part11(None) df19_Ch_PL = pd.DataFrame() df19_Ch_PL['Program_Language'] = [df19_Ch_rmQ18P11[col][1:].value_counts() .index[0] for col in df19_Ch_rmQ18P11.columns[82:92]] df19_Ch_PL['counts'] = [df19_Ch_rmQ18P11[col][1:].value_counts() .values[0] for col in df19_Ch_rmQ18P11.columns[82:92]]
앞으로 많은 그래프를 그려낼거고 df_Jp['Q2'][1:].value_counts() 형식이 반복된다.
df_Jp['Q2'][1:].value_counts()을 객체로 만들어서 넣어도 되겠지만, 이번 작업에서 사용할 df은 df_Jp&df_Ch 2개로 dataframe 객체의 변동이 있고, 칼럼명도 Q1,Q2로 변동이 있다. 위 조건에 부합하는 간단한 함수 하나 만들겠다.
E:\Sadness\anaconda3\lib\site-packages\IPython\core\interactiveshell.py:3165: DtypeWarning: Columns (0,195,201,285,286,287,288,289,290,291,292) have mixed types.Specify dtype option on import or set low_memory=False.
has_raised = await self.run_ast_nodes(code_ast.body, cell_name,
cvs datasat을 불러오는데 위와 같이 DtypeWarning 이라는 경고가 출력되었다.
DtypeWarning 해결하기
filterwarnings
경고 그 자체가 원하는 작업은 아니지만 문제가 발생하지 않는 이상 user는 경고를 무시해도 상관없다.
권장하지 않지만 거슬린다면 warnings 모듈의 경고 필터 ignore를 사용해 경고 출력을 무시할 수 있다.
1 2
from warnings import filterwarnings filterwarnings('ignore')
low_memory
하지만 근본적인 해결은 아니므로 terminal이 제안한 방식인 low_memory=False 를 넣어 해결할 수 있다.
1
df = pd.read_csv("csv url", low_memory=False)
위 방식은 각 column마다 data type을 추측하는 방식으로 옵션명(낮은 메모리)처럼 작업량이 data 크기에 비례해 증가한다.
방대한 데이터를 처리해야되서 Dtype 추측 방식이 부담된다면?
dtype 형식 지정
불러올 file의 문자 형식을 알고 있다면 dtype을 지정해 warning을 해결하고 memory 가용량도 줄일 수 있다.
definput_num(): print("input Question no.: ") Qnum = input() Qnum_subQ = [s for s in df_col_name if"Q"+Qnum+"_"in s] if Qnum_subQ == []: return"Q"+Qnum+" has not part_Q" Question_type = None if [s for s in Qnum_subQ if ("A"or"B") in s]: print("input Q"+Qnum+"'s type") Question_type = input().upper() if Qnum_subQ: print("input Q"+Qnum+"'s last part no.: ") part_num = input() return questions_count(Qnum,part_num,Question_type)
위 함수는 Question number(Qnum)을 입력받은 뒤 이전에 정의한 칼럼명을 추출해 낸 df_col_name 객체내에서 "Q"+Qnum+"_" 문자열이 있는 sub_Question 형식을 찾아낸다.
Data Transformation에서 얻어낸 코드는 Qnum 만 있는 경우에 대한 작업이 없으므로 이 경우는 line6 조건문에서 함수를 종료한다.
line 9의 if문에선 'A' or 'B' 문자가 들어있는 문자열을 list에서 식별해 해당하는 경우엔 Question_type 객체에 type을 입력 받게 되는 조건문을 작성했다. 이 때, upper() 함수를 이용해 소문자로 입력받아도 대문자로 자동변환되게 설정했다.
line 14의 조건문은 sub Question의 마지막 열을 입력받는 조건문으로 사용자가 ‘kaggle_survey’에서 직접 확인해서 input 값을 정해야한다.