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 자료 AdBlock 접근 방지 애드온 v0.1 3 file 네모 2017.10.05 745
77 자료 AdminLTE용 에디터 스타일 4 file title: 은메달도다 2017.07.07 837
76 코드 AWSCLI, in a single file (portable, linux) 1 file Seia 2021.04.10 312
75 코드 c 이진트리 전,중,후위 알고리즘 2 title: 대한민국 국기gimmepoint 2018.04.24 644
74 코드 Cmd 에서 서비스 시작 / 종료하기 1 ProjectSE 2018.02.18 698
73 코드 CMD로 로컬 연결 고정 IP 설정하기 1 title: 황금 서버 (30일)humit 2018.02.06 1092
72 코드 C언어 삼중자를 이용한 코드 title: 황금 서버 (30일)humit 2018.07.22 484
71 자료 even_move - 감성적인 에러 페이지 7 file title: 열려라 맛스타의 자물쇠TVJ 2017.08.08 897
70 자료 Gentelella 3 file NoYeah 2017.06.29 1061
69 자료 Gentelella 레이아웃에 사용가능한 가격 테이블 위젯입니다. 3 file NoYeah 2017.07.03 694
68 코드 Git 저장소에서 자동으로 받아 업데이트하는 쉘 스크립트 5 NoYeah 2017.09.16 769
67 코드 Hello, World!를 출력해보자 18 네모 2018.04.21 661
66 코드 HEX를 RGB로, RGB를 HEX로 바꾸는 PHP 코드 1 네모 2018.05.05 678
65 코드 html 초보가 만든 자소서 4 title: 대한민국 국기gimmepoint 2018.04.21 739
64 코드 JavaScript에서 파이썬 문자열 처리 함수 중 하나 (바인딩)를 구현 7 Seia 2020.01.20 564
63 코드 Koa에서 자동으로 라우팅 채워주기 Seia 2020.01.22 662
62 자료 RBGE - 이쁘고 깔끔한 에러페이지 4 file title: 열려라 맛스타의 자물쇠TVJ 2017.08.08 771
61 자료 [1.8a] Bootstrap 'Panel' 위젯 스타일 1 file title: 은메달도다 2017.08.09 678
60 자료 [Autohotkey] 매분 정각에 전체화면을 캡쳐하는 스크립트 4 file 이니스프리 2020.05.22 1242
59 자료 [Bootstrap] xeACE 레이아웃 3 title: 은메달도다 2017.09.17 727
Board Pagination Prev 1 2 3 4 Next
/ 4