python/Selenium 셀레니움

[Selenium+bs4] 네이버 카페 검색창, iframe 크롤링 방법, 네이버 로그인 방법

고로케 2021. 4. 28.
반응형

이 글은 네이버 카페내의 결과에서 게시글 제목을 리스트로 출력하고,

게시글을 순서대로 클릭해서 들어갔다가 나가는 코드 설명입니다.

# 1. iframe이란?

네이버 카페 내에서 카페내 검색 결과를 크롤링하는 방법

일단 iframe 이란 것을 알아야한다.

 

아이프레임은 HTML Inline Frame 요소이며

 inline frame의 약자이다. 

" 효과적으로 다른 HTML 페이지를 현재 페이지에 포함시키는 중첩된 브라우저로

 iframe 요소를 이용하면 해당 웹 페이지 안에 어떠한 제한 없이 다른 페이지를 불러와서 삽입 할 수 있다. "

 

라는 어려운 말이지만 눈으로 보면 편하다 아래와 같은 것이 iframe이다

페이지 안의 따로 작동하는 페이지 느낌

 

그래서 일반적인 크롤링 방식으로 검색결과를 가져오려고 하면 class 이름이라던지 id를 찾지 못해 에러가 난다.

따라서 iframe 을 Selenium으로 창을 선택해주고 그안의 결과를 가져오는 식으로 진행해야 한다.

 

아래 코드를 참고하여 iframe의 결과를 가져오는데 도움이 되길 바랍니다.

 

코드 설명은 그 아래 작성함

 

# 2. 전체 코드

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
from bs4 import BeautifulSoup
import pandas as pd

driver = webdriver.Chrome("./chromedriver")

driver.get("https://cafe.naver.com/kig")
driver.implicitly_wait(3)

driver.find_element_by_name('query').send_keys('보라매역')
driver.find_element_by_name("query").send_keys(Keys.ENTER)
time.sleep(2)

driver.switch_to.frame("cafe_main")

for i in range(1, 3):
    req = driver.page_source
    soup = BeautifulSoup(req, 'html.parser')
    titles = soup.select("#main-area > div:nth-child(7) > table > tbody > tr")


    print('----' + str(i) + ' 번째 페이지 -----')
    list3 = []

    for title in titles:
        list = title.select_one(' td.td_article > div.board-list > div > a').text
        list2 = ''.join(list.split())
        list3.append(list2)

    list4_sr = pd.Series(list3)
    print(list4_sr)

    # for a in range(1, 3):
        # driver.find_element_by_xpath(f'//*[@id="main-area"]/div[5]/table/tbody/tr[{a}]/td[1]/div[2]/div/a').click()
        # time.sleep(3)
        # driver.back()
        # time.sleep(2)
        # driver.switch_to.frame("cafe_main")
    if i<2:
        driver.find_element_by_xpath(f'//*[@id="main-area"]/div[7]/a[{i}+1]').click()

 

 

# 3. 코드 설명

## part 1.

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
from bs4 import BeautifulSoup
import pandas as pd

먼저 selenium , bs4 , time , pandas를 import 하자

time 은 페이지를 넘어갈 때 약간의 딜레이를 줘 오류를 방지한다.

pandas는 리스트 결과를 출력하기 위함

 

driver = webdriver.Chrome("./chromedriver")	# 크롬 브라우저를 driver로 지정

driver.get("https://cafe.naver.com/kig")    # 원하는 카페 주소를 입력
driver.implicitly_wait(3)                   # 대기시간 3초

driver.find_element_by_name('query').send_keys('보라매역')  # 원하는 검색어를 검색창에 입력
driver.find_element_by_name("query").send_keys(Keys.ENTER)  # 그리고 엔터를 입력해 검색
time.sleep(2)  # 2초 대기

검색창을 'name' 엘리먼트로 find 했다.

driver.switch_to.frame("cafe_main")

이 부분이 핵심이다. 결과가 나온 게시물들은 iframe 안에 있기 때문에 

driver창의 frame 을 iframe의 태그인 cafe_main 으로 바꿔줘야 

bs4로 원하는 내용을 가져올 수 있다.

frame 변경을 하지 않으면 iframe 밖의 개체밖에 가져오지 못하고

반대로 iframe 으로 변경하면 밖의 개체를 가져올 수 없다.

 

 

## part.2

