import pandas as pd
df = pd.read_csv('C:\\Users\\user\\Desktop\\티스토리데이터\\수원도시공사_실내공기질 측정 정보_20201211.csv', encoding = 'euc-kr')
df.head(3)

주차장명 주소 규모(㎡) 미세먼지(㎍/㎡) 이산화탄소(ppm) 포름알데히드(㎍/㎡) 일산화탄소(ppm) 오존(ppm) 측정결과 데이터기준일
0 교동공영주차장 팔달구 향교로 140(교동) 9165.68 49.8 513.0 3.838 4.00 NaN 양호 2020/12/11
1 권선지하공영주차장 권선구 세지로 82번길 15-3(권선동) 2073.96 33.0 476.5 2.556 3.60 NaN 양호 2020/12/11
2 매봉공영주차장 영통구 동탄원천로 1109번길 61(매탄동) 4682.43 40.5 460.0 6.647 3.10 NaN 양호 2020/12/11

데이터 출처 :

https://www.data.go.kr/data/15025602/fileData.do

 

수원도시공사_실내공기질 측정 정보_20201211

수원도시공사 실내공기질 측정 정보 (주차장명, 주소, 규모, 미세먼지, 이산화탄소, 포름알데히드, 일산화탄소, 오존) 데이터를 제공합니다.

www.data.go.kr

 

 

1. columns에 리스트 넣기

df.columns = ['a', 'b', 'c', 'd', 'e', 'f',
       'g', 'h', 'i', 'j' ]
df.head(3)

a b c d e f g h i j
0 교동공영주차장 팔달구 향교로 140(교동) 9165.68 49.8 513.0 3.838 4.0 NaN 양호 2020/12/11
1 권선지하공영주차장 권선구 세지로 82번길 15-3(권선동) 2073.96 33.0 476.5 2.556 3.6 NaN 양호 2020/12/11
2 매봉공영주차장 영통구 동탄원천로 1109번길 61(매탄동) 4682.43 40.5 460.0 6.647 3.1 NaN 양호 2020/12/11

컬럼명이 알파벳으로 바뀜

단. 이경우 입력되는 리스트의 길이가 원데이터프레임의 길이와 일치 하지 않을시 에러발생

 

2. pandas의 rename 활용

 

원하는 컬럼만 이름 변경가능

df.rename(columns = {'c':'C','h':'H'}, inplace =True)
df.head(1)

a b C d e f g H i j
0 교동공영주차장 팔달구 향교로 140(교동) 9165.68 49.8 513.0 3.838 4.0 NaN 양호 2020/12/11

c와 h만 대문자로 변환

 

df.rename({'a':'A','b':'B','e':'E' }, axis = 1, inplace =True)
df.head(1)


A
B C d E f g H i j
0 교동공영주차장 팔달구 향교로 140(교동) 9165.68 49.8 513.0 3.838 4.0 NaN 양호 2020/12/11

axis를 활용해도 가능

 

이 형식을 포문을 활용해서 간단하게 모두 대문자로 바꿀 수 있음

 

for col in df.columns:
    df.rename({col:col.upper()}, axis = 1, inplace =True)
df.head(1)

A B C d E f g H i j
0 교동공영주차장 팔달구 향교로 140(교동) 9165.68 49.8 513.0 3.838 4.0 NaN 양호 2020/12/11

rename(axis) 를 활용한 방법은 인덱스에서도 동일적용가능

 

A 컬럼을 인덱스로 설정후 원하는 인덱스만 이름바꾸기

df.set_index('A', drop = True, inplace =True)
df.rename({'교동공영주차장':'교동'}, inplace = True )
df.head()

B C D E F G H I J
A








교동 팔달구 향교로 140(교동) 9165.68 49.8 513.0 3.838 4.00 NaN 양호 2020/12/11
권선지하공영주차장 권선구 세지로 82번길 15-3(권선동) 2073.96 33.0 476.5 2.556 3.60 NaN 양호 2020/12/11
매봉공영주차장 영통구 동탄원천로 1109번길 61(매탄동) 4682.43 40.5 460.0 6.647 3.10 NaN 양호 2020/12/11
영통공영주차장 영통구 봉영로 1598(영통동) 10974.16 54.4 498.7 3.639 4.97 NaN 양호 2020/12/11
영화동공영주차장 장안구 수성로 382번길 24(영화동) 5365.87 41.5 473.0 6.796 2.90 NaN 양호 2020/12/11
for idx in df.index:
    df.rename({idx:idx.replace('공영주차장','')}, inplace = True )
df.head()


B
C D E F G H I J
A








