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 님께서 말씀해주신 부분도 시간이 나는대로 수정해보겠습니다!


  1. AWSCLI, in a single file (portable, linux)

    Date2021.04.10 Category코드 BySeia Views312
    Read More
  2. [Python-Gnuboard] 파이썬으로 구현한 그누보드 자동 글쓰기 함수

    Date2021.04.08 Category코드 By이니스프리 Views1506
    Read More
  3. [Python] 휴일지킴이 약국을 크롤링하여 Folium 지도에 마커로 표시하는 PyQt 윈도우 앱

    Date2021.03.13 Category코드 By이니스프리 Views1254
    Read More
  4. 도박 중독자를 위한 광고 차단 규칙

    Date2020.08.21 Category코드 By제르엘 Views440
    Read More
  5. [Python] 유튜브 영상을 다운받아 일정 간격으로 캡쳐하여 10장씩 merge하기

    Date2020.05.27 Category코드 By이니스프리 Views1244
    Read More
  6. [Python/Telegram] Studyforus 알림봇 (댓글, 스티커 파싱)

    Date2020.05.15 Category코드 By이니스프리 Views833
    Read More
  7. [Python] url 주소로부터 IP 주소 알아내기

    Date2020.02.20 Category코드 Bytitle: 황금 서버 (30일)humit Views2223
    Read More
  8. [Python] 네이버 실시간 검색어

    Date2020.01.23 Category코드 Bytitle: 황금 서버 (30일)humit Views1321
    Read More
  9. Koa에서 자동으로 라우팅 채워주기

    Date2020.01.22 Category코드 BySeia Views662
    Read More
  10. JavaScript에서 파이썬 문자열 처리 함수 중 하나 (바인딩)를 구현

    Date2020.01.20 Category코드 BySeia Views564
    Read More
  11. [Python] Google Image Search 결과를 받아오기

    Date2019.12.09 Category코드 By이니스프리 Views1189
    Read More
  12. [파이썬] Requests를 사용한 네이버 카페 크롤링 - 일정수 이상의 리플이 달린 게시글만 텔레그램 알림

    Date2019.11.17 Category코드 By이니스프리 Views4347
    Read More
  13. [JS] 클라이언트단 GET Parameter

    Date2019.11.16 Category코드 ByHanam09 Views656
    Read More
  14. [Python] 싸이월드 미니홈피 백업 스크립트

    Date2019.11.07 Category코드 By이니스프리 Views2446
    Read More
  15. [Python] PIL을 이용한 Animated GIF의 리사이징

    Date2019.11.03 Category코드 By이니스프리 Views1317
    Read More
  16. [PyQt] sir.kr에서 스크랩한 게시글을 보여주는 윈도우앱 (검색 및 정렬 가능)

    Date2019.08.09 Category코드 By이니스프리 Views1126
    Read More
  17. [아미나] Dropbox API를 이용한 이미지 호스팅 보드스킨

    Date2019.07.13 Category코드 By이니스프리 Views1593
    Read More
  18. [Python] 네이버 모바일 이미지 검색에서의 이미지 파일을 멀티스레드로 다운받고 1개의 파일로 병합

    Date2019.07.12 Category코드 By이니스프리 Views1473
    Read More
  19. [PHP/Javascript] 아미나에 자동으로 게시글을 생성하고 Ajax로 전송하여 결과를 표시하기

    Date2019.07.09 Category코드 By이니스프리 Views925
    Read More
  20. [Python] Selenium을 이용하여 특정 element를 캡처하는 스크립트

    Date2019.07.03 Category코드 By이니스프리 Views6105
    Read More
Board Pagination Prev 1 2 3 4 Next
/ 4