for i in range(1, 3):
    req = driver.page_source
    soup = BeautifulSoup(req, 'html.parser')
    titles = soup.select("#main-area > div:nth-child(7) > table > tbody > tr")


    print('----' + str(i) + ' 번째 페이지 -----')
    list3 = []

    for title in titles:
        list = title.select_one(' td.td_article > div.board-list > div > a').text
        list2 = ''.join(list.split())
        list3.append(list2)

    list4_sr = pd.Series(list3)
    print(list4_sr)

    # for a in range(1, 3):
        # driver.find_element_by_xpath(f'//*[@id="main-area"]/div[5]/table/tbody/tr[{a}]/td[1]/div[2]/div/a').click()
        # time.sleep(3)
        # driver.back()
        # time.sleep(2)
        # driver.switch_to.frame("cafe_main")
        ## 게시글을 클릭하고 뒤로 돌아온 경우에는 switch로 다시 iframe을 선택해 주어야 한다.
    if i<2:
        driver.find_element_by_xpath(f'//*[@id="main-area"]/div[7]/a[{i}+1]').click()
        # 결과 다음 페이지로 가는 구문, 다음페이지로 간 경우 iframe 은 선택 되어 있으므로
        # 스위치를 안써줘도 된다.

for 구문은 검색 결과 게시글 제목을 리스트로 저장, 출력하는 코드이다.

 

주석 처리한 내용은 게시글을 클릭해서 들어가고 다시 뒤로 오는 구문이다.

게시글 클릭 구문에 switch로 iframe 을 다시 선택해주어야한다.

그래야 뒤로가기로 결과창으로 돌아와서 다시 iframe 을 선택 해주기 때문이다.

 

꿀팁으로 switch 구문에서 에러가 잘 발생하는데

이유는 iframe이 이미 선택되어 있는 상태에서

switch로 iframe을 또 선택하면 에러가 발생한다.

 

한번에 코드를 잘 짜면 좋지만 많은 for 반복문으로 헷갈릴 때,

혹은 러프하게 코딩을 할 땐

 

아래와같이 오류가 있을 땐 실행을 안하고

선택을 해야하는 경우엔 실행이 되게끔

try / except 구문을 이용하자

 

    try:
        driver.switch_to.frame("cafe_main")
    except:
        pass

혹은 함수형태로 앞쪽에 만들어서 호출해서 사용하면 된다.

def iframe():
    try:
        driver.switch_to.frame("cafe_main")
    except:
        pass

 

 

# 4. 결과

 

+ 내용 추가 

 첫 for 문의 beatifulsoup 관련 사용법

 

for i in range(1, 3):
    req = driver.page_source
    soup = BeautifulSoup(req, 'html.parser')
    titles = soup.select("#main-area > div:nth-child(7) > table > tbody > tr")

ㅇ여기서 soup.select ("게시물제목의 copyselect주소")

주소는 copyselector 로 가져옵니다.

첫 번째와 두 번째 게시물의 copyselector 주소를 가져와 보면

# 리스트 타이틀 참조 -copyselector
첫번째 : # main-area > div:nth-child(7) > table > tbody > tr:nth-child(1) > td.td_article > div.board-list > div > a
두번째 : # main-area > div:nth-child(7) > table > tbody > tr:nth-child(2) > td.td_article > div.board-list > div > a

 

 

 

위와 같습니다. tr:nth-child(1) / (2) 이 부분이 다른 걸 봐서 게시글이 다음으로 갈 수록

숫자가 커진다는 것을 알 수 있습니다

 

따라서 앞쪽의 주소만 따와서

titles = soup.select('#main-area > div:nth-child(7) > table > tbody > tr') 으로 작성합니다.

 

그리고나서 이제 남은 부분인  'td.td_article > div.board-list > div > a' 이 부분은

그 아래 for문에서 쓰입니다.

 

    for title in titles:
        list = title.select_one(' td.td_article > div.board-list > div > a').text
        list2 = ''.join(list.split())
        list3.append(list2)

for문을 공부하셨으면 아래 구문의 title에 titles (리스트)의 항목이 차례대로 들어가면서 반복 되는 것을 알 수 있으실 겁니다요

1번째 실행에 # main-area > div:nth-child(7) > table > tbody > tr:nth-child(1)

2번째 실행에 # main-area > div:nth-child(7) > table > tbody > tr:nth-child(2)

...

그럼이제 그 항목 중에 글자만 따오기 위해서

select_one('항목').text 를 작성 해줄 것 입니다.

항목은 위에서 짤라 쓴 주소의 뒷 부분이 됩니다.

# main-area > div:nth-child(7) > table > tbody > tr:nth-child(1) > td.td_article > div.board-list > div > a

