Extra Form
라이선스 기타(따로 작성)

안녕하세요? 비가 많이 내리는데 주말 잘 보내고 계시는가요? ^^


구글링해보면 selenium을 이용한 네이버 카페 크롤링 글은 많이 나오지만, requests를 이용한 예제는 흔하지 않더군요.


크롤링 연습을 하기 위해 requests 라이브러리만 사용하여 네이버 카페의 게시글 목록을 크롤링하여


일정수 이상의 리플이 달린 게시글만 텔레그램 알림을 하는 스크립트를 작성해보았습니다.



우선 간편한 크롤링을 위해 네이버 '모바일' 페이지를 활용하였습니다.


PC버전에서 페이지 목록을 직접 클릭하는 것과 달리, 모바일에서는 '더보기' 버튼을 클릭해서 목록을 넘기는데요.


humit 님께서 가르쳐주신 방법을 활용하여 ajax에서의 의심스러운 요청(?)을 찾아보았습니다.


브라우저 개발자 도구의 네트워크 탭에서 XHR을 살펴보면 ArticleListAjax.nhn를 통해 다음과 같은 폼을 전송하는 것을 확인할 수 있습니다.


저같은 초보도 어렵지 않게 발견할 수 있더군요 ^^





테스트해보니 모든 폼을 필요로 하는 것 같지는 않고, 일부만 params로 넣어주면 작동하는 것을 확인하였습니다.


이를 바탕으로 https://m.cafe.naver.com/hotellife 카페의 신용카드 게시판을 예제로 하여 


게시글 목록 1~3페이지에서 리플이 70개 이상인 게시글 제목만 텔레그램으로 알림을 하는 크롤링을 해보겠습니다.


네이버 카페앱에는 이런 기능이 구현되어있지 않어서 실생활에 도움이 될 수 있을 것 같아서요 ^^



import requests, time, telegram, re
from bs4 import BeautifulSoup
 
## requests와 BeautifulSoup으로 카페 게시글 목록을 불러오는 함수 ##
def bs(url, page):
    headers = { # 헤더를 넣지 않아도 작동하는 것을 확인했습니다.
        'Content-Type': 'application/json; charset=utf-8',
        'Accept-Language': 'ko-KR,ko;q=0.9,en-US',
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36'
    }
    params = {
        'search.clubid' : '18786605', # 카페 ID
        'search.menuid' : '741', # 메뉴 ID
        'search.boardtype' : 'L', # 보드타입 (반드시 필요로 하는 것은 아닌 것 같습니다.)
        'search.page' : page # 불러올 페이지
    }
    html = requests.get(url, headers = headers, params = params)
    soup = BeautifulSoup(html.text, 'html.parser')
    return soup

## 게시글 제목과 리플 수를 파싱하여 리플 수가 일정 이상인 경우만 추출하는 함수 ##
def parse(url):
    page = 0
    result_title = []
    result_reply = []
    while page <= 3: # 1~3페이지를 불러옵니다.
        soup = bs(url, page)
        titles = soup.select('strong.tit') # 게시글 제목
        for title in titles:
            result_title.append(' '.join(title.text.strip().split()))
        replys = soup.select('em.num') # 리플 수
        for reply in replys:
            result_reply.append(reply.text)
        page += 1
        time.sleep(0.5)
    count = 0
    final_list = []
    while count < len(result_title):
        if int(result_reply[count]) >= 70: # 리플이 70개가 넘는 글만 리스트에 담습니다.
            final_list.append(result_title[count] + ' (' + result_reply[count] + ')')
        count += 1
    return final_list

## 최종결과 문자열에서 리플 수를 제외하는 정규식 처리 ##
def regex(string):
    try: # 괄호 안 숫자가 두 글자인 경우
        if re.compile(r'.+(?<=\(\d{2}\))').search(string).end() > 0:
            result = string[:-5]
    except: # 괄호 안 숫자가 세 글자인 경우 (네 글자인 경우는 상정하지 않았습니다.)
        try:
            if re.compile(r'.+(?<=\(\d{3}\))').search(string).end() > 0:
                result = string[:-6]
        except:
            pass
    return result

