• 목록
  • 아래로
  • 위로

첨부 1

  1. image 20191117145927.jpg (File Size: 48.7KB/Download: 22)
라이선스 기타(따로 작성)

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


구글링해보면 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도까지 기온이 떨어진다는데 다들 감기 조심하세요!


작성자
이니스프리 95 Lv. (61%) 731420/737280EXP

ཇོ་མོ་གླང་མ

댓글 3

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

신고

"humit님의 댓글"

이 댓글을 신고 하시겠습니까?

이니스프리 작성자 → humit
profile image
오오~ 감사합니다!!
역시 컴퓨터 공학을 하려면 머리가 좋아야 되는 것 같네요 ㄷㄷ

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

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

그럼 비가 많이 오는데 humit 님께서도 즐거운 주말 저녁 되세요!
항상 감사드립니다 :)
comment menu
2019.11.17. 17:55

신고

"이니스프리님의 댓글"

이 댓글을 신고 하시겠습니까?

이니스프리 작성자
profile image

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

comment menu
2019.11.20. 21:44

신고

"이니스프리님의 댓글"

이 댓글을 신고 하시겠습니까?

권한이 없습니다.
번호 분류 제목 글쓴이 날짜 조회 수
76 코드 [PHP] 이미지를 원하는 크기(원본비율 유지)로 리사이즈 하여 출력 (원본 이미지는 수정하지 않습니다) 6 이니스프리 이니스프리 18.12.20.18:42 3030
75 코드 새 글 자동 댓글 스크립트 (AutoHotkey) 9 이니스프리 이니스프리 17.11.26.20:22 1999
74 코드 파이썬을 이용한 텔레그램 새 글 알림 (허접합니다) 5 이니스프리 이니스프리 17.11.19.18:55 1806
73 코드 [Python] 텔레그램을 이용한 게시판 새 글 알림봇 7 이니스프리 이니스프리 18.12.02.16:25 1697
코드 [파이썬] Requests를 사용한 네이버 카페 크롤링 - 일정수 이상의 리플이 달린 게시글만 텔레그램 알림 3 image 이니스프리 이니스프리 19.11.17.15:20 1538
71 코드 [Python] Selenium을 이용하여 특정 element를 캡처하는 스크립트 2 image 이니스프리 이니스프리 19.07.03.20:17 1485
70 코드 [Python] 싸이월드 미니홈피 백업 스크립트 이니스프리 이니스프리 19.11.07.20:06 1190
69 코드 [오토핫키] 구글 드라이브의 공유링크를 이미지 호스팅을 위한 다이렉트 링크로 바꿔주는 스크립트 10 image 이니스프리 이니스프리 18.09.25.16:54 1095
68 코드 엑셀 읽어서 그래프 그려주는 함수 2 왕뚠뚠돈까스라이츄 title: 인스타그램왕뚠뚠돈까스.. 17.08.03.22:06 1045
67 코드 CMD로 로컬 연결 고정 IP 설정하기 1 humit title: 황금 서버 (30일)humit 18.02.06.11:33 879
66 코드 유튜브에 약간의 기능을 추가 해주는 크롬 확장 프로그램. 11 image Hanam09 Hanam09 18.01.26.22:01 830
65 코드 [오토핫키] 특정 사이트에 대한 ping 테스트 결과를 실행시간과 함께 로그 파일로 저장하는 스크립트 2 이니스프리 이니스프리 18.09.22.10:59 771
64 코드 잘못 쓰면 컴퓨터가 날아가는 코드 29 제르엘 title: 생일 케이크 (1년권)제르엘 18.07.08.22:36 740
63 자료 even_move - 감성적인 에러 페이지 7 image TVJ title: 열려라 맛스타의 자물쇠TVJ 17.08.08.22:36 669
62 자료 소셜XE / 기존 통합 로그인 스킨 V2.2 2 image 맛스타 title: 생일 케이크 (1년권)맛스타 17.06.28.00:10 648
61 자료 AdminLTE용 에디터 스타일 4 file title: 은메달도다 17.07.07.20:48 620
60 코드 [Python] 네이버 모바일 이미지 검색에서의 이미지 파일을 멀티스레드로 다운받고 1개의 파일로 병합 11 image 이니스프리 이니스프리 19.07.12.01:22 617
59 코드 엑셀파일 불러서 히스토그램 그려주는 함수 왕뚠뚠돈까스라이츄 title: 인스타그램왕뚠뚠돈까스.. 17.08.03.21:53 599
58 코드 사이트 서버 이전 (또는 미러링 사이트 구축) 쉽게하는 스크립트 1 맛스타 title: 생일 케이크 (1년권)맛스타 18.01.14.01:09 598
57 코드 [아미나] Dropbox API를 이용한 이미지 호스팅 보드스킨 11 image 이니스프리 이니스프리 19.07.13.10:01 593