그래서 list = title.select_one(' td.td_article > div.board-list > div > a').text 이 됩니다요.

 

하지만 위의 copyselect 주소는 인터넷 사이트마다 다릅니다.

다른 네이버 카페에서는 copyselector 주소가 다르므로

연습하고자 하는 카페의 주소를 가져와야 쓰실 수 있으십니다요!!!

 

참고로 아래의 xpath도 마찬가지 입니다!!

    if i<2:
        driver.find_element_by_xpath(f'//*[@id="main-area"]/div[7]/a[{i}+1]').click()

driver.find_element_by_xpath(f'//*[@id="main-area"]/div[7]/a[{i}+1]').click()

//*[@id="main-area"]/div[7]/a[{i}+1] 이 주소는 아래와 같이 가져옵니다.

 

1페이지 , 2페이지의 다름을 알기 위해 두개를 가져와보니

.# 페이지 번호 XPATH 참조
# //*[@id="main-area"]/div[7]/a[1]
# //*[@id="main-area"]/div[7]/a[2]
# //*[@id="main-area"]/div[7]/a[3]

 

맨 뒷부분의 숫자만 다르군요!!

제가 반복하고자 하는 범위는 맨 윗쪽 for문 에 적었습니다.

for i in range(1, 3):  # 두 번째 페이지까지만 반복합니다..

 

driver.find_element_by_xpath(f'//*[@id="main-area"]/div[7]/a[{i}+1]').click()

f ' ' 로 변수 {i} 를 받아왔습니다

(f'' 스트링은 내용 안쪽에 변수를 {변수} 형태로 작성 가능하게합니다.)

이제 i값에 따라 페이지 번호가 변하겠네요!

 

i 가 1 이면 첫번째 페이지이므로 이미 첫 페이지니까 +1을 해줘서 

두 번째 페이지로 이동합니다.

 

i 가 2 이면 세번 째 페이지로 가지는데 그러고 싶지 않아서

if i < 2 로 제한을 주었습니다.

 

 

설명이 충분했으면 좋겠어요,,

저도 이제 공부 시작한 코린이라ㅎㅎㅎ

우리 힘내요👍👍

 

++내용추가 : 댓글 작성 방법

게시글에 들어가서 댓글을 작성하는 방법입니다.

 

1. 먼저 댓글창의 xpath를 가져옵니다.

2. 같은 방법으로 등록 버튼의 xpath도 가져옵니다.

 

ㅇ댓글 텍스트 작성공간
//*[@id="app"]/div/div/div[2]/div[2]/div[5]/div[2]/div[1]/textarea

ㅇ댓글 등록 버튼
//*[@id="app"]/div/div/div[2]/div[2]/div[5]/div[2]/div[2]/div[2]/a

 

3. 이제 다 됐습니다. 작성공간에 글자만 써주고나서 바로 등록을 누르게 코드를 입력해주면 끝!

*게시글 들어가고 로딩되는 약간의 시간(0.5)을 주었습니다

time.sleep(0.5)
driver.find_element_by_xpath('//*[@id="app"]/div/div/div[2]/div[2]/div[5]/div[2]/div[1]/textarea').send_keys("여기에 댓글")
driver.find_element_by_xpath('//*[@id="app"]/div/div/div[2]/div[2]/div[5]/div[2]/div[2]/div[2]/a').click()

 

 

🚩댓글을 못달고 에러가 뜨는 이유

네이버 카페는 로그인이 되고 해당 게시물에 들어갔을 때 댓글창이 보여야 댓글의 xpath를 찾을 수 있다.

해결하려면 로그인을 해야하는데 문제점이 2가지있다.

  1. 로그인 할 계정에 2차 인증기능이 켜져있으면 안된다. -> 네이버 계정 보안 설정에서 2차인증을 끄자
  2. 인터넷에 많이 올라와 있는 네이버 로그인 하는 법에는 아래와 같은 문제가 있다 
driver.get("https://nid.naver.com/nidlogin.login?url=http://section.cafe.naver.com")
time.sleep(1)

user_id = driver.find_element_by_id("id")
user_id.send_keys("아이디입력")
password = driver.find_element_by_id("pw")
password.send_keys("비밀번호입력")
password.submit()
time.sleep(1)
driver.find_element_by_id("new.dontsave").click()

위와 같은 코드는 많이 볼 수 있는데 정작 실행하면 자동입력 방지문자를 입력하라고 한다,,,,,ㅠㅠ

 

