본문 바로가기
내가 좋아하는 정보들/뒤쳐지지 말자, IT

파이썬, 셀레니움으로 네이버 카페의 모든 게시글 첨부파일 다운로드하기

by 앩옭 2023. 2. 23.

지난 22년 7월 경,

파이썬과 Selenium 패키지 및 웹드라이버를 이용해

네이버 카페에 있는 모든 게시글의 모든 파일을 다운받는 명령문을 작성했습니다.

 

모두 다운받아야 하는데 손으로 하기에는 시간이 걸릴 것 같아서 만들기 시작한 명령문인데

소요된 시간을 측정해보니 그냥 일일히 다운받는 쪽이 더 빠르긴 했을것 같습니다 :) 만

 

만들면서 매우 재미도 있었고 셀레니움의 여러 툴들을 배울 수 있었습니다.

모르는 게 많아서 웹 서치를 매우 많이 해서 다 만들고 나면 나도 꼭 명령문 업로드를 해야겠다고 생각했으나

 

약사 고시 준비로 미루다가 6개월이 지나 업로드 합니다.

 

#%%
# import pyperclip
import time
from selenium import webdriver
from selenium.webdriver.chrome.service import Service as ChromeService 
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By 
from selenium.webdriver.common.keys import Keys 

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains

op = webdriver.ChromeOptions()

 네이버 카페의 게시판별 CSS ID는는 f"#menuLink{dic[게시판]}"과 같이 지정되기 때문에 

아래와 같이 딕셔너리를 작성했습니다.

게시판 개수가 안되어 이렇게 손으로 쳤습니다.

노란색 박스처럼 게시판마다 menuLink{게시판번호}로 id 매겨져 있음

dic = {'게시판7':'7', '게시판1' : '1', '게시판3':'3', '게시판4':'4', '게시판5':'5', '게시판6':'6',  '게시판8':'8', '게시판9':'9', '게시판10':'10', '게시판11':'11', '게시판12':'12', '게시판13':'13', '게시판14':'14', '게시판15':'15', '게시판16':'16'}

게시판목록 = list(dic.keys())

카페에 있는 여러개의 게시판 목록을 for 문으로 구성하고

하나씩 방문하면서 그 안의 모든 게시글을 다시 다운받을 예정입니다.

 

게시판마다 각기 다른 디렉토리에 저장되게 하기 위해 

webdriver.ChromeOptions() 명령어로 디렉토리를 지정합니다.

이후 웹드라이버를 새창으로 하나 띄우도록 합니다. 

for 게시판 in 게시판목록 : 
    #크롬에서 다운받고 싶은 위치 지정하기
    op.add_experimental_option('prefs', {'download.default_directory':r'/Users/다운하고 싶은 디렉토리 입력/{}'.format(과목)})
     
     
     def chromeWebdriver(): 
        try :
            chrome_service = ChromeService(ChromeDriverManager().install())
            driver = webdriver.Chrome(service = chrome_service, options=op)
        except Exception as e:
            driver = None
            print(e)
            
        return driver

이후 다운받고 싶은 파일이  있는 카페의 주소로 이동합니다.

명령문을 잘라서 업로드하느라 잘 보이지는 않지만

아래부터 모든 코드는 for문 안에서 작동하고 있습니다.

  driver = chromeWebdriver()
  driver.get("https://cafe.naver.com/다운받고 싶은 카페 주소")

driver.execute_script 명령을 이용하여

각 Element의 고유한 name으로 네이버의 아이디 입력 칸, 비밀번호 입력 칸을 지정하고 

그 자리에 미리 변수로 텍스트를 입력하도록 합니다.

인터넷 속도를 고려하여 사이에 time.sleep()을 추가합니다.

    #reCAPTCHA 없이 로그인, script로 아이디와 비밀번호 입력하기
    my_id = "네이버 아이디"
    my_pw = "네이버 비밀번호"
    driver.execute_script("document.getElementsByName('id')[0].value = '" + my_id + "'")
    time.sleep(0.5)
    driver.execute_script("document.getElementsByName('pw')[0].value = '" + my_pw + "'")
    time.sleep(0.5)

 

Element의 ID로 로그인 버튼을 지정해 클릭합니다.

   #로그인 버튼 클릭
    button = driver.find_element(By.ID, "log.login")
    button.click()

    time.sleep(1)

제가 파일을 다운받고자 했던 게시판들은

드롭다운처럼 대분류에 가려져 있어서 

드롭다운 버튼을 인식해 펼쳐주는 명령문을 넣었습니다.

 