교동 팔달구 향교로 140(교동) 9165.68 49.8 513.0 3.838 4.00 NaN 양호 2020/12/11
권선지하 권선구 세지로 82번길 15-3(권선동) 2073.96 33.0 476.5 2.556 3.60 NaN 양호 2020/12/11
매봉 영통구 동탄원천로 1109번길 61(매탄동) 4682.43 40.5 460.0 6.647 3.10 NaN 양호 2020/12/11
영통 영통구 봉영로 1598(영통동) 10974.16 54.4 498.7 3.639 4.97 NaN 양호 2020/12/11
영화동 장안구 수성로 382번길 24(영화동) 5365.87 41.5 473.0 6.796 2.90 NaN 양호 2020/12/11

인덱스에서 공영주차장이라는 말을 제거하고 인덱스를 생성할 수 있다

def chunjiin(jamo):
    chosung = ['ㄱ', 'ㄴ', 'ㄷ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅅ',  'ㅇ', 'ㅈ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ']
    jamo = jamo_split(jamo_split(jamo))
    jaeum = ''
    k = []
    for i in jamo:
        if i in chosung:
            jaeum +=i
        else:
            k.append(jaeum)
            jaeum = ''
            k.append(i)
    k.append(jaeum)   
    k = list(filter(lambda x : len(x) > 0, k))
    jamo = '/'.join(k)
    
    dic2 = {'ㅏ':'ㅣㆍ','ㅑ':'ㅣㆍㆍ','ㅓ':'ㆍㅣ','ㅕ':'ㆍㆍㅣ',
        'ㅗ':'ㆍㅡ','ㅛ':'ㆍㆍㅡ','ㅜ':'ㅡㆍ','ㅠ':'ㅡㆍㆍ'}
    
    for i in jamo:
        if i in dic2.keys():
            jamo = jamo.replace(i,dic2[i])
    result = jamo.split('/')        
    tag = list(repeat('중성',len(result)))
    if result[0][0] in chosung:
            tag[0] = '초성'
    if result[-1][0] in chosung:
            tag[-1] = '종성'    
    return list(zip(result,tag))
#     return result

def word_chunjiin(word):
    return list(map(chunjiin,word))
    
    
word = '세종대왕'
chunjiin = word_chunjiin(word)
for i in range(len(word)):
    print(word[i], chunjiin[i])
세 [('ㅅ', '초성'), ('ㆍㅣ', '중성'), ('ㅣ', '중성')]
종 [('ㅈ', '초성'), ('ㆍㅡ', '중성'), ('ㅇ', '종성')]
대 [('ㄷ', '초성'), ('ㅣㆍ', '중성'), ('ㅣ', '중성')]
왕 [('ㅇ', '초성'), ('ㆍㅡ', '중성'), ('ㅣㆍ', '중성'), ('ㅇ', '종성')]

저번 게시글의 연장선코드

자모분리란

한글 -> ㅎㅏㄴㄱㅡㄹ이렇게 한글을 하나의 자음 모음으로 분리하는 것을 말한다.

 

한글 자모분리를 위해서는 jamo패키지설치가 필요하다.

!pio install jamo

 

jamo패키지에서는 초성 중성 종성분리를 지원한다.

j2hcj(h2j('한글'))
'ㅎㅏㄴㄱㅡㄹ'

이렇게 분리가 가능하지만 

 

j2hcj(h2j('민원'))
'ㅁㅣㄴㅇㅝㄴ'

ㅝ 같이 단모음이 아닌 이중모음의 경우 이중모음을 각각의 단모음으로 리턴해주지는 않는다 

이중모음을 단모음으로 분해해주기 위해서는 딕셔너리를 이용하여 대체해줄 필요가 있다.

같은이유로 ㄳ같은 이중자음 또한 단모음으로 분리할 수 있다.

 

def jamo_split(word):
    jamo = j2hcj(h2j(word))
    dic = {'ㅐ':'ㅏㅣ','ㅒ':'ㅑㅣ','ㅔ':'ㅓㅣ','ㅖ':'ㅕㅣ',
       'ㅘ':'ㅗㅏ','ㅙ':'ㅗㅐ','ㅚ':'ㅗㅣ','ㅝ':'ㅜㅓ',
       'ㅞ':'ㅜㅔ','ㅟ':'ㅜㅣ','ㅢ':'ㅡㅣ',
       'ㄳ' : 'ㄱㅅ', 'ㄵ' : 'ㄴㅈ', 'ㄶ' : 'ㄴㅎ','ㄺ' : 'ㄹㄱ',
       'ㄻ' : 'ㄹㅁ', 'ㄼ' : 'ㄹㅂ', 'ㄽ' : 'ㄹㅅ', 'ㄾ' : 'ㄹㅌ', 
       'ㄿ' : 'ㄹㅍ', 'ㅀ' : 'ㄹㅎ', 'ㅄ' : 'ㅂㅅ', 'ㄲ' : 'ㄱㄱ','ㅆ'  : 'ㅅㅅ'}
    for i in jamo:
        if i in dic.keys():
            jamo = jamo.replace(i,dic[i])
    return jamo
    