그래서 해결하는 방법을 찾았다. 좀 귀찮은 방법이지만 작동은 되니까,,,,

다른 아이디어 있으면 알려주세요

네이버 로그인 문제점 해결

네이버 로그인 방법엔 일반 아이디로그인과 QR로그인, 그리고 일회용 번호 로그인 방법이 있다.

그 중 일회용 로그인을 이용해서 접속을 하면 된다.

  • 해결법 

휴대폰으로 네이버 앱을 깔고 로그인 할 아이디의 2차 인증은 미리 해제해주세요.

로그인을 한 뒤 다음과 같이 일회용 로그인 번호 받기 창으로 이동해준다.

 

 

요런 상태까지 했으면 로그인하는 코드를 맨 위에 작성하자

다음과 같이 코드를 입력한다.

 

driver = webdriver.Chrome("./chromedriver")

"""
네이버 일회용 로그인
"""
driver.get("https://nid.naver.com/nidlogin.login?url=http://section.cafe.naver.com")
time.sleep(1)

driver.find_element_by_id("log.otn").click()
user_id = driver.find_element_by_id("disposable")
user_id.send_keys("띄어쓰기 없이 일회용 번호를 입력해주세요")
driver.find_element_by_id("otnlog.login").click()
time.sleep(2)

폰의 일회용 번호시간이 넉넉할 때

잽싸게 위의 번호입력하는 곳에 입력하고

실행하면 로그인이 성공한다.

 

전체코드는 다음과 같다.

 

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
from bs4 import BeautifulSoup


driver = webdriver.Chrome("./chromedriver")

"""
네이버 일회용 로그인
"""
driver.get("https://nid.naver.com/nidlogin.login?url=http://section.cafe.naver.com")
time.sleep(1)


driver.find_element_by_id("log.otn").click()
user_id = driver.find_element_by_id("disposable")
user_id.send_keys("15597038")
driver.find_element_by_id("otnlog.login").click()
time.sleep(2)

exit(0)

driver.get("https://cafe.naver.com/joonggonara")
driver.implicitly_wait(3)

driver.find_element_by_name('query').send_keys('과자')
driver.find_element_by_name("query").send_keys(Keys.ENTER)
time.sleep(2)

driver.switch_to.frame("cafe_main")

for i in range(1, 3):
    req = driver.page_source
    soup = BeautifulSoup(req, 'html.parser')
    titles = soup.select("#main-area > div:nth-child(7) > table > tbody > tr")

    for a in range(1, 2):
        driver.find_element_by_xpath(f'//*[@id="main-area"]/div[5]/table/tbody/tr[{a}]/td[1]/div[2]/div/a').click()
        time.sleep(3)
        driver.find_element_by_xpath('//*[@id="app"]/div/div/div[2]/div[2]/div[5]/div[2]/div[1]/textarea').send_keys(
            "여기에 댓글")
        driver.find_element_by_xpath('//*[@id="app"]/div/div/div[2]/div[2]/div[5]/div[2]/div[2]/div[2]/a').click()
        time.sleep(0.5)

        driver.back()
        time.sleep(2)
        driver.switch_to.frame("cafe_main")
    if i < 2:
        driver.find_element_by_xpath(f'//*[@id="main-area"]/div[7]/a[{i}+1]').click()

    def iframe():
        try:
            driver.switch_to.frame("cafe_main")
        except:
            pass

 

 

 

 

 

 

 

 

2021.04.08 - [python/Selenium 셀레니움] - [selenium] 크롤링 selenium 셀레니움 사용법, 명령어 모음

 

[selenium] 크롤링 selenium 셀레니움 사용법, 명령어 모음

#0. 셀레니움 실행을 위한 chrome 드라이버 다운로드 사용중인 chrome 버전으로 드라이버를 다운로드 한다. 크롬 버전 확인 (주소창에 복붙) chrome://version 크롬 드라이버 다운로드 링크 chromedriver.chromi

gorokke.tistory.com

2021.03.29 - [python/beatifulsoup 뷰티풀스프] - [python]파이썬 크롤링, mongodb, html, flask, bs4 활용

 

[python]파이썬 크롤링, mongodb, html, flask, bs4 활용

1. API 역할 url 받고 메시지 호출 2.  로딩이 되면 자동 요청 되는 부분 $(document).ready( function() {  함수이름();  }) #1. 서버쪽 (app.py)  : robo 3T (mongodb 에 저장 된 내용) 정보를..

gorokke.tistory.com

 

반응형

댓글