336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

목차 (작성 예정)

(1) 100% 만족할 파이썬 엑셀 사무 자동화, 회사에서 안 된다면?

(2) 엑셀 보안 한 방에 뚫기

(3) 시간 50배 단축, 실무 엑셀 함수 구현 (vlookup, index match 등)

(4) 실무 엑셀 함수 응용

(5) 엑셀 실무용 유용한 함수 및 기능들 파이썬으로 해결

(6) 언제까지 수작업할래? 크롤링과 사진 자동으로 캡쳐, 스샷


오랜만에 글을 썼는데.. 사무자동화 마지막 글이네요. 우선 생각했던건 여기까지고 추가로 필요한 부분 있으면 제가 또 만들지 않을까.. bs4나 셀레니움(selenium)으로 크롤링까지는 금방 완성이 되는데, 자동 캡쳐나 스크린샷을 찍는 방법은 처음으로 시도했다. 검색을 해보면 전체화면 캡쳐를 하는게 대부분이고, 내가 원하는 element나 특정 부분 캡쳐는 설명이 별로 없었다. 

 

복잡하게 좌표값이나 height, witdh를 다 구해서 전체화면 스크린샷을 찍고 원하는 부분만 가져오는 것도 많았다. 물론 크롬에서 쉽게 특정 element를 캡쳐하는 방법도 있었다. 약간 신세계를 경험하는 것 같았는데 자동화가 아니다 보니 패스.. 그래도 짧게 소개해보자면

 

 

 

GSMARENA라는 싸이트에서 빨간색 박스를 친 부분의 사진만 가져오고 싶을 때, 우선 F12 개발자 도구를 켠다. 그 다음에 Elements에서 저 부분을 포함하는 class나 id를 찾고 클릭을 한다. 이후에 shift + crtl + p 를 누른다.

 

 

 

 

그러면 개발자용 명령창이 뜨게 되는데 약자로 cnod를 검색. capture node screenshot이 나오게 되는데, 클릭하면 바로 캡쳐가 된다. 심지어 결과도 깔끔하고 화질도 나쁘지 않다. 그러면 뭐하나.. 자동이 아니면 매번 이 작업을 반복해야 한다.

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
 
chrome_options = Options()
driver = webdriver.Chrome(options=chrome_options)
 
element = driver.find_element_by_id('specs-list')
element.screenshot('screenshot.png')
 