print(jamo_split('민원'))
print(jamo_split('값싼은혜'))
'ㅁㅣㄴㅇㅜㅓㄴ'

'ㄱㅏㅂㅅㅅㅅㅏㄴㅇㅡㄴㅎㅕㅣ'

이렇게 모든 문자를 단모음,단자음으로 바꿀 수 있다.

 

 

데이터 타입변경

lf_bus = pd.read_csv('./저상버스.csv')
display(lf_bus.head())
lf_bus.info()

노선번호 인가대수 저상대수 보유율 배차간격 저상버스간격 차량count
0 100 32 32 1.0 8 8 135
1 742 31 24 1.0 8 10 105
2 2312 21 3 0.0 8 56 19
3 4312 29 14 0.0 8 17 65
4 5634 15 11 1.0 8 11 99
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 294 entries, 0 to 293
Data columns (total 7 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   노선번호     294 non-null    object 
 1   인가대수     294 non-null    int64  
 2   저상대수     294 non-null    int64  
 3   보유율      293 non-null    float64
 4   배차간격     294 non-null    int64  
 5   저상버스간격   294 non-null    object 
 6   차량count  294 non-null    object 
dtypes: float64(1), int64(3), object(3)
memory usage: 16.2+ KB

데이터 프레임을 확인하면 모든 데이터의 값이 숫자형태로 보이지만 사실은 오브젝트 형태인 녀석들이 있다 이러면 숫자 연산이 불가능 하기때문에 숫자형태로 형변환이 필요하다.

 

print(lf_bus['저상버스간격'])
print(lf_bus['저상버스간격'].astype('int'))
0       8
1      10
2      56
3      17
4      11
       ..
288     8
289     8
290    11
291     8
292     8
Name: 저상버스간격, Length: 293, dtype: object
0       8
1      10
2      56
3      17
4      11
       ..
288     8
289     8
290    11
291     8
292     8
Name: 저상버스간격, Length: 293, dtype: int32

이렇게 바꾼다 . 이것을 포문을 이용해서 바꾸고싶은 열만 바꾸면

int_cols = lf_bus.columns.tolist()
int_cols.remove('노선번호')
print(int_cols)

for col in int_cols:
    lf_bus[col] = lf_bus[col].astype('int')

lf_bus.info()
['인가대수', '저상대수', '보유율', '배차간격', '저상버스간격', '차량count']
<class 'pandas.core.frame.DataFrame'>
Int64Index: 293 entries, 0 to 292
Data columns (total 7 columns):
 #   Column   Non-Null Count  Dtype 
---  ------   --------------  ----- 
 0   노선번호     293 non-null    object
 1   인가대수     293 non-null    int32 
 2   저상대수     293 non-null    int32 
 3   보유율      293 non-null    int32 
 4   배차간격     293 non-null    int32 
 5   저상버스간격   293 non-null    int32 
 6   차량count  293 non-null    int32 
dtypes: int32(6), object(1)
memory usage: 11.4+ KB

내가 지정한 컬럼을 int32타입으로 바꿀 수 있다.

 

데이터 범주화

count= lf_bus['차량count']
lf_bus['범주'] = pd.cut(count,4, include_lowest= False)
print(lf_bus['범주'])
print('*'*30)
print(count.describe().iloc[3:])
print('*'*30)
lf_bus['범주'] = pd.cut(count,count.describe().iloc[3:], include_lowest= True)
print(lf_bus['범주'])
print('*'*30)
lf_bus['범주'] = pd.cut(count,count.describe().iloc[3:], labels = False, include_lowest= False)
print(lf_bus['범주'])
print('*'*30)
lf_bus['범주'] = pd.cut(count,count.describe().iloc[3:], labels = ['a','b','c','d'], include_lowest= False)
print(lf_bus['범주'])
0      (103.0, 135.0]
1      (103.0, 135.0]
2       (6.872, 39.0]
3        (39.0, 71.0]
4       (71.0, 103.0]
            ...      
288    (103.0, 135.0]
289    (103.0, 135.0]
290     (71.0, 103.0]
291    (103.0, 135.0]
292    (103.0, 135.0]
Name: 범주, Length: 293, dtype: category
Categories (4, interval[float64, right]): [(6.872, 39.0] < (39.0, 71.0] < (71.0, 103.0] < (103.0, 135.0]]
******************************
min      7.0
25%     68.0
50%     92.0
75%    122.0
max    135.0
Name: 차량count, dtype: float64
******************************
0      (122.0, 135.0]
1       (92.0, 122.0]
2       (6.999, 68.0]
3       (6.999, 68.0]
4       (92.0, 122.0]
            ...      
288    (122.0, 135.0]
289    (122.0, 135.0]
290     (92.0, 122.0]
291    (122.0, 135.0]
292    (122.0, 135.0]
Name: 범주, Length: 293, dtype: category
Categories (4, interval[float64, right]): [(6.999, 68.0] < (68.0, 92.0] < (92.0, 122.0] < (122.0, 135.0]]
******************************
0      3.0
1      2.0
2      0.0
3      0.0
4      2.0
      ... 
288    3.0
289    3.0
290    2.0
291    3.0
292    3.0
Name: 범주, Length: 293, dtype: float64
******************************
0      d
1      c
2      a
3      a
4      c
      ..
288    d
289    d
290    c
291    d
292    d
Name: 범주, Length: 293, dtype: category
Categories (4, object): ['a' < 'b' < 'c' < 'd']

pd.cut 함수로 데이터의 범주화가 가능하다.

원래는 if, else문을 활용해서 함수정의 후 map으로 각각 적용했는데

pd.cut을 이용하면 간단하게 범주화가 가능하다. 데이터타입은 labels = False가 아니라면 

기본적으로 카테고리 데이터이다.

라벨을 지정하면 카테고리 명을 설정가능하다.

데이터를 자르는 경계를 설정하지않으면 등간격으로 잘라준다.

 

include_lowest= True 를 선택시 왼쪽 간격을 포함, False시 미포함

 

( 는 <를 ] =<를 의미

lambda

함수를 한줄에 정의하고 사용만 한다.코드의 간결함 메모리의 절약

람다 함수는 정의와 동시에 사용할 수 있음

 

 

lambda  매개변수 : 리턴값

print((lambda x : x**2)(2))
4

제곱을 해주는 함수를 정의하고 바로 숫자 2를 넣어 리턴값을 돌려받음

 

lambda 매개변수 : 리턴값 if 조건 else 리턴값

nums = list(range(-4,4))
print(nums)
print(list(map(lambda x :  '양수' if x >= 0 else '음수',nums)))
[-4, -3, -2, -1, 0, 1, 2, 3]
['음수', '음수', '음수', '음수', '양수', '양수', '양수', '양수']

 

map

리스트데이터의 각 원소에 함수를 적용하고 싶을 때 사용

map(각원소에 적용할 함수, 리스트 )

각 과일이름의 길이를 리턴

fruits = ['사과','바나나','포도','파인애플','귤']
print(map(len,fruits))
print(list(map(len,fruits)))
<map object at 0x0000013FDE88A7C0>
[2, 3, 2, 4, 1]

맵 함수만 적용하면 맵형태의 객체가 리턴되므로

리스트형태로 명시적 형변환을 실시

names = ['이우현','이종수','이병군','김나명']
list(map(lambda x : x[-2:],names))
['우현', '종수', '병군', '나명']

람다함수를 이용도 가능

 

list_2 = [[1,23,4],[21,24624,12321],[4718,82,29,300],[32,365,33]]
print(list(map(sum,list_2)))
print([sum(i) for i in list_2 ])
[28, 36966, 5129, 430]
[28, 36966, 5129, 430]

리스트 컴프리헨션과 같은결과

 

import pandas as pd
print(series_)
print(series_.map(lambda x : x**2))
print(series_**2)
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9
dtype: int64
0     0
1     1
2     4
3     9
4    16
5    25
6    36
7    49
8    64
9    81
dtype: int64
0     0
1     1
2     4
3     9
4    16
5    25
6    36
7    49
8    64
9    81
dtype: int64

판다스 시리즈객체에 대해도 적용가능

filter

리스트에서 질의한 부분이 참인 경우만 리턴하는 함수

map과 같이 list로 명시적형변환 필요

시리즈에는 x

print(list_)
print('-'*30)
print(series_)
print('-'*30)
print(list(filter(lambda x: x>2,list_)))
print('-'*30)
print(series_[series_ > 2])
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
------------------------------
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9
dtype: int64
------------------------------
[3, 4, 5, 6, 7, 8, 9]
------------------------------
3    3
4    4
5    5
6    6
7    7
8    8
9    9
dtype: int64

시리즈에는 인덱싱으로 적용

웹페이지를 크롤링하다보면 스크롤을 하지않으면 댓글이나 게시글이 보이지않는 경우가 있을것이다

예를 들어 유튜브의 댓글창 같은것 말이다. 스크롤을 하지않으면 댓글이 웹브라우저의 정보가 추가 되지않는다

그럴 경우에는 스크롤을 내려 정보를 모두 가져온 후에 웹페이지정보를 가져와 크롤링을 시도해야한다.

 

options = webdriver.ChromeOptions()
# options.add_argument('--headless') # 창 새로 띄우는 것 없이
options.add_argument('--disable-gpu') # gpu 사용안하겠다 괜한 오류나오니깐

browser = webdriver.Chrome(options=options)
browser.maximize_window()

browser.get(url)

sleep_time = 1

# 스크롤 높이 가져옴
last_height = browser.execute_script("return document.body.scrollHeight")

while True:
    # 끝까지 스크롤 내리기
    browser.execute_script("window.scrollTo(0, document.body.scrollHeight);")

    # 대기
    time.sleep(sleep_time)

    # 스크롤 내린 후 스크롤 높이 다시 가져옴
    new_height = browser.execute_script("return document.body.scrollHeight")
    if new_height == last_height:
        break
    last_height = new_height

 그럴경우에는 이렇게 하면된다.

이 이후 크롤링을 계속하면되겠다..

 

하지만 유튜브의 댓글의 경우 

browser.execute_script("return document.body.scrollHeight") 

스크롤의 정보가 변했음에도 불구하고 위의 명령어가 같은 정보를 반환했다. 

그래서 찾은해결책이

from selenium.webdriver.common.keys import Keys

for i in range(300)    
        browser.find_element_by_tag_name('body').send_keys(Keys.PAGE_DOWN)
        time.sleep(0.5)

 

페이지다운을 누르는 것이다. 

하지만 이방법은 끝에 도달하지않아도 페이지다운을 누르는 것을 멈추고

끝에 도달했음에도 페이지다운을 누르는 문제점이 있어 시간적으로 비효율적이다.

유튜브 페이지에서 페이지의 스크롤 정보를 가져오는 것을 막아 놓아서 방법을 찾지못했다. 

 

chim_coments = []

url = 'https://www.youtube.com/watch?v=hnanNlDbsE4'

options = webdriver.ChromeOptions()
options.add_argument('--headless') # 창 새로 띄우는 것 없이
options.add_argument('--disable-gpu') # gpu 사용안하겠다 괜한 오류나오니깐

browser = webdriver.Chrome(options=options)
browser.maximize_window()
browser.get(url)

time.sleep(3)
    
for i in range(300):
        browser.find_element_by_tag_name('body').send_keys(Keys.PAGE_DOWN)
        time.sleep(0.5)
     

    
soup = BeautifulSoup(browser.page_source,'lxml')
titles = soup.find_all('yt-formatted-string', attrs = {'id':'content-text'})
titles
for title in tqdm(titles):
    comment = title.get_text()
    chim_coments.append(comment)

침착맨 유튜브의 댓글을 크롤링한 코드이다. 

혹시 나와같은 상황이라면 도움이되었으면 좋겠다.

크롤링을 시도하다보면 분명 f12를 통해서 페이지검사를 실시했을 떄는 문제 없이 나오던 html형식이

request모듈을 사용해서 가져오면 파이썬 내에서 보이지않는 경우가 발생한다.

url = 'https://sports.news.naver.com/wfootball/index'

re = requests.get(url)
soup = BeautifulSoup(re.text, 'lxml')
soup.find_all('span','title')
[<span class="title" onclick="clickcr(this, 'stb.txt', '', '', event);">
                                 BBC 전문가 "손흥민 있으니 케인 팔아라" 2..
                             </span>,
 <span class="title" onclick="clickcr(this, 'stb.txt', '', '', event);">
                                 '첼시 새 9번' 루카쿠 "경기장에서 증명해보이..
                             </span>,
 <span class="title" onclick="clickcr(this, 'stb.txt', '', '', event);">
                                 황의조 이적료 137억!! 무조건 팔아야 하는 ..
                             </span>,
 <span class="title" onclick="clickcr(this, 'stb.txt', '', '', event);">
                                 토트넘은 노났고, 맨시티는 절박해졌다 [달수네라..
                             </span>,
 <span class="title" onclick="clickcr(this, 'stb.txt', '', '', event);">
                             
                             
                             21/22시즌 맨시티가 챔스 조별리그에서 만날 상대는?
                         </span>,
 <span class="title" onclick="clickcr(this, 'stb.txt', '', '', event);">
                             
                             
                             무리뉴의 전사된 타미, 로마 개막전 승리 견인
                         </span>,
 <span class="title" onclick="clickcr(this, 'stb.txt', '', '', event);">
                             
                             
                             밀란 GK 메냥, 선방만큼 돋보였던 롱패스
                         </span>,
 <span class="title" onclick="clickcr(this, 'stb.txt', '', '', event);">
                             
                             
                             첼시 루카쿠, 홀드 업 플레이 장착해 왕으로 돌아왔다
                         </span>]

내가 선택한 부분은 분명 span태그의 title클래스를 가지는 녀석인데 soup과 requests를 이용한 결과에는 확인되지않는다.

 

 

이 경우에는 정확하지는 않지만 자바스크립트로 작성된 웹페이지여서 그렇다고 한다.

움직이는 페이지나 움직여야만, 사용자가 동작을 해야만 페이지의 정보를 보여줘서, 가져올 수 있어서 

동적크롤링이라고 생각하면 편하다.

 

동적크롤링을 위해서는 셀레니움모듈을 사용한다.

 

 

!pip install selenium

셀레니움을 다른 모듈과 같은 방식으로 설치하고

사용을 위해서는 웹드라이버 설치가 요구된다.

크롬브라우저의 우측상단 점세개 > 도움말 >크롬정보로 가면 

위와같이 크롬브라우저의 버전을 확인할 수 있다.

 

https://chromedriver.chromium.org/downloads

 

ChromeDriver - WebDriver for Chrome - Downloads

Current Releases If you are using Chrome version 93, please download ChromeDriver 93.0.4577.15 If you are using Chrome version 92, please download ChromeDriver 92.0.4515.107 If you are using Chrome version 91, please download ChromeDriver 91.0.4472.101 For

chromedriver.chromium.org

위 사이트에 가서 자신의 버전과 가까운 크롬브라우저를 다운로드받으면 된다.

나는 92.0.4515.159 버전이기 때문에 ChromeDriver 92.0.4515.107를 다운했했다.

마지막 세자리를 제외하고 생각하는것이 편할 것이다.

그리고 자신의 버전보다 숫자가 높다면 호환이 문제가 생길 수 있기때문에 가까운버전을 선택하되 낮은 버전을 선택하는 것이 좋겠다.

 

다운을 완료했다면 압축을 해제하고 자신이 코딩할 파이썬 파일(.py , .ipynb )이 있는 폴더에 옮기면된다.

자신의 경로를 모르겠다면 

import os 
os.getcwd()

이렇게 입력하고 실행하면 현재 경로를 알려준다.

웹드라이버가 제 위치를 찾았다면 동적크롤링의 준비가 완료된  것이다. 

 

이제 아까와 같은 페이지를 셀레니움을 활용해 동적크롤링해보자.

 

url = 'https://sports.news.naver.com/wfootball/index'

options = webdriver.ChromeOptions()
options.add_argument('--headless') # 창 새로 띄우는 것 없이
options.add_argument('--disable-gpu') # gpu 사용안하겠다 괜한 오류나오니깐

browser = webdriver.Chrome(options=options)
browser.maximize_window()

browser.get(url)

soup = BeautifulSoup(browser.page_source,'lxml')
soup.find_all('span','title')
[<span class="title">[UCL PO 2차] PSV 에인트호번 vs 벤피카 MVP 코디 학포</span>,
 <span class="title">[UCL PO 2차] PSV에게 '제로'라고 답하는 블라호디모스</span>,
 <span class="title">[UCL PO 2차] '무리한 도전' 베리시무의 경고 누적 퇴장!</span>,
 <span class="title">이강인 이적 상황 급변...발렌시아와 계약 종료 협상 중</span>,
 <span class="title">'단 1분도 못 뛰었는데' 벤치 신세로 전락한 에이스, 솔샤르의 희망고문인가</span>,
 <span class="title">토트넘 팬들 속탄다...케인, 이적설 파동 속에 골프 삼매경</span>,
 <span class="title">"호날두 만나고 싶다" 손흥민 꿈 이루어지나…토트넘 이적설 화제</span>,
 <span class="title">토트넘, 233억 원에 파페 마타르 사르 영입 합의</span>,
 <span class="title">AI가 예측한 EPL 최종 순위는? 1위는 맨시티, 아스널은 8위?</span>,
 <span class="title"> PSV 에인트호번 vs 벤피카 MVP 코디 학포</span>,
 <span class="title"> PSV에게 '제로'라고 답하는 블라호디모스</span>,
 <span class="title"> '이해를 못한 것이냐? ' 극대노하는 제주스 감독</span>,
 <span class="title"> 골대 강타에 멘탈이 무너지는 자하비</span>,
 <span class="title"> 과한 욕심에 역습 찬스 놓치는 야렘추크</span>,
 <span class="title"> PSV 에인트호번 vs 벤피카 전반 주요장면</span>,
 <span class="title"> '무리한 도전' 베리시무의 경고 누적 퇴장!</span>,
 <span class="title"> 괴체의 테크닉, 기회를 놓치는 필립 막스</span>,
 <span class="title"> '아직 죽지 않았다' 벤피카의 돌격 대장 타랍!</span>,
 <span class="title"> 슈팅의 침묵을 깨는 필립 막스</span>,
 <span class="title" onclick="clickcr(this, 'stb.txt', '', '', event);">
                                 BBC 전문가 "손흥민 있으니 케인 팔아라" 2..
                             </span>,
 <span class="title" onclick="clickcr(this, 'stb.txt', '', '', event);">
                                 '첼시 새 9번' 루카쿠 "경기장에서 증명해보이..
                             </span>,
 <span class="title" onclick="clickcr(this, 'stb.txt', '', '', event);">
                                 황의조 이적료 137억!! 무조건 팔아야 하는 ..
                             </span>,
 <span class="title" onclick="clickcr(this, 'stb.txt', '', '', event);">
                                 토트넘은 노났고, 맨시티는 절박해졌다 [달수네라..
                             </span>,
 <span class="title" onclick="clickcr(this, 'stb.txt', '', '', event);">
                             
                             
                             21/22시즌 맨시티가 챔스 조별리그에서 만날 상대는?
                         </span>,
 <span class="title" onclick="clickcr(this, 'stb.txt', '', '', event);">
                             
                             
                             '완전체로 돌아온' 루카쿠, 골-연계-몸싸움까지 다 보여주다
                         </span>,
 <span class="title" onclick="clickcr(this, 'stb.txt', '', '', event);">
                             
                             
                             밀란 GK 메냥, 선방만큼 돋보였던 롱패스
                         </span>,
 <span class="title" onclick="clickcr(this, 'stb.txt', '', '', event);">
                             
                             
                             무리뉴의 전사된 타미, 로마 개막전 승리 견인
                         </span>]

일반 크롤링에서는 찾아볼 수 없었던 정보를 가져오고 있는 것을 확인할 수 있다.

지도를 활용하여 시각화를 하고싶은데 주소는 있지만 위경도 정보가 없어서 시각화하기 제한되는 경우 사용

 

먼저 카카오 api에 접속 및 가입

 

https://developers.kakao.com/

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

바로보이는 시작하기

어플 추가하기 클릭

 

형식에 맞게 입력!

너무 긴장할 필요없이 지오코딩 / 실명 이런식으로 입력해도됌

생성 후 클릭하면 

이렇게 인증키들이 생성된다.

여기서 우리가 사용할 것은 restapikey입니다.

 

이제 파이썬을 활용해서 위도와 경도를 가져와보자

https://developers.kakao.com/docs/latest/ko/local/dev-guide

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

개발가이드를 참고하면 어떤정보 입력하고 어떤정보를 돌려받는지 확인가능.

def get_coordinate(address): # 주소를 입력받음
    result = "" 
 
    url = 'https://dapi.kakao.com/v2/local/search/address.json?query=' + address # 카카오 api 서버스를 이용하여 접근
    rest_api_key = 'api키이빈다' # 사용자 api key
    header = {'Authorization': 'KakaoAK ' + rest_api_key} 

    r = requests.get(url, headers=header) 
 
    if r.status_code == 200: # 정보를 오류없이 받아왔다면
        if len(r.json()['documents']) != 0: # 길이가 0이 아니라면
            try : 
                result_address = r.json()["documents"][0]["address"]
                result = (result_address["y"],result_address["x"])  # 좌표 정보에 접근
            except:  # 예외처리
                result = np.nan
            
        else: # 길이가 0인경우 na처리
            result = np.nan
            
    else:
        result = "ERROR[" + str(r.status_code) + "]"
    return result # 좌표를 반환
    
get_coordinate('수원시 권선구 권선로669번길 34(권선동) (권선동 1011-15)')
('37.2612803904025', '127.025397892703')

주소를 텍스트로 입력하면 위도와 경도를 튜플형태로 순차적으로 반환하는 함수를 정의

 

개발가이드를 보면 여러가지가 가능

웹크롤링 도중 서버에서 크롤링 시도를 거부할 경우

직접 웹을 열지않고 html형식을 가져오려면 

첫시도 몇번에는 가능했지만 여러번이 되면 html형식을 가져오려는 것을 거부하는 경우가 생긴다.

이럴경우에는 user agent 정보를 넣어주면된다.

간단히 말하면 내가 사람이고 직접접속하는 것이라고 말해주는 것이다.

접속하는 사람의 정보를 알려주는것이다.

 

https://www.whatismybrowser.com/detect/what-is-my-user-agent

 

What is my user agent?

Every request your web browser makes includes your User Agent; find out what your browser is sending and what this identifies your system as.

www.whatismybrowser.com

url = 'https://nadocoding.tistory.com'
headers = {'User-Agent':'에이전트'}
res = requests.get(url, headers = headers)

res.raise_for_status()

res.text

헤더에 유저에이전트를 명시하고 requests의 get함수를 이용할 때 헤더 정보를 입력하면 된다.

모든 사용자가 자신마의 유저에이전트 정보를 가지는 데 자신의 유저에이전트는 

상단의 웹링크에서 확인할 수 있다.

여기서파란박스에 해당하는 녀석들이 고유한 유저정보이다.

 

서버에서 정보를 가져오는 것을 막았던 경우라면 유저에이전트를 넣으면 해결될 것이다.

공공데이터에 있어 api란 실시간정보가 조회가 가능한 것이다

공공데이터 포털에 들어가게되면 csv, excel형식의 파일데이터와  open api형태의 데이터로 나누어 정보를 제공하는데 

 

보통 파일데이터로 제공하기에는 너무 정보가 방대하거나 데이터의 업데이트주기가 짧아야할 경우에 api로 제공하는모양새이다.

 

api는 사용자가 요청한 정보만을 반환한다.

 

한국천문연구원의 특일정보 api를 이용해서 공휴일 정보를 가져와보겠다.

먼저 로그인후에 원하는 데이터를 검색하고 우측상단에 활용신청을 누르면 다음과 같은페이지가 나온다. 

활용신청을 누른뒤 형식에 맞게 입력후 신청을 하면 api에 따라 다르겠지만 보통은 금방 승인이 난다. 

 

후에 마이페이지 -> 오픈 api

아까 신청했던 목록이 보일 것이다 이것을 클릭하면 

이렇게 인증키를 확인할 수 있다. 

 

이제 데이터를 받아 저장하는 파이썬 코드를 알아보자 이 api는 xml이다.

 

본문 상단에 있는 기본정보란에 상세설명을 누르면 

이렇게 요청하는해야하는 변수와 출력해주는 결과를  보여준다.

위의 경우에는 

api주소 + 기능이름 + 년 + 월 + 서비스키를 입력하면

출력결과를 가져오게된다. 

 

import requests
import datetime
from bs4 import BeautifulSoup
import urllib.parse as urlparse


def get_request_query(url, operation, params, serviceKey):
    params = urlparse.urlencode(params)
    request_query = url + '/' + operation + '?' + params + '&' + 'serviceKey' + '=' + serviceKey
    return request_query

먼저 주소,  기능, 요청변수, 서비스키를 입력하면 이들을 합쳐 하나의 url로  이어주는 함수를 정의하였다.

params는 파라미터들의 준말로 여기서는 solYear,solMonth 이다. 

 

mykey = "인증키는 노출불가능"
url = 'http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService'
operation = 'getRestDeInfo'
year = '2021'
month = '09'

params = {'solYear':year, 'solMonth':month}

request_query = get_request_query(url, operation, params, mykey)
print(request_query)

이렇게 나의 인증키, 주소, 요청인자들을 활용해 하나의 url을 만들고 해당 html구문을 분석하여

원하는 정보를 가져오면 된다.

res = requests.get(request_query)
soup = BeautifulSoup(res.text, 'lxml')
items = soup.find_all('item')

for item in items:
    day = item.locdate.get_text()
    name = item.datename.get_text()
    
    print(name, ' : ', day)
추석  :  20210920
추석  :  20210921
추석  :  20210922

포문을 활용하면 이렇게 

import requests
import datetime
import pandas as pd
from bs4 import BeautifulSoup
import urllib.parse as urlparse


def get_request_query(url, operation, params, serviceKey):
    params = urlparse.urlencode(params)
    request_query = url + '/' + operation + '?' + params + '&' + 'serviceKey' + '=' + serviceKey
    return request_query

mykey = "인증키공개 ㄴㄴ"
url = 'http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService'
operation = 'getRestDeInfo'

df = pd.DataFrame()

for year in [2019,2020,2021]:
    for month in range(1,13):

        if month < 10:
            month = '0' + str(month)
        else:
            month = str(month)
        params = {'solYear':year, 'solMonth':month}

        request_query = get_request_query(url, operation, params, mykey)

        res = requests.get(request_query)
        soup = BeautifulSoup(res.text, 'lxml')
        items = soup.find_all('item')

        for item in items:
            day = item.locdate.get_text()
            name = item.datename.get_text()
            data = {'휴일이름':name , '날짜':day}
            df = df.append(data, ignore_index=True)
    
display(df.head())
display(df.tail())
  휴일이름 날짜
0 11 20190101
1 설날 20190204
2 설날 20190205
3 설날 20190206
4 삼일절 20190301
  휴일이름 날짜
47 개천절 20211003
48 대체공휴일 20211004
49 한글날 20211009
50 대체공휴일 20211011
51 기독탄신일 20211225

 

+ Recent posts