## TXT 파일로 결과를 저장하고 텔레그램으로 새 글을 알리는 함수 ##
def telegram_bot(titles):
    bot = telegram.Bot(token='토큰을 입력하세요')
    try:
        chat_id = bot.getUpdates()[-1].message.chat.id
    except:
        chat_id = 챗아이디를 입력하세요 # 알림이 장시간 없는 경우에 발생하는 에러를 방지합니다.

    try:
        lines = [line.rstrip('\n') for line in open('ncafe.txt', 'r', encoding='utf8')]
    except: # 파일이 존재하지 않는 경우를 예외처리합니다. (처음 실행하는 경우 에러가 발생하기 때문입니다.)
        lines = ['no data']
   
    check = 0
    with open('ncafe.txt', 'w', encoding='utf8') as f: # TXT 파일을 업데이트합니다.
        for title in titles:
            for line in lines:
                if regex(title) == regex(line):
                    check = 1
            if check == 0: # 새 글만 텔레그램으로 알립니다.
                bot.sendMessage(chat_id=chat_id, text=title)
                print(title)
            f.write(title  + '\n')

if __name__ == '__main__':
    url = 'https://m.cafe.naver.com/ArticleAllListAjax.nhn'
    titles = parse(url)
    telegram_bot(titles)



게시글 제목과 리플 수를 한꺼번에 telegram_bot() 함수로 넘겨주다보니 리플 수가 업데이트 되어도 알림이 오는 문제가 발생하더군요 ㅠㅠ


이를 해결하기 위해 regex() 함수를 넣어서 제목만 따로 분리하여 if 문에서 동일한지 여부를 판단하는 방식을 택했습니다.


이는 게시글 제목과 리플 수를 따로 넘겨주어 처리하면 보다 간편히 처리할 수 있으므로 사실상 redundant한 부분인데 


제가 정규식 후방탐색을 연습하기 위해 의도적으로 넣은 것입니다 ^^;



스포어에는 파이썬 고수님들도 많이 계시던데 저같은 아마추어의 허접한 스크립트를 번번이 읽어주셔서 감사합니다 :)


그럼 즐거운 주말 되시고, 이번주에는 서울도 영하 4도까지 기온이 떨어진다는데 다들 감기 조심하세요!


  • profile
    title: 황금 서버 (30일)humit 2019.11.17 17:00
    이니스프리님의 말대로 따로 넘겨주어도 되지만 저의 경우에는 제목과 리플을 tuple 형태로 만들어서 리스트에 추가하는 식으로 만듭니다.
    + 해당 문제의 경우 정규식을 사용할 때 후방 탐색을 하지 않더라도 $를 활용하여 똑같은 기능을 할 수 있도록 코드를 작성할 수도 있습니다. (댓글 수가 맨 뒤에 오기 때문)
    re.compile('\\((\d+)\\)$').search('게시물 제목(1) (10)').group(1)
  • profile
    이니스프리 2019.11.17 17:55
    오오~ 감사합니다!!
    역시 컴퓨터 공학을 하려면 머리가 좋아야 되는 것 같네요 ㄷㄷ

    1.
    여태껏 게시글 목록에서 두 개의 요소를 불러오는 경우에 딕셔너리를 사용해본 적은 있는데
    튜플을 리스트에 넣어주는 방법은 제가 생각조차 못했네요! ㅎㄷㄷ
    앞으로 활용하여 연습해보겠습니다~

    2.
    말씀해주신대로 $를 활용하면 간단하게 해결되는 문제인데, 제가 오래간만에 정규식을 사용하다보니 삽질을 했네요 ㅠㅠ
    후방탐색을 사용하니 \d{2,3} 같이 길이가 고정되지 않는 용법이 제한되더군요 ㅜㅜ

    그럼 비가 많이 오는데 humit 님께서도 즐거운 주말 저녁 되세요!
    항상 감사드립니다 :)
  • profile
    이니스프리 2019.11.20 21:44

    리플 70개를 넘은 글이 공지로 올라간 경우, 알림이 중복으로 오는 문제가 있는데요 ㅠㅠ


    모바일에서는 공지에서 안 보이는데, PC에서는 보이는 문제와도 아마도 관련이 있을 것 같네요.


    우회적이긴 하지만 다음과 같이 70~79행 부분을 처리하면 중복으로 알림이 오는 문제는 해결할 수 있군요.

    (덧붙여 속도향상을 위해 break 문도 넣었습니다.)


        message_list = []
        with open('ncafe.txt', 'w', encoding='utf8') as f: # TXT 파일을 업데이트합니다.
            for title in titles:
                check = 0
                for line in lines:
                    if regex(title) == regex(line):
                        check = 1
                        break
                if check == 0: # 새 글만 텔레그램으로 알립니다.
                    message_list.append(title)
                f.write(title  + '\n')
        message_list = list(dict.fromkeys(message_list)) # 중복된 요소를 제거합니다.
        for message in message_list:
            bot.sendMessage(chat_id=chat_id, text=message)
            print(message)


    humit 님께서 말씀해주신 부분도 시간이 나는대로 수정해보겠습니다!


