React+AWS環境を使ってアンテナサイト(RSS)を作成②

Reactでアンテナサイトを作成してみます。

Ishiguro Suguru

いわゆるアンテナサイトをReactで作ってみよう思い、その時のメモを公開しておきます。 今回はバックエンド部分を作成します。

【前回】React+AWS環境を使ってアンテナサイト(RSS)を作成①

環境

PyCharmをAWS SAMのプロジェクトを作成します。 今回はdocker Imageで作成してみます。

  • PyCharm 2021.3.3 (Community Edition)
  • Package Type: Image
  • Architecture: x86_64
  • Runtime: python 3.9

本記事で出来上がるもの

  • Lambda実行コード(python)

Lambda実行コード

仕様はざっくり以下の通り。

  • LambdaはAWS EventBridge(旧Cloud Watch)でキックして実行
  • S3に配置した設定ファイルを配置
  • 設定ファイルにはクロールするサイト情報(RSS)を記載
  • Lambda実行時に設定ファイルを読み込みRSS情報を取得
  • S3に取得した情報を保存

■ app.py(メイン処理)

import json
import feedparser
import boto3
import os
import datetime
import email.utils

CLIENT = boto3.client('s3')
# Lambdaの環境変数を取得する(template.yaml記載)
S3_BUCKET_NAME = os.environ.get('S3_BUCKET_NAME')
# 設定ファイル名
CONFIG_FILE = 'config.json'


def lambda_handler(event, context):
    # 現在時刻の取得
    now = datetime.datetime.now()
    # 現在-1日前の時刻取得
    pre = now - datetime.timedelta(days=1)

    # 年 文字列(当日)
    yNow = now.strftime('%Y')
    # 年 文字列(前日)
    yPre = pre.strftime('%Y')
    # 年月日(当日)
    ymdNow = now.strftime('%Y-%m-%d')
    # 年月日(前日)
    ymdPre = pre.strftime('%Y-%m-%d')
    # ファイル名(当日)
    fileNameNow = ymdNow + '.json'
    # ファイル名(前日)
    fileNamePre = ymdPre + '.json'
    # フルパス(当日)
    fullPathNow = yNow + '/' + fileNameNow
    # フルパス(前日)
    fullPathPre = yPre + '/' + fileNamePre

    # データ取得先の存在チェック(当日)
    linksNow = check_file(fullPathNow)
    # データ取得先の存在チェック(前日)
    linksPre = check_file(fullPathPre)

    if linksNow:
        print(fullPathNow, 'is exist')
        postsNow = linksNow
    else:
        print(fullPathNow, 'is not exist')
        postsNow = []

    if linksPre:
        print(fullPathPre, 'is exist')
        postsPre = linksPre
    else:
        print(fullPathPre, 'is not exist')
        postsPre = []

    # 設定情報ファイルのチェック
    config = check_file(CONFIG_FILE)
    # 新規データフラグ
    isNewNow = False
    isNewPre = False

    # ファイル有り->
    if config:
        try:
            for rss in config['RSSlinks']:
                print(rss)

                # RSSの情報を取得
                feed = feedparser.parse(rss['url'])

                # 取得した情報から記事の日付、タイトル、URLを取得
                for entry in feed.entries:
                    # 記事の日付取得(datetime型)
                    postDate = check_date(entry)
                    # 記事の年 取得
                    ymdPost = postDate.strftime('%Y-%m-%d')

                    # 追加するデータ生成
                    post = {
                        'date': postDate.strftime('%Y/%m/%d %H:%M'),
                        'title': entry.title,
                        'url': entry.link,
                        'siteName': rss['name'],
                        'siteUrl': feed.feed.links[0].href,
                        'category': rss['category']
                    }
                    print(post)

                    if ymdNow == ymdPost:
                        if post in postsNow:
                            print('Post exist in Now')
                        else:
                            postsNow.append(post)
                            isNewNow = True
                    elif ymdPre == ymdPost:
                        if post in postsPre:
                            print('Post exist in Pre')
                        else:
                            postsPre.append(post)
                            isNewPre = True

            if isNewNow:
                # 日付の降順で並べ替え
                postsNow = sorted(postsNow, key=lambda x: x['date'], reverse=True)
                # データ登録
                CLIENT.put_object(Body=json.dumps(postsNow), Bucket=S3_BUCKET_NAME, Key=fullPathNow)
            else:
                print('New Post don\'t exist in Now')

            if isNewPre:
                # 日付の降順で並べ替え
                postsPre = sorted(postsPre, key=lambda x: x['date'], reverse=True)
                # データ登録
                CLIENT.put_object(Body=json.dumps(postsPre), Bucket=S3_BUCKET_NAME, Key=fullPathPre)
            else:
                print('New Post don\'t exist in Pre')

        except:
            print('RSSlinks element is not found')

    # ファイル無し->
    else:
        print('Config File don\'t exist')


def check_file(file_name):
    """登録先のファイルの有無をチェック
    :rtype: object
    """
    try:
        response = CLIENT.get_object(Bucket=S3_BUCKET_NAME, Key=file_name)
        check_result = json.loads(response['Body'].read())
    except:
        check_result = False

    return check_result


def check_date(entry):
    """日付チェック
    :rtype: object
    """
    try:
        if entry.get('published') is not None:
            result = parse_date_string_to_datetime(entry.get('published'))
        elif entry.get('date') is not None:
            result = datetime.datetime.fromisoformat(entry.get('date'))
        else:
            print('Date element is not found')
            result = None

    except Exception as e:
        print('[error] ', e)
        result = False

    return result


def parse_date_string_to_datetime(date_string_rfc822):
    """
    RFC2822形式の日付を解析して、
    JST aware な datetime にする
    """
    timetuple = email.utils.parsedate_tz(date_string_rfc822)
    d = datetime.datetime(*timetuple[:7])  # native
    d -= datetime.timedelta(seconds=timetuple[-1])
    d = d.replace(tzinfo=UTC()).astimezone(JST())
    return d  # aware


class UTC(datetime.tzinfo):
    def utcoffset(self, dt):
        return datetime.timedelta(0)

    def dst(self, dt):
        return datetime.timedelta(0)

    def tzname(self, dt):
        return "UTC"


class JST(datetime.tzinfo):
    def utcoffset(self, dt):
        return datetime.timedelta(hours=9)

    def dst(self, dt):
        return datetime.timedelta(0)

    def tzname(self, dt):
        return "JST"

■ requirements.txt(Lambdaプロジェクトのファイル)

requests
boto3
feedparser

■ config.json(S3配置の設定ファイル)

{
    "RSSlinks": [
        {
            "name": "漫画まとめ速報",
            "url": "https://manga.itsys-tech.com/index.xml",
            "category": "漫画"
        },
        {
            "name": "キニ速",
            "url": "http://blog.livedoor.jp/kinisoku/index.rdf",
            "category": "ネタ"
        },
        {
            "name": "ゴールデンタイムズ",
            "url": "http://blog.livedoor.jp/goldennews/index.rdf",
            "category": "ネタ"
        },
        {
            "name": "ガハろぐNewsヽ(・ω・)/ズコー",
            "url": "http://gahalog.2chblog.jp/index.rdf",
            "category": "ニュース"
        }
    ]
}

次回はCloudFormationmでAWSのリソースを作っていきます。

comments powered by Disqus