드롭다운이 없는 카페는 생략해도 되는 부분입니다.

참고로 time.sleep()과 달리 driver.implictly_wait()은

직전 명령어로 인한 동작이 끝난 이후로 time.sleep()을 적용한다는 차이점이 있습니다.

    #CSS_SELECTOR로 게시판 드롭다운 펼치기 
    dropdown = driver.find_element(By.CSS_SELECTOR, '#group2btn > a > img')
    dropdown.click()
    driver.implicitly_wait(3)

 

그다음 미리 만들어둔 딕셔너리를 이용해

게시판을 지정하여 진입합니다.

   #게시판 지정 및 클릭

    board = driver.find_element(By.CSS_SELECTOR, f"#menuLink{dic[게시판]}")
    driver.implicitly_wait(3)
    board.click()

그 다음 작성하면서 시간을 가장 많이 들였던 부분인데,

iframe을 찾고 지정하는 것입니다.

 

네이버에서 가장 유명한 중고나라 카페를 예로 들면
(예시를 위해 스크린샷만 찍고 여기서 파일을 다운받지 않았습니다.)

스크린 샷에서 그어놓은 노란 선 안은 iframe 이라는 프레임으로,

프레임 외 부분과 별도로 분리되어 있습니다. 

 

즉 게시판을 선택해 진입한 이후에,

게시글 내부에서 첨부파일을 클릭하고 다운 받는 행동들은

iframe 안에서 이루어져야 한다는 뜻이었습니다.

노란 선 그은 영역 내부는 iframe 적용

저는 컴알못이므로, 

어떻게 해야 이 내부의 요소를 지정해서 클릭을 할 수 있는지만 알아보았습니다.

 

아래는 사용자가 상호작용하고 싶은 영역이

어떤 아이프레임인지 찾는 명령문입니다.

  #페이지 안의 모든 iframe 찾기
    iframes = driver.find_elements(By.TAG_NAME, 'iframe')

  # iframe ID로 특정이 안 될 때, 원하는 iframe 찾기 
    for i, iframe in enumerate(iframes): 
         try :
             print('%d번째 iframe입니다' % i)
             print(iframe)
             driver.switch_to.frame(iframes[i])
             print('%d번째 페이지로 전환합니다' % i)
             print(driver.page_source)
             driver.switch_to.default_content()
         except Exception as e :
             print('pass by except : iframe[%d]' % i)
             print(e)
             pass

 

제가 필요했던 frame은 7번째였나봅니다.

그래서 아래와 같이 게시글 내부의 원하는 프레임 영역으로 전환합니다.

 

    #iframe 전환
    driver.switch_to.frame(iframes[7])
    driver.implicitly_wait(2)

우선 첫 번째 게시글로 진입합니다.

#첫 파일 다운로드
    print(f'{과목} 파일 다운로드 시작')

    WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.CSS_SELECTOR, "첫 게시글 선택자"))).click()

그 다음에는 다운로드하는 일련의 행위를 묶은 download() 함수 및

여러가지 요소를 정의했습니다.

 

다운받을 파일들의 목록을 = files

다운로드를 위해 클릭해야 할 버튼을 = 펼치기

다음 게시글로 넘어가기 위한 버튼을 = Next

각각 CSS 선택자로 정의했습니다.

 

게시글에 진입해서 다운로드 한 뒤, 뒤로가기 후 다음 게시글을 선택하는 방법도 있겠지만

네이버 카페 게시글 하단에 게시글 일부를 보여준 뒤, 다음 게시글로 넘어갈 수 있도록 만든 창을 

Next로 지정하고 이용하기로 했습니다.

각각 files, Next, 펼기치로 정의된 요소들

def download() : 
		files = driver.find_elements(By.CSS_SELECTOR, "li.AttachFileListItem")
        펼치기 = driver.find_element(By.CSS_SELECTOR, "첨부파일 펼치기 선택자")
        download = driver.find_element(By.CSS_SELECTOR, f"#app > div > div > div.ArticleContentBox > div.article_container > div.AttachFileList > div.attach_file > div > ul > li:nth-child({idx+1}) > div.file_download > a:nth-child(1)")
    	article = 2
        Next = "#app > div > div > div.RelatedArticles > div.RelatedArticlesTabContainer > div > div.tab_content > div > ul > li:nth-child(4) > div.tit_area > a > span"
        
        for idx in range(len(files)) : 

            ActionChains(driver).move_to_element(button).click(button).perform()
            download = driver.find_element(By.CSS_SELECTOR, f"#app > div > div > div.ArticleContentBox > div.article_container > div.AttachFileList > div.attach_file > div > ul > li:nth-child({idx+1}) > div.file_download > a:nth-child(1)")
            driver.implicitly_wait(10)
            ActionChains(driver).move_to_element(download).click(download).perform()
            driver.implicitly_wait(10)
            time.sleep(2)

            print(f'{게시판}의 {article-1}번째 게시글 중 {idx+1}번째 파일 저장 완료')

 