List of Articles
번호 분류 제목 글쓴이 날짜 조회 수
78 코드 AWSCLI, in a single file (portable, linux) 1 file Seia 2021.04.10 309
77 코드 [Python-Gnuboard] 파이썬으로 구현한 그누보드 자동 글쓰기 함수 1 file 이니스프리 2021.04.08 1502
76 코드 [Python] 휴일지킴이 약국을 크롤링하여 Folium 지도에 마커로 표시하는 PyQt 윈도우 앱 7 file 이니스프리 2021.03.13 1252
75 코드 도박 중독자를 위한 광고 차단 규칙 file 제르엘 2020.08.21 440
74 코드 [Python] 유튜브 영상을 다운받아 일정 간격으로 캡쳐하여 10장씩 merge하기 3 file 이니스프리 2020.05.27 1241
73 자료 [Autohotkey] 매분 정각에 전체화면을 캡쳐하는 스크립트 4 file 이니스프리 2020.05.22 1239
72 코드 [Python/Telegram] Studyforus 알림봇 (댓글, 스티커 파싱) 7 file 이니스프리 2020.05.15 829
71 코드 [Python] url 주소로부터 IP 주소 알아내기 title: 황금 서버 (30일)humit 2020.02.20 2218
70 코드 [Python] 네이버 실시간 검색어 3 title: 황금 서버 (30일)humit 2020.01.23 1320
69 코드 Koa에서 자동으로 라우팅 채워주기 Seia 2020.01.22 662
68 코드 JavaScript에서 파이썬 문자열 처리 함수 중 하나 (바인딩)를 구현 7 Seia 2020.01.20 564
67 코드 [Python] Google Image Search 결과를 받아오기 file 이니스프리 2019.12.09 1187
» 코드 [파이썬] Requests를 사용한 네이버 카페 크롤링 - 일정수 이상의 리플이 달린 게시글만 텔레그램 알림 3 file 이니스프리 2019.11.17 4342
65 코드 [JS] 클라이언트단 GET Parameter Hanam09 2019.11.16 656
64 코드 [Python] 싸이월드 미니홈피 백업 스크립트 1 이니스프리 2019.11.07 2446
63 코드 [Python] PIL을 이용한 Animated GIF의 리사이징 file 이니스프리 2019.11.03 1314
62 코드 [PyQt] sir.kr에서 스크랩한 게시글을 보여주는 윈도우앱 (검색 및 정렬 가능) 7 file 이니스프리 2019.08.09 1126
61 코드 [아미나] Dropbox API를 이용한 이미지 호스팅 보드스킨 12 file 이니스프리 2019.07.13 1590
60 코드 [Python] 네이버 모바일 이미지 검색에서의 이미지 파일을 멀티스레드로 다운받고 1개의 파일로 병합 11 file 이니스프리 2019.07.12 1473
59 코드 [PHP/Javascript] 아미나에 자동으로 게시글을 생성하고 Ajax로 전송하여 결과를 표시하기 2 file 이니스프리 2019.07.09 924
Board Pagination Prev 1 2 3 4 Next
/ 4