메뉴 닫기

[파이썬(Python)] RSS PostChecker 개발 과정

프로그램 기획
어제 3시간, 오늘 4시간정도 걸려서 프로그램을 완성시키고 깃허브 액션에 등록했다.
전체 프로그램 코드는 모든 블로그에서 정상작동하는게 확인되면 공유할 예정이다.

기능

  1. RSS 파싱
  2. 게시물 날짜와 오늘 날짜 비교
  3. 오늘 올라온 게시물 정보 검색
  4. 구글드라이브에 게시물 정보 기록
  5. 과거 게시물 검색
  6. 이메일 알림 또는 메신저 알림

프로그램 설계

크게 3가지 클래스를 가지고 시스템을 구현했다. 블로그 게시물 정보를 담을 수 있는 Post 클래스, Post를 관리할 수 있는 기능의 Blog 클래스, 블로그를 관리하는 Manager 클래스이다.

Manager 클래스가 각각의 블로그를 등록해서 관리하고, Blog는 Post를 관리하는 구조이다.

전체적인 프로그램 동작 흐름

  1. 블로그 정보를 입력하여 블로그 클래스를 생성한다.
    blog1= Blog(‘소유자명’, ‘블로그 rss주소’, id)
    여기서 id 는 단순히 스프레드 시트에 이름순으로 정렬했을 때 오는 인덱스 위치이다.
  2. Manager 클래스를 생성한다.
    manager = Manager()
  3. 생성된 블로그 클래스를 매니저 클래스에 등록한다.
    manager.register (blog1)
  4. Manager클래스에서 각 블로그가 자신의 포스트 목록을 업데이트 하도록 하는 메소드를 실행시킨다.
    manager.update_all()
  5. 업데이트 이후 오늘 작성한 글이 있으면, 구글 스프레드 시트에 작성하게 하는 메소드를 실행시킨다.
    manager.check_daily()
  6. 프로그램이 종료된다.

사실 어제 거의 대부분의 기능은 완성을 해서 금방 끝날줄 알았다. 어제 대부분의 설계와 구현을 끝내고 구글시트에 RSS 정보를 가지고 요약된 내용을 적는 부분과 깃허브에서 스케쥴러를 등록해서 주기적으로 돌아가게 하는 부분만 하면 되었다.

블로그 클래스 – bs4, requests 로 RSS파싱

rss를 가지고 blog글을 찾는건 간단하다. request와 bs4를 가지고 xml을 파싱하고 날짜를 비교하면 된다.

from bs4 import BeautifulSoup as bs
import requests

post =[] 
r = requests.get("블로그 RSS 주소")
soup = bs(r.content, features='xml')
articles = soup.findAll('item')
for a in articles:
    posts.append(Post(a.title, a.description, a.pubDate, a.link))

위에처럼 request 를 xml로 파싱해서 article 별로 해당하는 title, description, pubDate, link를 찾아서 저장할 수있다.
나는 Post객체를 생성해서 객체안에 제목, 설명, 발행일자, 링크를 저장했다. 그리고 이 postlist를 블로그 객체에서 보관하도록 했다.

RSS의 시간정보와 현재 날짜 비교하기

다음 부분은 RSS의 pubDate와 현재 날짜를 비교하는 부분인데 이부분이 생각보다 신경써야 할게 많다. 시간대(Timezone)를 고려해서 코드를 작성해줘야 한다. 우선 pubDate를 datetime 객체로 저장하였다.

RSS 2.0의 pubDate같은 경우는 Sat, 06 Feb 2021 05:32:09 +0000 이런 형태로 표시된다. velog는 …. #이슈 참고
시간 비교를 위해서는 이걸 datetime 객체로 변환해주어야 하는데 datetime 패키지에서 strptime() 이라는 메소드가 있다.
이 메소드를 활용하면 string 문자열 타입의 시간 정보와 포맷을 입력해주면 datetime 객체로 변환해준다.

datetime.strptime(pubDate형식의 시간문자열, "%a, %d %b %Y %H:%M:%S %z").astimezone(timezone('Asia/Seoul'))

그런데 여기서 astimezone 을 이용해서 시간대를 변경해주지 않으면 나중에 시간을 비교할 때 문제가 생기기 때문에, astimezone 을 이용해서 변환해 서울 시간대로 변환해주었다.

시간을 비교하는 부분 현재 날짜와 비교해서 날짜가 같으면 리스트에 저장해서 반환한다.

postlist = []
for post in self.posts:
    # print(post.get_date(), date , post.get_date==date)
    if post.get_date() == date:
        postlist.append(post)
# print(postlist)
return postlist

Manager 클래스

블로그 클래스에서 파싱한 포스트 데이터와 현재 날짜를 비교하고, 이를 구글 시트에 기록한다.

wks.update_value((10, self.day + 1), datetime.now(timezone('Asia/Seoul')).strftime("%-d일 %H시 %M분"))
for blog in blogs:
    print(f"checking {blog.author}'s posts {blog.id}/5")
    # article = blog.get_latest_article()
    write_on_sheet(blog)