아래는 게시글의 순서에 따라 다음 게시글로 이동할 때 클릭해야 할 위치가 달라지는 점을 반영한 명령어입니다.

위에 노란색 박스  친 부분이 앞으로 클릭해야 할 부분이라면,

현재 머무르는 게시글은 3번째에 위치하게 됩니다.

 

그러다가 마지막 페이지 도달 전에는 Next 선택자 == 현재 머무르는 게시글 선택자 의 상황이 되어,

이 때는 Next 말고 리스트의 마지막 게시글로 이동하도록 합니다. 

elif driver.find_element(By.CSS_SELECTOR, "li.list_item.selected > div > a > span" ) == driver.find_element(By.CSS_SELECTOR, Next):

            print("마지막 페이지 도달 전")
            time.sleep(1)


            try : 
                download()
            except Exception as e:
                print(e)
                print(f'{게시판}의 {article-1}번째 게시글 저장 실패')
            
            driver.find_element(By.CSS_SELECTOR, "#app > div > div > div.RelatedArticles > div.RelatedArticlesTabContainer > div > div.tab_content > div > ul > li:nth-child(5) > div.tit_area > a > span").click()

사실 이러한 상황은 첫 번째, 두 번째 게시글일떄도 마찬가지입니다. 

따라서

1) 게시판의 1,2번째 게시글에 머무르는 중 

2) 게시판의 뒤에서 두번째 게시글에 머무르는 중

3) 그 외(중간 게시글이라 이동 보드?의 중간에 머무름)

으로 상황을 나누어서 작성했습니다.

 

또한 만약 파일 다운로드에 실패하면 원인과 함께 실패했음을 표시해서,

나중에 human intervention으로 직접 실패한 게시글을 찾아가 다운로드 할 수 있도록 했습니다. 

 

    while True : 
        if article == 2 or article == 3 :
            try : 
                download()
                            
            except Exception as e: 
                print(e)
                print(f'{게시판}의 {article-1}번째 게시글 저장 실패')
                
                
			#다음 게시글 선택하고 넘어가기
            driver.find_element(By.CSS_SELECTOR, f"#app > div > div > div.RelatedArticles > div.RelatedArticlesTabContainer > div > div.tab_content > div > ul > li:nth-child({article}) > div.tit_area > a > span").click()

            time.sleep(1)
                    
            article += 1 
        
        elif driver.find_element(By.CSS_SELECTOR, "li.list_item.selected > div > a > span" ) == driver.find_element(By.CSS_SELECTOR, Next):

            print("마지막 페이지 도달 전")
            time.sleep(1)


            try : 
                download()
            except Exception as e:
                print(e)
                print(f'{게시판}의 {article-1}번째 게시글 저장 실패')
            
            driver.find_element(By.CSS_SELECTOR, "#app > div > div > div.RelatedArticles > div.RelatedArticlesTabContainer > div > div.tab_content > div > ul > li:nth-child(5) > div.tit_area > a > span").click()

            time.sleep(1)
            article += 1 

            try : 
                download()
            except Exception as e:
                print(e)
                print(f'{게시판}의 {article-1}번째 게시글 저장 실패')
            
            print(f"{게시판} 게시판 다운로드 완료")

            break

        else : 
            try : 
                download()
                
            except Exception as e: 
                print(e)
                print(f'{게시판}의 {article-1}번째 게시글 저장 실패')

            driver.find_element(By.CSS_SELECTOR, Next).click()

            time.sleep(1.3)
            
            article += 1 
    
    driver.quit()

 

하는 동작은 간단한데

왜 이렇게 했는지 설명하는 것은 어렵네요 참 

제가 CSS나 HTML이 어떻게 작동하는지나 기본적인 명칭도 모르는 채

웹 드라이버를 어떻게든 쓰려고 도구적으로 사용해서 그런 것 같습니다. 

이게 왜 되지?

어쨌거나 이리저리 검색해 가며 작성한 명령어가 결과적으로 잘 돌아가서

파이썬과 셀레니움을 이용해

네이버 카페 안의 모든 파일을 잘 다운로드 받을 수 있었습니다. :) 

 

반응형

댓글