element_png = element.screenshot_as_png
with open('screenshot.png'"wb"as file:
    file.write(element_png)
cs

다행히 셀레니움 기능중에 특정 element 값만 캡쳐하는 기능이 있었다. screenshot으로 저장하는 기능과 screenshot_as_png로 저장하는 기능인데 차이점은 거의 없는 것 같다. 이렇게 수월하게 되면 개발이 재미가 없지...

 

해결해야할 문제들

- 특정부분이 화면에 나오지 않으면 짤려서 찍혔다. 

- 크롭옵션에 따라 캡쳐가 안 되기도 했다.

- 듀얼모니터에서 실행이 안 됐다.

- 화면 축소시 찍히지 않았다.

 

 

최대한 덜 잘려나오게 하려면 셀레니움이 노트북 화면이 아닌, 듀얼 모니터 화면에서 실행 되어야 하는데 안 됐다. 좌표값을 옮겨서 확대를 하라고 해서 여러 방법 끝에 됐다. 그리고 headless의 경우 어떤 화면에서는 잘 캡쳐가 됐지만, 안 되는 경우도 있어서 뺐다. 

 

 

 

1
2
3
4
5
6
7
8
9
10
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
 
chrome_options = Options()
 
# chrome_options.headless = True #싸이트마다 다르게 캡쳐가 됨
# chrome_options.add_argument("--kiosk") #F11 눌러진 효과
chrome_options.add_argument("--window-position=2000,0"#듀얼모니터에서 보기
driver = webdriver.Chrome(options=chrome_options)
driver.maximize_window() 
cs

듀얼 모니터 화면에서 캡쳐가 됐다!! 하지만 화면이 잘리지 않으려면 화면을 축소해서 찍어야 했다. 사람들마다 셀레니움 화면을 축소하는 다양한 방법을 소개시켜줬는데 잘 안 됐다. 기본 설정에 들어가서 바꾸라는 사람부터 zoom out을 하라는 것도 있었지만, 값을 바꾸게 되면 css에도 영향을 미치는 것 같았다.

 

 

 

1
2
3
4
5
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
 
# driver.execute_script("document.body.style.zoom='50%'")
driver.execute_script("document.body.style.transform = 'scale(0.50)'")
cs

document.body.style.zoom을 활용하게 되면 안 된다. document.body.style.transform을 사용해야 한다. 원하는 요소가 한 페이지에 나오면 좋겠지만, 아닌 경우도 있으니 쓰시면 될 것 같습니다. 문제는 어느정도 해결한 듯 보였으나 원하는 이미지가 서로 다른 element에 있어서 이미지 병합까지 하게 됐다. 

 

밑에 과정은 gsmarena에서 기본적인 정보를 크롤링하고 이미지 캡쳐, 병합하는 과정입니다.

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
from bs4 import BeautifulSoup
import pandas as pd
import requests
import re
 
#환율
def exchange_rate(today_rate, price,r) : #r은 반올림 자리 숫자
    p_price = round(today_rate * int(price),r)
    return str(p_price)
 
#월
def month_string_to_number(string):
    m = { 'jan'1'feb'2'mar'3'apr':4,  'may':5,  'jun':6,  'jul':7,  'aug':8,  'sep':9,  'oct':10,  'nov':11,  'dec':12 }
    s = string.strip()[:3].lower()    
    try:
        month = m[s]
        return month
    except:
        return '확인 바람'
 
#이름
def crawl_gsmarena(j,today_rate1,today_rate2) :
    
    global phone_name
    url = j
    
    headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36"}
    res = requests.get(url, headers=headers)
    soup = BeautifulSoup(res.text, "html.parser"
 
    tds= soup.find_all('td', attrs={'class''nfo'})
    
    try :    
        phone_name = soup.select('h1.specs-phone-name-title')[0].text
        
    except :
        phone_name = 0
        return phone_name
 
    for td in tds:
        
        global s_year, s_mon, phone_price, exchange
        
        if 'status' in str(td) :
 
        # <td class="nfo" data-spec="status">Available. Released 2021, August 27</td>
            try :
                td = td.text
                s_year = re.findall('\d+', td)[0]
                s_mon = td.split(',')[1]
                s_mon = re.sub('[^a-zA-Z]''', s_mon)
                s_mon = month_string_to_number(s_mon)
                
            except :
                s_year = 'comming_soon'
                s_mon = 'comming_soon'
                
        elif 'price' in str(td) :
            td = td.text 
            # p = td.split()[1]
            
            if '$' in td :
                p = td.split('$')[1].split()[0]
                phone_price = p
                exchange = ''
                    
            elif '€' in td :
                p = td.split('€')[1]
                p = p.split()[0].replace(',','').split('.')[0]
                phone_price = exchange_rate(today_rate1, p,2)  # today_rate 환율 입력
                exchange = today_rate1
                
            elif '₹' in td :
                p = td.split('₹')[1]
                p = p.split()[0].replace(',','').split('.')[0]
                phone_price = exchange_rate(today_rate2, p,3)  # today_rate 환율 입력
                exchange = today_rate2
                            
            elif 'EUR' in td :
                p = td.replace(',','')
                p = re.findall('\d+', p)[0]
                phone_price = exchange_rate(today_rate1, p,2)
                exchange = today_rate1    
 
            else :
                phone_price = td
                exchange = ''
                
    return phone_name, s_year, s_mon, phone_price, exchange
cs

핸드폰 이름, 생산 년도, 월, 핸드폰 가격 등을 가져옵니다.

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import requests
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time
import os 
import glob
from PIL import Image
import glob
import shutil
 
#이미지 병합
def merge_img(name):
    files = glob.glob('crawling_temp/*.png')
 
    full_width, full_height = 3640 #364로 고정 / 너비가 2배로 찍혀서 반은 없애야 함
 
    for f in files :
        image = Image.open(f)
        _, height = image.size
 
        # full_width = max(full_width,width)
        full_height += height
 
 
    canvas = Image.new('RGB', (full_width, full_height), 'white')
    output_height = 0
    
    for i in files:
        with Image.open(i) as image :
            _, height = image.size
            canvas.paste(image, (0, output_height))
            output_height += height
 
    canvas.save(f'crawling/{name}.png')
    
    shutil.rmtree('crawling_temp')
    
    return 
 
#이미지 리사이징
def resize_img():
    im = Image.open(f'crawling_temp/a.png')
    half = 0.5
    out = im.resize( [int(half * s) for s in im.size] )
    out.save(f'crawling_temp/a.png')
    im.close()
    return 
 
chrome_options = Options()
chrome_options.add_argument('--profile-directory=Default')
chrome_options.add_argument("--incognito")
chrome_options.add_argument("--disable-plugins-discovery")
chrome_options.add_argument("--window-position=2000,0"#듀얼모니터에서 보기
 
driver = webdriver.Chrome(options=chrome_options)
driver.maximize_window() 
 
 
    
urls = 'https://www.gsmarena.com/samsung_galaxy_s21_fe_5g-10954.php' # 찾고자 하는 url 리스트
names = 'samsung galaxy s21' # 검색키워드 리스트
 
for i in range(len(urls)) :
    url = urls[i]
    driver.get(url)
    
    element = driver.find_element_by_class_name("article-info")
 
    if os.path.exists('crawling_temp') :
        pass
    else :
        os.mkdir('crawling_temp')
    
    element.screenshot(f'crawling_temp/a.png')
    resize_img()
    
    driver.execute_script("document.body.style.transform = 'scale(0.50)'"#축소
 
    element2 = driver.find_element_by_id('specs-list')
    element2.screenshot(f'crawling_temp/b.png')
 
    name = names[i]
    if '/' in name :
        name = name.replace('/','')
    merge_img(name)
    
    time.sleep(2# 빠르면 봇으로 인식
    
# 이미 사용중이라고 에러가 뜨면 vs코드 껐다가 다시 실행
print('완료')
    
cs

이미지 병합, 리사이징까지의 과정이 있습니다. 화면을 축소하니까 사진을 잘못 찍는 경우가 있어서, 사진 1장은 원래 크기에서 찍고, 다른 1장은 축소해서 찍었네요. 그리고 리사이징을 해서 병합하는 복잡한 과정을 거쳤습니다. 여러분들이 도전하는 크롤링과 자동 캡쳐 스크린샷 코드는 문제가 없길 바랍니다 ㅠㅠ

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

목차 (작성 예정)

(1) 100% 만족할 파이썬 엑셀 사무 자동화, 회사에서 안 된다면?

(2) 엑셀 보안 한 방에 뚫기

(3) 시간 50배 단축, 실무 엑셀 함수 구현 (vlookup, index match 등)

(4) 실무 엑셀 함수 응용

(5) 엑셀 실무용 유용한 함수 및 기능들 파이썬으로 해결

(6) 언제까지 수작업할래? 크롤링과 사진 자동으로 캡쳐, 스샷


https://0goodmorning.tistory.com/72

https://0goodmorning.tistory.com/73

 

지난번 글에서는 실무에서 무조건 쓰는 VLOOKUP, INDEX/MATCH 함수를 파이썬으로 구현하는 방법에 대해서 소개해드렸습니다.  몇 천개가 넘는 데이터를 INDEX/MATCH를 사용하여 여러 조건을 따졌을 때, 밑에 프로세스 계산중이라고 뜨면서 엑셀이 멈추거나 버벅거렸는데 파이썬으로는 30초 안에 해결된 거 같네요.

 

 

엑셀 함수 자동화 기능들

- 띄어쓰기 다른 문자열 비교 (조건 찾기 개선)

- 포함 여부 찾기

- 필터링 방법 (있을 때, 없을 때)

- 인덱스 찾기

- 모든 제품 소문자/ 대문자 바꾸기

- 찾는 조건 값 한 번에 바꾸기

- 여러 시트 합치기

 

 

 

띄어쓰기가 다른 문자열

VLOOKUP 기능을 사용하면서 느꼈던 점은 제품의 경우 대소문자가 달라도 같은 제품으로 인식하는데, 띄어쓰기나 문자가 조금만 다르면 조건에 걸리지 않은 치명적인 단점이 있었습니다.

 

예를 들어 'GALAXY Z FLIP3' = 'galaxy z flip3' 는 조건에서 인식을 하는데 'GALAXY ZFLIP3'나 'GALAXYZFLIP3'는 조건에 해당하지 않아서 #N/A 값이 나오는 황당한 케이스가 너무 많았습니다. 이는 파이썬에서 금방 해결이 가능합니다.

 

 

 

1
2
3
4
5
6
7
8
9
10
11
import pandas as pd
 
target = 'GALAXY Z FLIP3'
data = ['galaxy z flip3','GALAXY ZFLIP3''GALAXYZFLIP3']
 
for i in data : 
    if target == i :
        print(f'{target}은 {i}과 같습니다.')
    
    else :
        print(f'{target}은 {i}과 다릅니다.')
cs

파이썬에서는 기본적으로 대소문자를 다르게 인식하고 있어서, 엑셀에서는 검색이 됐던 소문자도 다른 것으로 인식이 되고 있습니다. 해결방법은 간단합니다. 두 문자를 모두 소문자로 인식시키고, 공백을 제외해서 비교하면 됩니다.

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
import pandas as pd
 
target = 'GALAXY Z FLIP3'
data = ['galaxy z flip3','GALAXY ZFLIP3''GALAXYZFLIP3']
 
for i in data : 
    target_ = target.lower().replace(' ','')
    i_ = i.lower().replace(' ','')
    if target_ == i_ :
        print(f'{target}은 {i}과 같습니다.')
    
    else :
        print(f'{target}은 {i}과 다릅니다.')
cs

단어를 .lower().replace(' ', '')을 사용하시면 간단하게 해결이 됩니다. 여기서 중요한 것은 변수 자체를 바꾸는 것이 아닌, 비교할 때만 소문자로 만들고 공백(띄어쓰기)를 임시로 제거해야 합니다. 본래 값을 바꾸게 되면, 나중에 입력하는 값에 수정된 값이 입력될 수 있습니다. 예를 들어 'galaxy z flip3'가 아닌 'galaxyzflip3'이 입력이 됩니다.

 

 

 

포함 여부 (문자열, 숫자) / 숫자만 뽑기

1
2
3
4
5
6
7
import pandas as pd
 
data = ['galaxy z flip3','GALAXY ZFLIP3''GALAXYZFLIP3']
 
for i in data : 
    if 'GALAXY' in i :
        print(f'GALAXY가 {i} 제품명에 포함되어 있습니다')
cs

'galaxy z flip3'에 'GALAXY'가 포함되어 있는지 여부를 알려면 어떻게 해야할까요? 위에 예시와는 반대로 .lower()가 아닌 소문자를 대문자로 바꿔주는 .upper()를 사용하시면 됩니다. 

 

 

 

1
2
3
4
5
6
7
8
9
def contain_num(s):
    return any(i.isdigit() for i in s)
 
 
data = ['galaxy z flip','GALAXY ZFLIP2''GALAXYZFLIP3']
 
for i in data : 
    if contain_num(i) :
        print(f'{i} 제품명에 숫자가 포함되어 있습니다')
cs

contain_num 함수는 문자열을 하나하나 분리해서 그 안에 숫자가 있는지 확인을 합니다. isdigit()이 숫자면 True, 아니면 False를 반환 하게 됩니다. 그래서 숫자가 포함이 되면 return 값이 True가 되기 때문에, if contain_num(i)를 실행하게 됩니다. 여기서 궁금한 것이 그렇다면 숫자만 찾고고 싶을 수도 있어서 추가로 팁을 드리겠습니다.

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
import re
 
def contain_num(s):
    return any(i.isdigit() for i in s)
 
data = ['galaxy z flip','GALAXY ZFLIP2''GALAXYZFLIP3']
 
for i in data : 
    if contain_num(i) :
        number = re.findall(r'\d+', i)[0]
        print(f'{i}는, 갤럭시 Z플립{number} 시리즈 입니다.')
cs

이어져 있는 숫자를 다 가져오게 됩니다. 

 

 

 

re.findall(r'\d+', string)는 string에서 이어진 숫자를 찾아줘서 list를 만들어줍니다. 만약에 여러 숫자를 포함하고 있다면, [0]이 아닌 원하는 숫자가 포함된 인덱스를 사용하시면 됩니다.

 

 

 

엑셀 필터링 기능 - 필요한 데이터 추출, 포함 할 때 & 포함 안 할 때

코드 설명을 하고 밑에 한 번에 코드를 붙여놓을게요. 이러한 데이터가 있을 때 삼성 데이터만 보고싶다면 어떻게 해야할까요?

 

 

 

지난번 글에서 알려드렸던 것처럼 VLOOKUP 함수에서의 조건을 사용하시면 됩니다. phone[phone['Company'] ==  'Samsung'] 굳이 분리를 안 하셔도 되는데 이해하기 쉽게 사진을 찍었습니다.

 

 

 

찾아야할 조건이 여러개가 된다면 리스트를 만들어주고 isin 을 사용하시면 됩니다. 포함 유무를 확인하여 뽑아줍니다.

 

 

 

 

만약에 조건을 포함하지 않는 것을 찾는다면 어떻게 해야할까요? 방법은 의의로 간단합니다. df.loc[~조건 ] 조건 안에 물결 표시 '~'를 추가하면 됩니다. 

 

 

 

 

이 방법 또한 문자열을 포함하면 필터링을 해주는 기능인데, 위 방법과 무슨 차이가 있을 까요? 자세히 보시면 회사명이 정확히 apple과 samsung이 아닌데도 필터링이 정확하게 됐습니다. str.contains()는 부분 포함을 해도 찾게 됩니다.

 

 

 

 

인덱스를 구하는 것은 더 쉽습니다. 위에 함수에서 df.loc[조건]을 하지 않고 df[조건].index를 하시면 간단합니다.

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import pandas as pd 
 
#데이터 읽기
phone = pd.read_clipboard(keep_default_na=False, sep="\t")
 
#원하는 데이터 필터링
condition = ( phone['Company'== 'Samsung' )
samsung = phone[condition]
 
#여러 데이터 필터링
company_list = ['Apple','Samsung']
company_condition = phone['Company'].isin(company_list)
company = phone.loc[company_condition]
 
#원하는 데이터 포함하지 않는 값 필터링
company_list = ['Apple','Samsung']
company_condition = phone['Company'].isin(company_list)
company = phone.loc[~company_condition]
 
#문자열 포함 데이터 필터링
company_list = ['App','Sam']
condition2 = '|'.join(company_list)
company_condition = phone['Company'].str.contains(condition2,na=False)
company = phone.loc[company_condition]
 
#인덱스 구하기
company_condition = phone['Company'].str.contains('Apple',na=False)
company_index = phone[company_condition].index
cs

 

 

 

모든 데이터 변경 대문자 / 소문자

 

for 문을 돌릴 필요 없이 원하는 컬럼에 str.lower()를 사용하면 모든 제품이 소문자로 변경된다. 반대는 str.upper() 사용!

 

 

 

 

 

특정 데이터 한 번에 변경

갤럭시 Z플립3 5G만 5만원씩 할인을 한다고 했을 때, 'df.loc[조건, 열] = 원하는 값' 을 입력해주시면 됩니다.

 

 

 

 

여러 데이터 합치기 

 

위와 같이 phone과 phone_price라는 시트가 있습니다. phone 시트에서 Product와 Price 열만 가지고 오려면 어떻게 할까요?

 

 

 

pd.concat([기존 시트, 추가 시트1, 추가 시트2], join = 'inner) concat과 inner join을 사용하시면 기존 컬럼을 유지하게 됩니다.

 

 

 

append 사용하기. 앞으로 관리를 할 때 Company와 Review 열을 추가로 가져오고 싶으면, 열에 맞게 값이 추가가 됩니다. 기존 시트에 열이 없으면 새로 가져오는 시트에서 열을 가져오게 됩니다. 그래서 기존 시트는 Company와 Review 열에 NaN 값이 들어가게 됩니다.

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
import pandas as pd 
 
#기존 시트
phone_price = pd.read_clipboard(keep_default_na=False, sep="\t")
 
#새로운 시트
phone = pd.read_clipboard(keep_default_na=False, sep="\t")
 
#기존 시트의 열만 참조
phone_update = pd.concat([phone_price, phone], join='inner')
 
#새로운 시트의 열까지 참조
phone_new = phone_price.append(phone, sort=False)
cs

이외에도 중복 제거 drop_duplicates(), NaN 값 제거 dropna() 등 여러가지 함수들이 있는데, 이 부분은 찾으면 금방 나와서, 실무에서 자주 사용했던 기능들만 올렸습니다. 혹시 더 간단한 방법 있으면 공유해주시면 감사하겠습니다 ^^

 

 

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

목차 (작성 예정)

(1) 100% 만족할 파이썬 엑셀 사무 자동화, 회사에서 안 된다면?

(2) 엑셀 보안 한 방에 뚫기

(3) 시간 50배 단축, 실무 엑셀 함수 구현 (vlookup, index match 등)

(4) 실무 엑셀 함수 응용

(5) 엑셀 실무용 유용한 함수 및 기능들 파이썬으로 해결

(6) 언제까지 수작업할래? 크롤링과 사진 자동으로 캡쳐, 스샷


https://0goodmorning.tistory.com/72

지난 편에 이어서 작성하겠습니다. 실무에서 자주 활용되는 엑셀 함수 VLOOKUP, INDEX/MATCH 기본적인 구현 방법에 대해서는 전 글을 확인해주세요^^

 

 

 

1
2
3
4
5
6
7
8
9
10
11
import pandas as pd     # pandas가 없으면 터미널에서 pip install pandas
 
# df =  pd.read_csv('위치/파일명.csv', encoding='utf-8-sig') # 직접 불러오기
df =  pd.read_clipboard(keep_default_na=False, sep="\t"# 복사해서 불러오기
 
for i in range(4) : #시트를 분리하지 않았기 때문에 매체 4개 값만
    col_A = df['매체'][i]
    col_D = df['노출'][i]
    
    col_E = df[ (df['매체명'== col_A) & (df['노출수'== col_D) ]['가격'].values[0]
    df['비용'][i] = col_E
cs

저번처럼 모듈을 불러오고 반복문을 사용합니다. 지난번에는 facebook, exposure라고 변수명을 지정했는데, 이번에는 간편하게 col_A, col_D 라고 지정하겠습니다. 지난 내용을 단지 for 문 안에 집어넣은 것이기 때문에 어려움은 없지만, 파이썬을 아예 처음 접하신 분들을 위해서 잠깐 설명을 하자면

 

 

 

값이 4개 밖에 되지 않아서 직접 쳐도 된다고 생각하지만, 저희가 다루는 데이터 양이 많습니다. 그래서 이를 반복문으로 돌리는 것입니다. for문의 기본 구조를 보자면,

 

for [변수] in [문자열, 리스트, 튜플] :

    [수행부분]

 

이런 식으로 구성이 되어 있는데, [문자열, 리스트, 튜플] 등을 순서대로 돌면서 [변수]에 넣어주게 됩니다. range(4)를 할 경우에 [변수]에 0,1,2,3 까지를 넣어주게 됩니다. range의 경우 0부터 시작해서 range 안에 숫자-1 까지의 숫자를 사용합니다.

 

col_A = df['매체'][i] 는 for문을 돌면서 변수가 0 일 때 페이스북, 1 일 때 구글, 2 일 때 네이버... 이런 식으로 변경이 되는 것입니다. 몇 번 사용하시다 보면 자연스레 익히게 됩니다. 어떤 식으로 돌아가는지만 생각하시면 될 것 같네요.

 

 

 

만약 페이스북의 노출이 400이 되면 어떻게 될까요? 엑셀에서도 #N/A 값이라고 하면서 오류가 뜹니다. 파이썬에서도 위와 같은 코드로 진행할 경우 마찬가지로 오류가 뜨게 됩니다.

 

 

 

이렇게 오류가 뜨게 되는 이유는 매체명이 페이스북이면서, 노출수가 400인 케이스가 존재하지 않기 때문입니다. 그런데 .values[0]으로 값을 가져오라고 해서 index 0 is out of bounds가 뜨게 되는 것입니다. 이 오류를 해결하기 위해서는 간단하게 한 줄만 추가하면 됩니다. 

 

 

 

1
2
3
4
5
6
7
for i in range(4) : #시트를 분리하지 않았기 때문에 매체 4개 값만
    col_A = df['매체'][i]
    col_D = df['노출'][i]  
    col_E = df[ (df['매체명'== col_A) & (df['노출수'== int(col_D)) ]['가격']
    
    if len(col_E) : #존재하는지 확인
        df['비용'][i] = col_E.values[0]
cs

len() 함수는 문자열의 길이를 구하는 함수인데, 아무것도 존재하지 않을 때 0 이 된다. 그리고 파이썬에서 0은 False로 인식하기 때문에, len(col_E)가 0이 되면 if 문이 실행이 되지 않는다. 그렇기 때문에 페이스북의 비용은 빈칸이 된 것이다. 그렇다면 페이스북 비용에 다른 값을 넣고 싶다면 어떻게 해야할까?

 

 

 

1
2
3
4
5
6
7
8
9
10
for i in range(4) : #시트를 분리하지 않았기 때문에 매체 4개 값만
    col_A = df['매체'][i]
    col_D = df['노출'][i]  
    col_E = df[ (df['매체명'== col_A) & (df['노출수'== col_D) ]['가격']
    
    if len(col_E) :
        df['비용'][i] = col_E.values[0]
    
    else : 
        df['비용'][i] = '비용 확인바람'
cs

else 구문을 활용하여 쉽게 자신이 원하는 문구를 집어넣을 수 있다. 파이썬을 많이 이용하신 분들에게는 완전 쌩기초기 때문에 그냥 웃으면서 넘기시면 됩니다. 이제 어느정도 활용을 해봤으니, 다른 시트에서 값을 가져오는 방법을 소개하고자 합니다.

 

 

 

파일을 불러오는 방식은 처음과 동일합니다. 다만 차이는 데이터를 한 번에 불러오지 않고, 나눠서 불러오는 것입니다. df를 쓰려다가 이해하기 쉽게 sheet1과 sheet2로 데이터를 읽었습니다. 

 

 

 

1
2
3
4
5
6
7
8
9
10
for i in range(len(sheet1)) : #시트를 분리했기 때문에 sheet1의 길이만큼 len()
    col_A = sheet1['매체'][i]
    col_D = sheet1['노출'][i]  
    col_E = sheet2[ (sheet2['매체명'== col_A) & (sheet2['노출수'== int(col_D)) ]['가격']
    
    if len(col_E) :
        sheet1['비용'][i] = col_E.values[0]
    
    else : 
        sheet1['비용'][i] = '비용 확인바람'
cs

이전 코드와 달라진 부분이 뭘까요?

 

df에서 sheet1으로 바뀐 부분과 어떤건 sheet2로 바뀌었습니다. 차이가 무엇일까 살펴보면, 우리가 비교하려는 값은 sheet1에서 뽑아줍니다. 그리고 비교하려는 값으로 얻는 것은 sheet2에서 찾기 때문에 col_E (원하는 것 = 원하는 매체 노출수에 따른 '가격')는 sheet2에서 조건을 걸게 된 것입니다.

 

그리고 우리는 sheet1 '비용' 컬럼을 채워야하기 때문에 sheet1['비용'][i]에 sheet2에서 받아온 '가격'을 입력하게 됩니다. 애초에 두 시트의 컬럼명이 '가격'이었으면 덜 헷갈렸을 수도 있는데, 어떤 부분을 체크해야하는지 알려드리기 위해서 일부러 다르게 사용했습니다. 코드는 작업하기 편하게 사용하시면 됩니다.

 

데이터가 몇개 되지 않으면 INDEX/MATCH 함수나 VLOOKUP 함수를 엑셀에서 돌려도 시간이 얼마 안 걸립니다. 하지만 데이터가 많아지면 많아질수록 웬만한 사무용 컴퓨터에서는 엑셀이 버벅입니다. 조금이라도 도움이 되셨으면 좋겠고 다음에는 엑셀을 파이썬으로 구현할 때 사용했던 기능들에 대해서 소개하겠습니다.

 

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

목차 (작성 예정)

(1) 100% 만족할 파이썬 엑셀 사무 자동화, 회사에서 안 된다면?

(2) 엑셀 보안 한 방에 뚫기

(3) 시간 50배 단축, 실무 엑셀 함수 구현 (vlookup, index match 등)

(4) 실무 엑셀 함수 응용

(5) 엑셀 실무용 유용한 함수 및 기능들 파이썬으로 해결

(6) 언제까지 수작업할래? 크롤링과 사진 자동으로 캡쳐, 스샷

 


회사내 보안이 심하지 않으면 목차 1, 2번은 아마 안 보셨을거라 생각합니다. (운이 좋은 경우) 3편에서는 실무에서 TOP3 안에 드는 엑셀 함수 VLOOKUP 사용법, 유용한 함수들을 파이썬으로 구현하는 방법에 대해서 소개하고자 합니다. 엑셀을 제대로 사용해보지 않은 신입들도 브이룩업?이라고 많이들 들어보셨을텐데 실무에서 엄청 사용하는 것 같습니다.

 

솔직히 제목 어그로를 어떻게 끌까 하다가, 파이썬 엑셀 사무 자동화라는 타이틀은 빼버리고 필요한 키워드들만 넣었습니다. 단순 조회수를 위해서가 아닌, 저처럼 효율충들을 위한 글입니다. 한 번 사용해보세요 제발...

 

 

 

네이버에서 이런 캐릭터와 함께 오늘은 ~하는 방법 알아볼까요? 하고 특별한 정보도 없이, '그냥 그렇다고 하네요~' 로 끝나는 글들을 혐오해서 유용하지 않으면 욕하셔도 됩니다. 저도 사수 분이 엑셀을 VLOOKUP 사용법과 데이터 전처리하는 방법들을 알려주셨는데, 수작업이 너무 심하고... 데이터가 많다 보니 VLOOKUP이나 다중 조건 함수를 처리하는데 시간이 너무 오래 걸렸습니다. 결국 답답해서 파이썬으로 해결했습니다.

 

서론이 길었는데 엑셀에서 함수를 처리하는데 멀뚱멀뚱할 동안, 파이썬은 작업을 완료할만큼 속도도 빠르고 시간도 단축된다는 것.

 

데이터가 적으면 VLOOKUP 함수를 사용하면 얼마 안 걸린다. 하지만 조건이 하나 둘씩 붙어서 다중 조건이 되고, 데이터가 몇 천개 ~ 몇 만개 정도 되면 시간이 기하급수적으로 늘어난다. 다중 조건 INDEX MATCH 조합을 사용했을 때 컴퓨터가 느려서인지 시간이 정말 오래 걸렸다. 하지만 파이썬에서는 다중 조건을 여러번 사용했음에도 몇분도 안 돼서 작업이 완료 됐다.

 

키워드 : #다중조건, #여러시트, #여러값가져오기

 

 

 

 

 

VLOOKUP

이미 아시겠지만 VLOOKUP은 (비교값, 비교대상 범위, 결과를 가져올 열 번호, 옵션)을 활용하지만, 기준값의 우측 데이터만 가져올 수 있다. 하지만 좌측 데이터를 가져오려면 #N/A가 뜨면서 오류가 난다. 다중조건을 사용하기 쉽지 않아서, 많은 분들이 INDEX MATCH를 사용한다. 다른 시트에서 값을 찾으려면 범위 앞에 시트명!을 붙인다. EX) =VLOOKUP($A2, 시트명!$A:$C, 2, FALSE

 

INDEX MATCH

INDEX 함수를 사용하여 특정 위치에 해당하는 값을 가져오고, MATCH를 사용해서 기준값이 어디에 위치하는지 정보를 얻을 수 있다. 실무에서 이 두 함수를 조합하는 것으로 보인다. 대부분 옵션은 0이나 FALSE가 들어가는데 정확히 일치한다는 뜻으로 쓰인다. 

 

-INDEX (검색범위, 행번호, 열번호)

-MATCH (찾는 값, 검색범위, 옵션) 

-MATCH (1, (첫번째 조건)*(두번째 조건), 옵션)   

ㄴ 조건이 충족하면 1(TRUE) 아니면 0(FALSE), 곱해서 1이 되는 조건을 찾는다로 이해하시면 됩니다

 


 

*파이썬으로 진행되기 때문에 파이썬(3.7기준), 주피터노트북, pandas 모듈 필수 설치 (csv로 진행)

 

이걸 어떻게 설명을 할까 생각하다가 직접 보여주는게 좋을거 같아서, 임시로 데이터를 만들었습니다. 오해는 말아주세요. 그리고  파이썬에서 VLOOKUP과 INDEX MATCH 사용법이 비슷해서 INDEX MATCH 기준으로 설명드리겠습니다. 

매체별 노출수에 따른 비용을 구하려고 할 때 엑셀 함수로는 =INDEX($J:$J,MATCH(1,($H:$H=$A2)*($I:$I=D2),0),1) 이런 식으로 구하면 별 문제 없이 5000, 7000, 30000, 11000이 입력이 될 것이다. 그렇다면 이것을 어떻게 파이썬으로 구현할까?

 

 

 

1. 파일 불러오기

1
2
3
4
import pandas as pd     # pandas가 없으면 터미널에서 pip install pandas
 
df =  pd.read_csv('위치/파일명.csv', encoding='utf-8-sig'# 직접 불러오기
df =  pd.read_clipboard(keep_default_na=False, sep="\t"# 복사해서 불러오기
cs

pandas 모듈을 불러옵니다. 직접 파일의 위치를 찾아서 불러와도 되고, 편하게 원하는 부분만 드래그 후 복사하는 두번째 방법대로 하시면 됩니다. (첫번째 행이 컬럼명이 됩니다 / df는 DataFrame의 약자, 다른 값으로 해도 됩니다)

 

 

 

엑셀 시트를 나눠서 '매체~비용' / '매체명~가격'으로 하셔도 되는데 우선은 시트를 나누지 않고 설명드리겠습니다.

 

 

 

2. 원하는 부분 가져오기

1
2
3
4
facebook = df['매체'][0]
exposure = df['노출'][0]
facebook, exposure
df[ (df['매체명'] == facebook) & (df['노출수'] == exposure) ]['가격'].values[0]
cs

 

=INDEX($J:$J,MATCH(1,($H:$H=$A2)*($I:$I=D2),0),1)

 

매칭을 시키자면, 우리가 필요로하는 A2의 값은 '매체명'컬럼에 0번째 인덱스인 '페이스북' facebook/ D2의 값은 '노출수'컬럼에 0번째 인덱스인 '100'exposure라고 칭하겠다는 소리입니다. 

 

안쪽에 있는 df들은 MATCH의 조건들이라고 생각하시면 될 것 같고, ['가격']은 $J :$J라고 생각하시면 됩니다. df[( 조건1 ) & ( 조건2 ) ][ $J:$J ]으로 이해하면 편할 듯합니다. MATCH에서 $H:$H=$A2 이 부분은 df['매체명'] == facebook 입니다. H컬럼('매체명')에서 facebook인 애를 찾아줘. $I:$I=D2 이 부분은 df['매체명'] == exposure를 뜻합니다. I컬럼('노출수')에서 exposure를 찾아줘.

 

그리고 마지막 '.values[0]'은 구한 값을 가져오는 것입니다. (중복값 없이 값이 유일할 때 첫번째 값)

 

 

 

어느정도 이해가 되시나요? 페이스북 체류시간을 구하는 VLOOKUP 함수를 구현한다고 하면 어떻게 하면 될까요? 

 

df[ df['이름'] ==  facebook ]['체류 시간']      =>      df [  ] 안에 조건을 하나만 넣으면 됩니다. 

 

 

 

 

3. 값 대입하기

1
2
facebook_price = df[ (df['매체명'] == facebook) & (df['노출수'] == exposure) ]['가격'].values[0]
df['비용'][0] = facebook_price
cs

 

방금 구한 값을 facebook_price라고 칭하고, '비용' 컬럼 0번째 인덱스에 집어넣겠다는 것입니다. 그런데 facebook_price 끝 부분 '.vlaues[0]'은 유의하셔서 보셔야 합니다. 만약에 매체가 페이스북이고 노출 400인 값이 들어왔을 때 비용을 구할 수 없기 때문에 #N/A 값이 생기는 것처럼 파이썬에서도 에러가 납니다.  

 

그렇다면 이 에러를 어떻게 잡을 수 있을까요?

 

다음 편에 이어서 작성하겠습니다. 원래 여러 내용을 같이 작성하려고 하였으나, 이 게시물을 읽으실 분들은 아직 파이썬이 능숙하지 않으실 것 같아서 우선 위에 내용부터 이해하면 좋겠네요! 

 


 

정리

VLOOKUP => df [ df['열 이름'] == A2] ['구하고자 하는 값이 있는 열 이름'].values[0]

INDEX/MATCH = >  df [ ( df['열 이름1'] == A2 ) & ( df['열 이름2'] == B2 ) ] ['구하고자 하는 값이 있는 열 이름'].values[0]

조건이 추가 되면 df [  (기존 조건)  &  df['열 이름3'] == C3   ] 안에 &와 함께 조건을 추가하면 됩니다.

 

 

다음 글

- 반복해서 값을 적용하는 방법 (이걸 일일이 다 칠 수 없으니...)

- #N/A 값이 나올 때 예외 처리

- 다른 시트에서 값 확인

 

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

목차 (작성 예정)

(1) 100% 만족할 파이썬 엑셀 사무 자동화, 회사에서 안 된다면?

(2) 엑셀 보안 한 방에 뚫기

(3) 시간 50배 단축, 실무 엑셀 함수 구현 (vlookup, index match 등)

(4) 실무 엑셀 함수 응용

(5) 엑셀 실무용 유용한 함수 및 기능들 파이썬으로 해결

(6) 언제까지 수작업할래? 크롤링과 사진 자동으로 캡쳐, 스샷

 


1
2
import pandas as pd
df = pd.read_csv('111.csv')
cs

전 글에서 pip install 문제 때문에 ipykernel이 설치가 안 돼서 print(1)도 되지 않았다. 이후 문제를 해결하고 1이 출력 됐을 때의 기쁨은 엄청났다. 하지만 그 기쁨도 잠시뿐. 행복한 마음으로 csv 데이터 파일을 열었더니 오류가 이렇게나 길게 나왔다.

 

 

 

문제는 UnicodeDecodeError: 'utf-8' codec can't decode byte 0x9b in position 32: invalid start byte 라고 한다.  codec 문제라고 하는데, 예전에도 이런 문제와 부딪혀 봐서 금방 해결할거라 생각했다. 

 

 

 

encodig = 'utf-8' / 'euc-kr' / 'cp949' / 'utf-8-sig' 이 중에 한 놈이라도 걸려라 했는데, 한 명도 걸리지 않았다. 그 어떤 것도 되지 않아서.. csv 파일이 문제라고 생각해서 xlsx 확장자를 열어봤는데

 

 

 

 

ValueError: Excel file format cannot be determined, you must specify an engine manually. 라는 에러 메시지가 출력됐다. 그래서 또 열심히 구글링을 하기 시작했는데 도움이 되지 않았다. 그래도 참고하실 분은 봐주세요.

 

 

 

 

 

 

 

PANDAS & glob - Excel file format cannot be determined, you must specify an engine manually

I am not sure why I am getting this error although sometimes my code works fine! Excel file format cannot be determined, you must specify an engine manually. Here below is my code with steps: 1- li...

stackoverflow.com

 

How to read SharePoint Online (Office365) Excel files in Python with Work or School Account?

I am a university student and I have registered as an Office 365 Education user via my university Email address. I usually log into https://www.office.com with my Email account: alice@abc.edu. The ...

stackoverflow.com

열심히 찾아봤는데 해결책은 의외로 간단했다.

 

openpyxl가 아닌,  xlwings을 설치하면 된다.

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import openpyxl
wb = openpyxl.load_workbook('파일명')
 
#sheet 열기
sheet = wb['sheet1'
 
#workbook 생성
wb = openpyxl.Workbook() # 기본 시트 생성됨 sheet1
sheet2 = wb.create_sheet('sheet2'#마지막에 추가
sheet3 = wb.create_sheet('sheet3'1#sheet1 자리에 삽입 하여 추가
 
#시트 이름 변경
sheet2.title = '업무자동화'
 
#저장
wb.save('./new_test_file.xlsx')
wb.close()
cs

openpyxl 같은 경우 시트별로 불러와서 작업이 가능하지만, 애초에 회사 사내망 때문에 보안이 걸린 엑셀의 경우는 xlwings로만 불러 올 수 있었다. 하지만 xlwings를 사용하는 사람들이 많이 없어서 참고 자료를 찾기 어려워서 다른 방법을 선택했다.

 

 

 

1
2
3
4
import xlwings as xw
book = xw.Book('실습1.csv')
df = book.sheets(1).used_range.options(pd.DataFrame).value
df
cs

자료는 회사와 관련이 없는 자료다. 이런 식으로 작업을 하면 바로 엑셀이 실행돼서 동시 작업이 된다. 엑셀 실행이 돼서 기뻤지만 작업을 하지 못 해서 다른 방법이 뭐가 있을까 찾아봤다.

 

 

 

 

사내망 엑셀파일에 걸린 자물쇠를 뚫고 코딩하기

엑셀 자동화 (1) - xlwings 라이브러리 시작하기

maeng-gun.github.io

그래도 혹시 궁금해하실 분들을 위해서 관련 링크 걸어놓습니다.

 

저는 xlwings 대신에 pd.read_clipboard()를 사용하였습니다. 이게 참 훌륭한 기능이라는 걸 깨달았네요. 그냥 가져오고자 하는 데이터를 드래그해서 복사하시면 dataframe이 만들어집니다. 자동으로 행, 열도 생성되고 이거 아니었으면 자동화는 힘들었을거 같네요 ㅠㅠ

 

그리고 이걸로 긁어와서 만든 파일의 경우 세션(?)이 끊기기 전까지는 csv로 불러서 읽을 수 있습니다! 다만 csv 파일을 열어서 수정을 하게 되면 다시 못 쓴다는 치명적인 단점이... 그래도 수작업을 할 시간에 코드로 자동화를 한다면 훨씬 더 편하니까 이렇게 삥삥 돌아서 갑니다. 

 

다 같이 화이팅 합시다!

 

 

 

 

 

 

 

 

 

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

목차 (작성 예정)

(1) 100% 만족할 파이썬 엑셀 사무 자동화, 회사에서 안 된다면?

(2) 엑셀 보안 한 방에 뚫기

(3) 시간 50배 단축, 실무 엑셀 함수 구현 (vlookup, index match 등)

(4) 실무 엑셀 함수 응용

(5) 엑셀 실무용 유용한 함수 및 기능들 파이썬으로 해결

(6) 언제까지 수작업할래? 크롤링과 사진 자동으로 캡쳐, 스샷


카테고리 분류 작업을 포함한 수작업이  많아서, 파이썬 응용AI 교육과정에서 900시간 배웠던 파이썬을 써보기로 했다. 우선 보안이 훌륭한 회사에서는 사내망에서 접속을 차단한 사이트들이 많다. Visual Studio Code(비쥬얼 스튜디오 코드)를 다른 팀에서 쓰고 있어서 vscode는 괜찮다는 것을 알았다. 그리고 다행히 python.org 는 접속이 가능했지만, 아나콘다(anaconda)는 접속이 차단 됐다. 

 

우선 파이썬을 설치하고 비쥬얼 스튜디오 코드를 설치했다. 확장 기능들을 추천하자면

 

-pyhthon (기본)

-jupyter (기본)

-Korean(사용법) Language Pack for Visual Studio Code (기본)

-Visual Studio IntelliCode (feature 들을 추천해줌)

-Python Indent(들여쓰기 할 때 위치 구분)

-Python Docstring Generator

기타 필요 기능들은 검색

 

 

 

1
2
3
4
5
6
7
WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ConnectTimeoutError(<pip._vendor.urllib3.connection.HTTPSConnection object at 0x0000027DE1695D88>, 'Connection to pypi.org timed out. (connect timeout=15)')'/simple/google-image-download/
WARNING: Retrying (Retry(total=3, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ConnectTimeoutError(<pip._vendor.urllib3.connection.HTTPSConnection object at 0x0000027DE16A65C8>, 'Connection to pypi.org timed out. (connect timeout=15)')'/simple/google-image-download/
WARNING: Retrying (Retry(total=2, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ConnectTimeoutError(<pip._vendor.urllib3.connection.HTTPSConnection object at 0x0000027DE16A6CC8>, 'Connection to pypi.org timed out. (connect timeout=15)')'/simple/google-image-download/
WARNING: Retrying (Retry(total=1, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ConnectTimeoutError(<pip._vendor.urllib3.connection.HTTPSConnection object at 0x0000027DE16AD408>, 'Connection to pypi.org timed out. (connect timeout=15)')'/simple/google-image-download/
WARNING: Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ConnectTimeoutError(<pip._vendor.urllib3.connection.HTTPSConnection object at 0x0000027DE16ADB08>, 'Connection to pypi.org timed out. (connect timeout=15)')'/simple/google-image-download/
ERROR: Could not find a version that satisfies the requirement google-image-download (from versions: none)
ERROR: No matching distribution found for google-image-download
cs

아나콘다로 모듈들을 설치하면 더 편리한데, 그래도 pip install로 설치가 가능하니 문제가 없겠거니 생각했다. 그런데 파이썬을 설치하고 vscode까지 설치를 했는데 ipykernel이 설치가 되지 않았다. juptyer notebook을 활용하려고 했으나 실패했다.

 

이런 느낌의 오류와 함께 ipykernel이 없다는 식의 경고창이 떴다. 그래서 어떤거 문제일까 열심히 찾았는데 답이 없었다.

 

 

 

 

 

회사 프록시 때문에 pip, npm을 통해 제대로 패키지 다운로드가 안 될 때

보통 보안 인프라가 갖추어져 있는 회사에는 회사 프록시를 통해 외부 인터넷에 접속할 수 있습니다. 하지만 이게 종종 문제가 되는 게 pip, npm 같은 패키지 매니져를 통해 해당 라이브러리가 제

xzio.tistory.com

 

Proxy awareness with pip

I have tried to set up a python development environment at my work place to create some basic applications. I can't seem to get pip to work, for example I type in pip install numpy and get the foll...

stackoverflow.com

 

라즈베리파이4 파이썬 3.8 설치와 pip3 install 에러를 해결하기까지

어찌저찌 라즈베이파이4에 설치된 라즈비안 OS에 기본 내장된 파이썬이 아닌 다른 버전의 파이썬을 설치하는데 성공했습니다. 그러나, 파이썬 3.8.7을 설치한 다음 파이썬 코드를 작성하고, 실행

redfox.tistory.com

검색을 해보니 보안 인프라가 갖추어져 있기 때문에, pip 같은 패키지 매니저로 제대로 작동이 안 된다는 느낌? 그래서 프록시 서버를 무조건 신뢰한다는 옵션을 넣어서 설치하면 된다고 하는데.. 만약 위 링크들에서 해결이 되면 다행이고 안 되면 다른 방법을 소개해드리려고 한다.

 

우선 이 과정을 하기 전에 회사 IT팀과 연락을 필수로 해야한다. 원격으로 어떤 문제가 있는지 확인을 해봤는데, 목적지 IP 대역이 너무 많아서 정확한 목적지 IP대역 확인이 불가능하다는 결론이 나왔다. 목적지 IP/port를 알 수 있다면 방화벽 신청을 하면 되지만 이건 불가능했다.

 

그래서 임시방편으로 테더링을 추천해주셔서 그렇게 했다. 안 되면 핫스팟으로 pip install을 해보면 된다. 혼자서 파이썬을 설치하려고 하니.. 기본 구축이 왜 이렇게 어렵고, 에러가 많이 나지? 좌절했는데 알고보니 회사 프록시 서버 문제여서 황당했다. 이후에는 별다른 문제 없이 모듈들이 잘 설치 됐고 작업을 진행할 수 있었다.

 

 

 

 

사내망 막힌 사이트 볼 수 있는(뚫을 수 있는) 팁을 드리자면, 구글에서 사이트 주소 옆에 거꾸로된 삼각형을 클릭하여 저장된 페이지를 클릭한다. velog.io 사이트나 간혹 아예 차단이 된 사이트는 열리지 않는다. 그래도 코드를 혼자 짜다보면 무한 에러가 나기 때문에 구글에서 이렇게라도 검색할 수 있어서 다행이다 ㅠㅠ

 

하지만 더 큰 문제가 있었으니... 파이썬에서 엑셀, csv 파일들을 읽을 수 없었다. 진짜 산 넘어 산

 

다음 편에 계속

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

지금 크롤링을 하고 있어서 시간이 나는 김에 글을 작성합니다. 크롤링도 크롤링이지만 이 데이터를 어떻게 정제할지가 더 고민이네요. 지난 번 글들을 활용해서 작성하오니 본인의 목적에 맞게끔 수정해서 사용하면 됩니다!

 

 

 

 

 

 

유튜브 크롤링(1) - 셀레니움 페이지 자동 번역, api 번역기 없이 가능! (키 입력, 마우스 입력)

유튜브로 새로운 수익모델을 찾기위한 채널 분석을 시도하고 있다. (기존 채널에 영상을 새로 올려야 하는데 요즘 못 올리고 있다 ㅠㅠ) 솔직히 노가다를 해도 되는데 파이썬을 배웠으면 자동화

0goodmorning.tistory.com

기능

- 특정 유튜브 채널에서 동영상 목록의 링크를 가져오기 (채널명, 구독자수)

- 제목, 조회수, 날짜, 좋아요 수, 싫어요 수, 댓글 개수

- 댓글 크롤링 (번역 기능 추가)

- 자동번역 자막 추출

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from selenium import webdriver
from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
 
options = webdriver.ChromeOptions() # 크롬 옵션 객체 생성
user_agent = "Mozilla/5.0 (Windows NT 4.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36 "
options.add_argument('user-agent=' + user_agent)
options.add_argument('headless'# headless 모드 설정
options.add_argument("window-size=1920x1080"# 화면크기(전체화면)
options.add_argument("disable-gpu"
options.add_argument("disable-infobars")
options.add_argument("--disable-extensions")
options.add_argument("--mute-audio"#mute
options.add_argument('--blink-settings=imagesEnabled=false'#브라우저에서 이미지 로딩을 하지 않습니다.
options.add_argument('incognito'#시크릿 모드의 브라우저가 실행됩니다.
options.add_argument("--start-maximized")
 
#1
prefs = {
  "translate_whitelists": {"en":"ko"},
  "translate":{"enabled":"true"}
}
options.add_experimental_option("prefs", prefs)
 
#2
prefs = {
  "translate_whitelists": {"your native language":"ko"},
  "translate":{"enabled":"True"}
}
options.add_experimental_option("prefs", prefs)
 
#3
options.add_experimental_option('prefs', {'intl.accept_languages''ko,ko_kr'})
cs

기본 셀레니움 webdriver 세팅입니다. prefs 기능은 영어를 번역할 때 필요한 기능이라서 끄셔도 상관 없습니다. 그리고 처음에 어떻게 돌아가는지 궁금하시면 # options.add_argument('headless') headless 기능을 꺼주세요.

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import os
import pandas as pd
import winsound
 
ytb = pd.read_csv('youtube_link.csv')
ytb_link = ytb.link.to_list()
 
for i in ytb_link :
    
    driver = webdriver.Chrome('chromedriver.exe', options= options)
    driver.get(i)
 
    # 스크롤 다운
    time.sleep(1.5)
   endkey = 4 # 90~120개 / 늘릴때 마다 30개
    while endkey:
        driver.find_element_by_tag_name('body').send_keys(Keys.END)
        time.sleep(0.3)
        endk -= 1
 
    channel_name = driver.find_element_by_xpath('//*[@id="text-container"]').text
    subscribe = driver.find_element_by_css_selector('#subscriber-count').text
    channel_name = re.sub('[=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…《\》]''', channel_name)
    # print(channel_name,subscribe)
 
    # bs4 실행    
    html = driver.page_source
    soup = BeautifulSoup(html, 'lxml')
 
    video_list0 = soup.find('div', {'id''contents'})
    video_list2 = video_list0.find_all('ytd-grid-video-renderer',{'class':'style-scope ytd-grid-renderer'})
 
    base_url = 'http://www.youtube.com'
    video_url = []
 
    # 반복문을 실행시켜 비디오의 주소를 video_url에 넣는다.
    for i in range(len(video_list2)):
        url = base_url+video_list2[i].find('a',{'id':'thumbnail'})['href']
        video_url.append(url)
 
    driver.quit()    
 
    if subscribe :
        channel = channel_name + ' - ' + subscribe
    else :
        channel = channel_name
        
    
    directory = f'data/{channel}/subtitle'
    if not os.path.exists(directory):
        os.makedirs(directory)
        
    print(channel, len(video_url))
    
    ytb_info(video_url, channel)
    print()
    winsound.PlaySound('sound.wav', winsound.SND_FILENAME)
cs

ytb_link : 본인이 수집하고자하는 채널을 리스트 형식으로 만들어주세요. 저는 csv 파일로 만들어서 컬럼 이름을 'link'로 하여 생성을 했습니다. 

 

channel : 채널 이름으로 폴더를 만들기 때문에, 폴더 이름에 들어가면 오류가 생기는 부호들을 미리 전처리 합니다. subtitle까지 만든 건 미리 자막 파일을 저장할 수 있는 폴더도 같이 만들어놨습니다.

 

# 한 채널이 끝날 때마다 윈도우 플레이사운드로 알려줍니다. 시끄럽다고 생각하시면 끄면 됩니다.

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
import time
 
last_page_height = driver.execute_script("return document.documentElement.scrollHeight")
 
while True:
    driver.execute_script("window.scrollTo(0, document.documentElement.scrollHeight);")
    time.sleep(0.5)
    
    if new_page_height == last_page_height:
        break
    last_page_height = new_page_height
    time.sleep(0.75)
cs

endkey : 본인이 수집하고자 하는 채널의 링크 개수를 결정합니다. 현재 설정으로는 90~120개를 수집합니다. time.sleep(2)으로 설정하시면 180개까지 크롤링을 합니다. endkey 개수를 늘리면 30개씩 추가가 됩니다. 에라 모르겠다하고 모든 링크를 크롤링하시려면 위에 코드를 입력해주세요.

 

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# 정보만 크롤링하고 싶을 때
from bs4 import BeautifulSoup
import pyautogui
import pandas as pd
import re
 
def ytb_info2(video_url,channel) :
    print(f'{channel}',' 크롤링 시작')
    driver = webdriver.Chrome('C:/work/python/Asia_GAN/myproject/youtube/chromedriver.exe', options= options)
 
    #데이터 넣을 리스트
    date_list = []
    title_list = []
    view_list = []
    like_list = []
    dislike_list = []
    comment_list = []
    
    #각 채널별 영상으로 크롤링
    for i in range(len(video_url)):
        start_url = video_url[i]
        print(start_url, end= ' / ')
        driver.get(start_url)
        driver.implicitly_wait(1.5)
                
        body = driver.find_element_by_tag_name('body')
        
        #댓글 null 값 방지 
        num_of_pagedowns = 2
        while num_of_pagedowns:
            body.send_keys(Keys.PAGE_DOWN)
            time.sleep(0.5)
            num_of_pagedowns -= 1
            time.sleep(0.5)
        
        #크롤링 요소    
        try : 
            info = driver.find_element_by_css_selector('.style-scope ytd-video-primary-info-renderer').text.split('\n')
 
            if '인기 급상승 동영상' in info[0] :
                info.pop(0)
            elif '#' in info[0].split(' ')[0] :
                info.pop(0)
        
            title = info[0]
            divide = info[1].replace('조회수 ','').replace(',','').split('회')
            view = divide[0]
            date = divide[1].replace(' ','')
            like = info[2]
            dislike = info[3]    
            
            driver.implicitly_wait(1)  
                  
            try:
                comment = driver.find_element_by_css_selector('#count > yt-formatted-string > span:nth-child(2)').text.replace(',','')
            except:
                comment = '댓글x'
                
            #리스트에 추가
            title_list.append(title)
            view_list.append(view)
            date_list.append(date)
            like_list.append(like)
            dislike_list.append(dislike)
            comment_list.append(comment) 
            
            # 크롤링 정보 저장    
            new_data = {'date':date_list, 'title':title_list, 'view':view_list, 'comment': comment_list, 'like':like_list, 'dislike':dislike_list}
            df = pd.DataFrame(new_data)
            df.to_csv(f'data/{channel}/{channel}.csv', encoding='utf-8-sig')
        except :
            continue
        
        # 확인용
        print(title, view, date, like, dislike, comment)
   
    driver.quit()
cs

자막과 댓글이 필요 없을 경우

제목, 날짜, 조회수, 좋아요 수, 싫어요 수, 댓글 수만 크롤링을 합니다. 정보 양이 많지 않기 때문에 셀레니움만으로도 가능합니다. html_source를 bs4로 넘겼을 때와 비교해도 얼마 차이가 나지 않습니다.

 

# print(title, view, date, like, dislike, comment) 만약 어떤 정보가 나오는지 확인할 필요가 없으시면 비활성화해주세요. 

 

 

 

 

나는 댓글과 자막도 필요하신 분들은

밑으로

 

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
from youtube_transcript_api import YouTubeTranscriptApi
from konlpy.tag import Kkma
from pykospacing import Spacing
 
def ytb_subtitle(start_url, title) :
    try:
        code = start_url.split('=')[1]
        srt = YouTubeTranscriptApi.get_transcript(f"{code}", languages=['ko']) #한글로, 딕셔너리 구조
 
        text = ''
 
        for i in range(len(srt)):
            text += srt[i]['text'+ ' '
            
        text_ = text.replace(' ','')
 
        #문장 분리 / kss 사용해도 무방
        kkma = Kkma()
 
        text_sentences = kkma.sentences(text_)
 
        #종결 단어
        lst = ['죠','다','요','시오''습니까','십니까','됩니까','옵니까','뭡니까',]
 
        df = pd.read_csv('not_verb.csv',encoding='utf-8')
        not_verb = df.stop.to_list()
 
        #단어 단위로 끊기
        text_all = ' '.join(text_sentences).split(' ')
 
        for n in range(len(text_all)) :
            i = text_all[n]
            if len(i) == 1 : #한글자일 경우 추가로 작업x
                continue
            
            else :
                for j in lst : #종결 단어
                    #질문형
                    if j in lst[4:]:
                        i += '?'
                    
                    #명령형                
                    elif j == '시오' :
                        i += '!'
                    
                    #마침표    
                    else :
                        if i in not_verb : #특정 단어 제외
                            continue
                        else :        
                            if j == i[len(i)-1] : #종결
                                    text_all[n] += '.'
                                    
 
        spacing = Spacing()
        text_all_in_one = ' '.join(text_all)
 
        text_split = spacing(text_all_in_one.replace(' ','')).split('.')
        text2one= []
        for t in text_split:
            text2one.append(t.lstrip())  
            
        w = '. '.join(text2one)
                        
        f = open(f'data/{channel}/subtitle/{title}.txt','w',encoding='utf-8')
        f.write(w)
        f.close()
        print('O')
    except:
        print('X')
cs
 

유튜브 크롤링(2) - ㄹㅇ 초간단 유튜브 자막 다운 & 추출 (문장분리까지)

유튜브 크롤링 글에 제목, 조회수, 댓글, 좋아요를 크롤링하는 방법에 대해서 글을 써야 하는데, 요즘 자소서를 쓰고 알고리즘 공부도 하고 이것저것 하다보니 글을 쓸 시간이 많지 않았다. 유튜

0goodmorning.tistory.com

유튜브 자막 추출 다운과 관련해서는 이전 글을 참고해주시면 좋을 것 같습니다. not_verb.csv 파일의 경우 '다', '요'로 끝나는 단어 중 동사가 아닌 명사, 형용사 단어를 stop 컬럼으로 추가하시면 됩니다.

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# 영어 번역 없음
import winsound as sd
from bs4 import BeautifulSoup
import pyautogui
import pandas as pd
import re
 
def beepsound():
    fr = 2000    # range : 37 ~ 32767
    du = 1000     # 1000 ms ==1second
    sd.Beep(fr, du) # winsound.Beep(frequency, duration)
 
def ytb_info(video_url,channel) :
    print(f'{channel}',' 크롤링 시작')
    driver = webdriver.Chrome('chromedriver.exe', options= options)
    # new_data = {'date': '', 'title': '', 'view': '', 'comment': '', 'like':'', 'dislike':''}
    
    count = 1
    
    #데이터 넣을 리스트
    date_list = []
    title_list = []
    view_list = []
    like_list = []
    dislike_list = []
    comment_list = []
    
    try:
        #각 채널별 영상으로 크롤링
        for i in range(len(video_url)):
            start_url = video_url[i]
            print(start_url, end= ' / ')
            driver.get(start_url)
            driver.implicitly_wait(1.5)
                    
            body = driver.find_element_by_tag_name('body')
            
            #댓글 null 값 방지 
            num_of_pagedowns = 1
            while num_of_pagedowns:
                body.send_keys(Keys.PAGE_DOWN)
                time.sleep(0.5)
                num_of_pagedowns -= 1
                driver.implicitly_wait(1)
            
            #크롤링 요소    
            try : 
                info = driver.find_element_by_css_selector('.style-scope ytd-video-primary-info-renderer').text.split('\n')
 
                if '인기 급상승 동영상' in info[0] :
                    info.pop(0)
                elif '#' in info[0].split(' ')[0] :
                    info.pop(0)
            
                title = info[0]
                divide = info[1].replace('조회수 ','').replace(',','').split('회')
                view = divide[0]
                date = divide[1].replace(' ','')
                like = info[2]
                dislike = info[3]    
                        
                try:
                    comment = driver.find_element_by_css_selector('#count > yt-formatted-string > span:nth-child(2)').text.replace(',','')
                except:
                    comment = '댓글x'
                    
                #리스트에 추가
                title_list.append(title)
                view_list.append(view)
                date_list.append(date)
                like_list.append(like)
                dislike_list.append(dislike)
                comment_list.append(comment) 
                
                # 크롤링 정보 저장    
                new_data = {'date':date_list, 'title':title_list, 'view':view_list, 'comment': comment_list, 'like':like_list, 'dislike':dislike_list}
                df = pd.DataFrame(new_data)
                df.to_csv(f'data/{channel}/-{channel}.csv', encoding='utf-8-sig')
 
            except :
                continue
 
            # print(title, view, date, like, dislike, comment)
            
            num_of_pagedowns = 1
            while num_of_pagedowns:
                body.send_keys(Keys.PAGE_DOWN)
                time.sleep(0.5)
                num_of_pagedowns -= 1
                
            #페이지 다운
            last_page_height = driver.execute_script("return document.documentElement.scrollHeight")
 
            while True:
                driver.execute_script("window.scrollTo(0, document.documentElement.scrollHeight);")
                # driver.implicitly_wait(2) #오류남
                time.sleep(0.5)
                new_page_height = driver.execute_script("return document.documentElement.scrollHeight")
 
                if new_page_height == last_page_height:
                    break
                last_page_height = new_page_height
                # driver.implicitly_wait(1)
                time.sleep(0.75)
            
            time.sleep(0.5)
 
            
            # 댓글 크롤링
            html = driver.page_source
            soup = BeautifulSoup(html, 'lxml')
            
            users = soup.select("div#header-author > h3 > #author-text > span")
            comments = soup.select("yt-formatted-string#content-text")
            
            user_list=[]
            review_list=[]
 
            for i in range(len(users)):
                str_tmp = str(users[i].text)
                str_tmp = str_tmp.replace('\n''')
                str_tmp = str_tmp.replace('\t''')
                str_tmp = str_tmp.replace('              ','')
                str_tmp = str_tmp.replace('            ','')
                user_list.append(str_tmp)
 
                str_tmp = str(comments[i].text) 
                str_tmp = str_tmp.replace('\n''')
                str_tmp = str_tmp.replace('\t''')
                str_tmp = str_tmp.replace('            ''')
 
                review_list.append(str_tmp)        
 
            
            # 댓글 추가    
            pd_data = {"ID":user_list, "Comment":review_list}
            youtube_pd = pd.DataFrame(pd_data)
            
            title = re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…《\》]''', title)
            youtube_pd.to_csv(f"data/{channel}/{title}.csv", encoding = 'utf-8-sig')#,index_col = False)
            print('ㅁ',end='')
 
            # 자막 추출
            ytb_subtitle(start_url, title)
            
            # 광고 끄기
            if count :
                # time.sleep(1)
                try:
                    driver.implicitly_wait(0.5)
                    driver.find_element_by_css_selector("#main > div > ytd-button-renderer").click()
                    count -=1
                except:
                    continue
 
    except :
        driver.quit()
        beepsound()
    driver.quit()
    beepsound()
cs

기본 정보 / 댓글 / 자막까지 

기본 정보 크롤링 밑으로 추가된 기능은 스크롤 다운 후, html page_source를 bs4로 넘겨서 댓글을 크롤링 합니다. 양이 많기 때문에 셀레니움보다 가볍고 빠른 bs4를 사용하시는 것을 추천드립니다.

 

댓글을 다 크롤링하고, 자막까지 받았을 때 영상 1개당 33초 정도 걸렸습니다. 컴퓨터, 인터넷 사양에 따라서 다를 거라 생각합니다. 한 채널이 끝날 때마다 소리가 나게 했습니다. 필요 없으면 꺼주세요!

 

 

 

 

*주의사항 *

유튜브 댓글은 기본적으로 인기 댓글순으로 정렬이 되어있기 때문에, 뒤에 있는 댓글일수록 공감을 적게 받거나 관심이 적은 댓글일 확률이 높습니다. 저는 모든 댓글이 필요하지 않기 때문에, 가장 크롤링이 빠르면서 댓글들 정보를 모을 수 있게 시간 설정을 했습니다. 댓글이 적으면 모든 댓글을 크롤링하지만, 많아지면 60~90% 정도만 크롤링을 하게 됩니다.

 

모든 댓글들이 필요하신 분들은, time.sleep을 1초 이상으로 해주세요. driver.implicitly_wait의 경우 스크롤은 내려가는데 댓글들이 로딩이 되지 않는 경우가 있어서 time.sleep을 사용했습니다. 

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#영어 번역
import pyautogui
import pandas as pd
import re
 
def ytb_info(video_url,channel) :
    print(f'{channel}',' 크롤링 시작')
    driver = webdriver.Chrome('chromedriver.exe', options= options)
    df = pd.DataFrame()
 
    count = 1
    
    #각 채널별 영상으로 크롤링
    for i in range(len(video_url)):
        start_url = video_url[i]
        print(start_url, end= '/ ')
        driver.implicitly_wait(1)
        driver.get(start_url)
        
        #영어 번역
        pyautogui.hotkey('shift','F10')
        for i in range(7):
            pyautogui.hotkey('down')
        pyautogui.hotkey('enter')
        
        body = driver.find_element_by_tag_name('body')
        
        #댓글 null 값 방지 
        num_of_pagedowns = 1
        while num_of_pagedowns:
            body.send_keys(Keys.PAGE_DOWN)
            time.sleep(.75)
            num_of_pagedowns -= 1
            driver.implicitly_wait(1)
        
        #크롤링 요소    
        info = driver.find_element_by_css_selector('.style-scope ytd-video-primary-info-renderer').text.split('\n')
 
        if '인기 급상승 동영상' in info[0] :
            info.pop(0)
        elif '#' in info[0].split(' ')[0] :
            info.pop(0)
        
        title = info[0]
        divide = info[1].replace('조회수 ','').replace(',','').split('회')
        view = divide[0]
        date = divide[1].replace(' ','')
        like = info[2]
        dislike = info[3]    
                
        try:
            comment = driver.find_element_by_css_selector('#count > yt-formatted-string > span:nth-child(2)').text.replace(',','')
        except:
            comment = '댓글x'
   
        
        # 크롤링 정보 저장    
        new_data = {'date':date, 'title':title, 'view':view, 'comment': comment, 'like':like, 'dislike':dislike}
        df = df.append(new_data, ignore_index=True)
        df.to_csv(f'data/{channel}/{channel}.csv', encoding='utf-8-sig')
        # print(title, view, date, like, dislike, comment)
        
        #페이지 다운
        last_page_height = driver.execute_script("return document.documentElement.scrollHeight")
 
        while True:
            driver.execute_script("window.scrollTo(0, document.documentElement.scrollHeight);")
            time.sleep(1)
            new_page_height = driver.execute_script("return document.documentElement.scrollHeight")
 
            if new_page_height == last_page_height:
                break
            last_page_height = new_page_height
            time.sleep(1)
        
        time.sleep(0.5)
        
        #댓글 크롤링
        review_list = []
        user_list =[]
        reviews = driver.find_elements_by_css_selector('#content-text')
        users = driver.find_elements_by_css_selector('h3.ytd-comment-renderer a span')
        num = 0
        for i in range(len(users)):
            review = reviews[i].text.replace('\n'' ')
            review_list.append(review)
            
            user = users[i].text
            user_list.append(user)
            
        # 댓글    
        pd_data = {"ID":user_list, "Comment":review_list}
        youtube_pd = pd.DataFrame(pd_data)
        
        title = re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\‘|\(\)\[\]\<\>`\'…《\》]''', title)
        youtube_pd.to_csv(f"data/{channel}/{title}.csv", encoding = 'utf-8-sig')
        print('ㅁ',end='')
 
        # 자막 추출
        ytb_subtitle(start_url, title)
        
        # 광고 끄기
        if count :
            # time.sleep(1)
            try:
                driver.implicitly_wait(0.5)
                driver.find_element_by_css_selector("#main > div > ytd-button-renderer").click()
                count -=1
            except:
                continue
        
    driver.quit()
cs

해외 번역

단점 : headless으로 하면 안 된다. 마우스를 사용하지 못 한다. 시간이 진짜아아아아 엄처어어어엉 오래 걸린다. 굳이 이렇게 안 해도 될 거라고 생각이 드는데 혹시나 필요하신 분들을 위해서 남긴다.

 

가장 문제가 되는 부분이 번역을 한 정보는 bs4로 넘어가지 않는다. 셀레니움으로 모든 댓글과 닉네임들을 모아야 하기 때문에 시간이 오래 걸리는 것이다.

 

 

 

이 데이터들을 어떻게 사용할 것인지는 아직까지는 비밀.

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

유튜브 크롤링 글에 제목, 조회수, 댓글, 좋아요를 크롤링하는 방법에 대해서 글을 써야 하는데, 요즘 자소서를 쓰고 알고리즘 공부도 하고 이것저것 하다보니 글을 쓸 시간이 많지 않았다. 유튜브 관리도 제대로 안 되고 있어서 돈만 날리고 있다 ㅠㅠ

 

 

 

유튜브 크롤링(3) 올인원 - 채널 제목, 댓글, 조회수, 자막까지

지금 크롤링을 하고 있어서 시간이 나는 김에 글을 작성합니다. 크롤링도 크롤링이지만 이 데이터를 어떻게 정제할지가 더 고민이네요. 지난 번 글들을 활용해서 작성하오니 본인의 목적에 맞

0goodmorning.tistory.com

다른 분들도 친절하게 크롤링 방법에 대해서 언급을 하고 있어서 일단 관련 글은 미루다가 작성(9.29 수정), 구글 검색을 해도 잘 나오지 않는 유튜브 자막 다운 방법에 대해서 쓰겠습니다. 검색을 했을 때 pytube3로도 다운이 가능하다고 되어있으나 2020년 글들이고 오류가 나서 사용을 하지 못 했습니다. 사용 방법을 아시면 알려주세요 :)

 

pip install youtube-transcript-api

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
from youtube_transcript_api import YouTubeTranscriptApi
# 오징어 게임 url https://www.youtube.com/watch?v=ysz5Sl3msyk
 
srt = YouTubeTranscriptApi.get_transcript("ysz5Sl3msyk", languages=['ko'])
 
with open("subtitles.txt""w", encoding='utf-8'as f:  
    for i in srt:
        # writing each element of srt on a new line
        f.write("{}\n".format(i))


# {'text': '저는...', 'start': 0.0, 'duration': 0.584} 이런 식으로 저장됨
cs

이걸로 srt 느낌의 자막을 다운로드 받을 수 있습니다. 쉽죠? 자막 다운이 급하신 분들을 위해서 먼저 설명을 드렸고, 딱 자막 부분만 필요하신 분들은 더 따라와 주세요. 오타나 인식이 잘못된 부분은 구글의 SST를 탓하세요! 

 

 

※ 야매 주의 ※

(야매가 싫으시면 뒤로가기 버튼을)

 

 

[사용]

pykospacing(맞춤법), kkma.sentence(kss도 가능!)

 

[시도]

kss(문장분리) / mecab, kkma, okt pos

 

[순서]

자막 다운 -> 띄어쓰기 제거, kkma.sentence -> 야매 문장 분리 -> 띄어쓰기 제거, spacing

-kss 시도를 해봤지만, 문장 부호가 없으면 시간이 많이 걸리고 모든 문장으로 안 나뉘어지는 단점 // 다시 해보니 0.3초 뭐지...

-spacing을 먼저 해봤으나 맞춤법 전문이라 제대로 안 나뉘어짐 ('~요' 인식이 잘 안 됨)

-꼬꼬마로 문장을 어느정도 나누고, 문장 부호를 추가하니 그나마 나아짐

 

 

 

1
2
3
4
5
6
7
from youtube_transcript_api import YouTubeTranscriptApi
# 오징어 게임 url https://www.youtube.com/watch?v=ysz5Sl3msyk
 
srt = YouTubeTranscriptApi.get_transcript("ysz5Sl3msyk", languages=['ko'])
 
for i in srt :
    print(i)
cs

어떤 식으로 자막이 이루어졌는지 확인을 해보기 전에, 영상이 자막을 지원하는 영상인지 아닌지부터 확인을 합니다. 자동생성 자막을 지원하지 않을 경우 에러가 납니다.

 

 

 

 

결과를 확인해보면 리스트 형식 안에 딕셔너리 형식이 있습니다. 우리는 'text'만 필요하기 때문에 딕셔너리에서 'text' 부분만 가져오면 됩니다. 

 

 

 

 

1
2
3
4
5
6
text = ''
 
for i in range(len(srt)):
    text += srt[i]['text'+ ' ' # text 부분만 가져옴
    
text_ = text.replace(' ','')
cs

간단하게 가져왔지만 문제가 하나 있습니다. 띄어쓰기가 제대로 되어 있지 않아서 잘 알아볼 수가 없습니다. 한국어 전처리 패키지인 PyKoSpacing을 바로 사용하려고 했으나, 문장 부호가 없어서 잘 되지 않습니다. 영어의 경우 친절하게 '.'을 찍어주는데 한국은 차별하는게 아닌가... 

 

kss 한국어 문장 분리기를 사용해보았으나 특정 어휘(EX : 처음)에서 이상하게 분리를 하고, 모든 문장을 나눠주지는 않았다. 글 쓰기 전에 kss를 사용하는데 상당한 시간이 걸려서 사용하지 않았는데, 글 쓰는 중에 다시 테스트를 하니 깔끔하게 잘 나뉘어서 당황스럽다. 그래서 Kkma.sentence()를 사용했던건데 어떤 것을 사용하든 상관은 없어보인다. 

 

그 후 종결어미인지 확인하기 위해서 mecab, kkma, okt를 사용해서 확인을 하려고 했으나 case를 나눌 것들이 너무 많아서 야매를 사용했다. kkma는 품사 분류표가 너무 복잡하고 pos로 나눌 때 명사가 포함되면 종결어미인지 분간이 잘 안 갔다. okt의 경우 품사 분류표가 간단하지만 (verb, noun 등..) 얘도 생각보다 case가 많았다.

 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from konlpy.tag import Kkma
import pandas as pd
 
#문장 분리 / kss 사용해도 무방
kkma = Kkma()
text_sentences = kkma.sentences(text_)
 
#종결 단어
lst = ['죠','다','요','시오''습니까','십니까','됩니까','옵니까','뭡니까',]
 
df = pd.read_csv('not_verb.csv',encoding='utf-8')
not_verb = df.stop.to_list()
 
#단어 단위로 끊기
text_all = ' '.join(text_sentences).split(' ')
 
for n in range(len(text_all)) :
    i = text_all[n]
    if len(i) == 1 : #한글자일 경우 추가로 작업x
        continue
    
    else :
        for j in lst : #종결 단어
            #질문형
            if j in lst[4:]:
                i += '?'
            
            #명령형                
            elif j == '시오' :
                i += '!'
            
            #마침표    
            else :
                if i in not_verb : #특정 단어 제외
                    continue
                else :        
                    if j == i[len(i)-1] : #종결
                            text_all[n] += '.'
                            print(text_all[n], end = '/ ')
cs

여기서부터 야매 방법이다.

 

종결형 단어를 뽑아서 lst로 만들었다. 구어체는 제외하고 뉴스기사를 여러개 찾아보면서 거의 웬만한 종결형 단어를 넣었다. 문제가 있으면 추가로 집어넣으면 된다. 그리고 문장을 모두 단어로 쪼개서 실행을 했다. 

 

처음에는 '죠, 다, 요'로만 구성하려고 했으나 질문, 감탄형 문장부호도 필요할 것 같아서 추가했다. 마지막 단어에 '다, 요'가 쓰인 경우, 무조건 종결형 어미가 아니라서 특정 단어를 제외했다. 바다, 날마다, 우간다, 노가다 / 필요, 수요, 중요, 노동요  ... 같은 단어를 찾아서 csv파일에 추가했다. 다로 끝나는 단어는 10만개가 넘어서 추후에 더 추가하면 될 듯 하다.

 

 

 

결과를 보면 나쁘지 않다. 걸러지는 걸 보여주면 효과가 더 좋아보일텐데 일단 변경되는 것만 보여드립니다. 그리고 나서 한국 전처리 기본 패키지인 pykospacing을 사용하게 되면 나름 괜찮게 결과가 나온다.

 

 

 

1
2
3
4
5
from pykospacing import Spacing
 
spacing = Spacing()
text_all_in_one = ' '.join(text_all)
print(spacing(text_all_in_one.replace(' ','')))
cs

단점은 마침표 다음에 띄어쓰기가 안 되어 있는 경우도 있어서. split으로 나누고 다시 붙이면 된다.

 

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

유튜브로 새로운 수익모델을 찾기위한 채널 분석을 시도하고 있다. (기존 채널에 영상을 새로 올려야 하는데 요즘 못 올리고 있다 ㅠㅠ) 솔직히 노가다를 해도 되는데 파이썬을 배웠으면 자동화를 하는게 맞지 않을까 싶고, 재밌는 실험들을 해보고 싶어서 코드를 짜고 있다.

 

셀레니움으로 페이지 자동 번역, 키 입력 정보가 필요하신 분들은 밑으로 내려주세요!

 

진행할 목록

- 페이지 성별 분석 (이름 데이터셋 수집 완료, CNN 카테고리 분류 완료, 검증 단계) => 얘 때문에 자동번역이 필요했음

- 페이지 연령 분석, 댓글 문체로 분류 도전 (자체 유튜브 조회수 100만 영상들에서 댓글 크롤링 후, 연령, 성비 분석 후 학습)

- 댓글로 영상 주제 분석 (긍정, 부정, 어떤 반응으로 영상이 화제가 됐는지 체크)

- 어그로 확인 (같은 주제, 같은 업로드 날짜 => 왜 다른 조회수? 썸네일, 키워드)

 

 

 

 

셀레니움 자동 번역 필요했던 이유?

연령대가 있는 층들은 대부분 유튜브 계정 닉네임이 본인 이름으로 되어 있어서 성별 분석하기가 조금 수월해 보였다. 하지만 영어로 이름을 입력하신 분들도 있어서 이걸 자동으로 번역해서 닉네임을 크롤링 해야 했다. 문제는 셀레니움으로 자동 번역이 잘 되지 않았다.

 

-파이썬에서 셀레니움 웹 사이트 언어 변경

-Translate the webpage opened via Selenium Webdriver to English

-Translate webpage language using Selenium

-Select an Option from the Right-Click Menu in Selenium Webdriver

-Modifying the language option in selenium

 

이렇게 다양하게 검색을 했는데도 문제가 잘 해결 되지 않았다. (구글이 PageRank를 활용한 알고리즘이라고 하는데 SEO 시스템을 잘 몰라서 외국에서 검색이 될지는 모르겠지만 그래도 찾아주시는 분들은 도움이 됐으면 좋겠다.) 일단 시도했던 것들에 대해서 간단하게 보여주자면

 

 

 

 

1
2
3
4
5
from selenium import webdriver
 
options = webdriver.ChromeOptions()
options.add_argument("--lang=ko_KR")
options.add_argument("--lang=ko")
cs

셀레니움에서 크롬옵션으로 기본 언어를 설정하면 된다고 하지만 내 경우는 되지 않았다. --lang=en 영어로도 안 된다는 사람들이 많았다. 그래서 계속 구글링을 시도했다.

 

 

 

 

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from selenium import webdriver
 
#1
prefs = {
  "translate_whitelists": {"en":"ko"},
  "translate":{"enabled":"true"}
}
options.add_experimental_option("prefs", prefs)
 
#2
prefs = {
  "translate_whitelists": {"your native language":"ko"},
  "translate":{"enabled":"True"}
}
options.add_experimental_option("prefs", prefs)
 
#3
options.add_experimental_option('prefs', {'intl.accept_languages''ko,ko_KR'})
 
cs

translate_whitelists에서 en -> ko로 바꿔주면 된다고 친절하게 설명이 되어있는데 이것 또한 되지 않았다. 그래서 영어권에서는 2번, 3번 방법의 경우를 추천했는데 이것 또한 되지 않았다^^ 

 

 

 

그래서 생각한 방법이 오른쪽 마우스로 번역하면 되겠거니 싶었다. 마우스 좌표 값을 설정해서 오른쪽 버튼을 클릭하기 번거로워서, 키보드로 마우스 오른쪽 버튼을 누르는 방법이 있나 찾아보니 shift + f10이라고 구글에서 친히 알려주신다. 그리고 Translate에 'T' 앞 글자를 따서 단축키를 만들지 않았을까 하고 T를 눌러보니 번역이 됐다!

 

그리고 send_keys(Keys.SHIFT + Keys.F10 + 't')를 해봤더니 t가 눌리지 않았다. 이런... 

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
 
#1
body = driver.find_element_by_tag_name('body')
body.send_keys(Keys.SHIFT, 'a')
 
#2
action = webdriver.ActionChains(driver)
 
# 한번에 버튼 누르기
action.send_keys(Keys.SHIFT, Keys.F10, 't').perform()
 
# 키를 누르고 떼기
action.key_down(Keys.SHIFT).key_down(Keys.F10).send_keys("t").key_up(Keys.SHIFT).key_up(Keys.F10).perform()
cs

ctrl + f, shift + a 처럼 '페이지 내 검색어 찾기', '전체 선택'같이 셀레니움에서 번역이 아닌 단순 키 입력을 원하시는 분이라면 1번 방법을 사용하시면 됩니다. 이것도 안 돼서 더 찾아보니, ActionChains를 활용하면 된다고 해결책으로 제시했는데 이것도 되지 않았다. 

 

 

 

1
2
3
4
5
6
import pyautogui
 
pyautogui.hotkey('shift','F10')
for i in range(7):
    pyautogui.hotkey('down')
pyautogui.hotkey('enter')
cs

그래서 가장 원초적인 방법을 사용했다. 이전에 파이썬으로 계산기 들어가서 숫자 계산할 때 사용했던, 마우스 / 키보드 자동 조작 모듈을 사용했다. 

 

 

 

 

되긴 됐다. 자동으로 한국어 번역이...!

 

다만 단점이 있다면 headless로 작업이 되지 않다는 점과 pyautogui를 사용하기 때문에 초반에 다른 걸 만지면 안 된다. 좋게 생각하면 셀레니움으로 크롤링이 잘 작동되고 있구나를 확인한다고 생각하자.

 

다음은 유튜브 정보, 댓글 크롤링 방법들에 대한 포스팅을 진행하려고 한다. 요즘 유튜브가 지속적으로 페이지 UI랑, 아이콘 설정들을 조금씩 바꾸고 있어서 며칠 전 됐던 코드도 오류가 났다. 그래서 오늘 조금 수정을 했는데 글쓰는데 시간이 걸려서...

 

 

 

 

유튜브 크롤링(2) - ㄹㅇ 초간단 유튜브 자막 다운 & 추출 (문장분리까지)

유튜브 크롤링 글에 제목, 조회수, 댓글, 좋아요를 크롤링하는 방법에 대해서 글을 써야 하는데, 요즘 자소서를 쓰고 알고리즘 공부도 하고 이것저것 하다보니 글을 쓸 시간이 많지 않았다. 유튜

0goodmorning.tistory.com

 

유튜브 크롤링(3) 올인원 - 채널 제목, 댓글, 조회수, 자막까지

지금 크롤링을 하고 있어서 시간이 나는 김에 글을 작성합니다. 크롤링도 크롤링이지만 이 데이터를 어떻게 정제할지가 더 고민이네요. 지난 번 글들을 활용해서 작성하오니 본인의 목적에 맞

0goodmorning.tistory.com

 

+ Recent posts