첫번째 줄의 코드는 오늘 날짜를 제일 시트 첫번째 열에 기록한다.
blogs는 블로그 리스트로 blog를 각각 순회하면서 write_on_sheet 메소드에 블로그를 담아 실행시킨다.

 def write_on_sheet(blog):
        posts = blog.find_posts_by_date(self.date)
        temp = ""
        if len(posts) == 1:
            post = posts[0]
            wks.update_value((blog.id + 1, day + 1), f'=HYPERLINK("{post.hyperlink()}","{post.title}")')
        elif len(posts) == 0:
            wks.update_value((blog.id + 1, day + 1), "미확인")
        else:
            for post in posts:
                temp = temp.join(post.summary())
                temp = temp.join("\n")
            wks.update_value((blog.id + 1,day + 1), temp)

wirte_on_sheet 는 구글 시트에 오늘 날짜로 등록된 블로그 포스트를 검색해서 작성한다.
포스트가 1개도 없으면 미확인 으로 작성하고, 1개면 블로그 셀에다가 블로그 링크를 걸어준다.
2개이상일 경우에는 글 제목, 작성시간 등이 담긴 summary를 입력해준다.
2개 이상이면 셀에 하이퍼링크를 거는것이 불가능하기 때문에 저런식으로 하였다.
셀을 추가할 수도 있겠지만, 그렇게 되면 로직이 너무 복잡해져서 저런식으로 진행하였다.

Pygsheets 사용법

import pygsheets
from datetime import datetime
gc = pygsheets.authorize(service_account_env_var="GDRIVE_API_CREDENTIAL")
sh = gc.open(GOOGLE_SPREADSHEET_NAME)
wks = self.sh.worksheet_by_title(str(self.month) + '월')
wks.update_value((1, self.day + 1), datetime.today().strftime("%-m월%-d일"))

우선 pygsheets를 사용하려면 구글 API에서 키를 발급받아야 한다.
인증 정보가 있어야만 pygsheets를 사용할 수 있다.
구글 드라이브 api와 구글 시트 api를 사용설정해야한다.
구글 API설정법 참고
먼저 pygsheets.authorize 이부분은 인증정보를 설정하는 부분이다.
서버에서 실행하기 위해서 service_account_env_var 을 써서 환경변수 이름을 전달해주었다.
보통은 개인 컴퓨터에서 실행시킨다면 env_var이 아니라

pygsheets.authorize(service_file='path/to/service_account_credentials.json')

service_file구글 인증정보가 담긴 json 파일의 주소를 입력한다.

gc = pygsheets.authorize 이 파트는 구글 클라우드 수준의 접근이고,

sh =gc.open(스프레드시트이름) 이부분은 스프레드 시트 파일의 이름을 가지고 파일을 여는 수준의 접근이다.
wks 워크시트는 이 스프레드 시트의 시트 명을 가지고 접근하며 이 단계까지 들어와야 구글시트에 데이터를 입력하거나 받아갈 수 있다.
자세한건 api 문서를 참조하시라.

기타 자잘한 issue 및 주의사항

velog 의 경우 rss 2.0 을 준수하지 않아서 오류가 발생했다.
description.text로 접근이 되지 않고 description 자체로 접근해야했다.
날짜 포맷같은 경우도 “%a, %d %b %Y %H:%M:%S %z” 가 아닌 마지막 timezone이 대문자 %Z이기에 오류가 발생해서 예외처리 해주었다.
github action 에서 crontab은 깃헙만의 표현방식을 따르니 다른 cron validator 보다는 깃헙의 공식 문서를 보는게 더 좋다.
github action crontab에서 0시는 GMT +00 시 기준 0시이다. 우리나라는 GMT +0900 이기 때문에 이름 감안해 9를 빼서 계산해줘야 한다.

후기

간단한듯 간단하지 않았다. 특히 RSS2.0을 따르지 않는 velog라는 놈은 참…
timezone과 관련해서 실제로 코딩을 하면서 적용해 볼 수 있었고, 이번에 특별히 프로그램 전체를 클래스 형식으로 설계해서 구현했는데, 자바를 배우면서 배웠던 개념들을 다시 생각하면서 만들었다.
Datetime 패키지는 엄청 많이 사용해왔음에도 쓸때마다 조금씩 새로운걸 배운다. 이번에는 astimezone, strftime 과 strptime을 엄청 사용했다. 아직 포매팅 문자열은 계속 헷갈린다 ㅋㅋ
기존의 RSS 피드를 전부 방문하는 코드를 짜봤던것과 구글드라이브 Api를 사용해본 경험이 있어서, 빠르게 만들 수 있었던 것 같다.
요새 계속 자바만 공부를 했는데, 파이썬이 역시 좋긴하다…ㅎ 아직 자바랑은 정이 들랑 말랑하는데, 이제 한동안 파이썬을 쓸일이 없을 것 같다.. lostring 프로젝트도 마무리 하긴 해야하는데, 설동안 시간이 나면 빡세게 며칠 해서 마루리 하고 자바에 올인 해야겠다.

Related Posts

1 Comment

  1. 핑백:[기획] PostChecker - 블로그에 글 올렸는지 확인하는 프로그램 - Clazitive Developer

답글 남기기

이메일 주소는 공개되지 않습니다.

%d 블로거가 이것을 좋아합니다: