API Gateway + AWS Lambda（Python3）でRSSフィード(JIMDO)を取得（スクレイピング）し、ローカルHTMLファイルで表示する（クロスドメイン問題も対応）

# やりたいこと

RSSフィード(JIMDO)を取得し、外部サイトにブログのタイトル、テキスト、サムネイル画像、投稿日を表示させたい。
取得件数は最新のフィード3件とする。

# 環境

PC：macOS High Sierra 10.13.5
サーバ環境：AWS API Gateway、AWS Lambda
言語1(RSS)：Python3.7 (AWS Lambda) で使用
言語2(View)：HTML、CSS、jQuery（ajax）

# JIMDOとは

160万ユーザーが利用する最大級のウェブサイト作成サービス
https://jp.jimdo.com/
JIMDOのブログ情報を外部サイトに載せる場合はRSSになります。（APIは提供していない ※2019/2/22現在）

# 補足

・JIMDOのRSS情報を今回使用してますが、他のRSS情報にも転用できると思います。
・ステージングを用意できていない状態で書いたので、あくまでガチローカルでの実装になってます。
　STG環境用は今後載せられたら載せたいと思います。

# 構成

ローカル環境(外部サイト)
　↓　　↑
AWS API Gateway
　↓　　↑
AWS Lambda(Python3.7) 
　↓　　↑
RSSフィード(JIMDO)

# RSSフィード

```
※一部割愛したValueがあります
{
  'title': 'タイトル', #取得対象
  'link': 'https://指定したドメイン.jimdofree.com/記事のエイリアス', #取得対象
  'id': 'https://指定したドメイン.jimdofree.com/記事のエイリアス',
  'summary': '
    <?xml version="1.0"?>
    <div id="">
      <div class="" id="">
        <figure class="">
          <a href="" rel="">
            <img alt="" class="" id="" src="挿入画像のURL" /> #取得対象
          </a>
        </figure>
        <div class=""></div>
      </div>
      <div class="" id="">
        <p>1行目</p> #取得対象
        <p>2行目</p> #取得対象
        <p>3行目</p> #取得対象
        <p>4行目</p> #取得対象
        <p>5行目</p> #取得対象
      </div>
    </div>',
  ',
  'published': 'Thu, 14 Feb 2019 15:10:07 +0900', #取得対象
}
```

# コード

###  AWS Lambda(Python3)のコード

```Python:index.py
from bs4 import BeautifulSoup
import datetime
import feedparser
import html5lib
import json
import urllib.request, urllib.error

# ブログURL
RSS_URL = "ブログURL"
# ブログエントリー数のMAX値(3記事)
ENTRY_NUM_MAX = 3
# 本文の取得行数のMAX値(2行)
SENTENCE_NUM_MAX = 2
# 本文の切り取りの閾値
SENTENCE_THRESHOLD = 30

def lambda_handler(event, context):

  try:

    origin = "*" # クロスドメイン対策でワイルドカード

    # 返却用データ
    responseBody = []

    # RSS情報を取得する
    # 最新日付の記事から順にMAX18件取得できます（件数の指定はできない ※2019/2/22現在）
    rss = feedparser.parse(RSS_URL)

    entry_num = 0
    # RSS情報からブログの要素を取得する
    for entry in rss.entries:
      entry_num+=1

      # タイトル
      title = entry.title
      # 記事へのリンク
      link  = entry.link
      # 投稿時間
      date = formatPublishedDate(entry.published)

      # HTML要素をパースする
      summary = entry.summary # 本文、サムネイル画像を含む要素
      soup = BeautifulSoup(summary, "html5lib")

      # サムネイル画像のURL
      img = ""
      if soup.find("img") is not None:
        img = soup.img['src']

      all_sentence = soup.select("p") # 本文（全て）を取得する
      sentence_num = 0
      text = ""
      for sentence in all_sentence:
        # 本文を2行分（改行区切り）取り出し連結する
        sentence_num+=1
        text+=sentence.contents[0].strip()
        if (sentence_num == SENTENCE_NUM_MAX):
          break

      # 本文が指定文字数以上の場合は以降を省略する
      if (len(text) > SENTENCE_THRESHOLD):
        text = text[0:SENTENCE_THRESHOLD] + "..."

      # レスポンス用に整形する
      items = {
        "title" : title,
        "link"  : link,
        "date"  : date,
        "img"   : img,
        "text"  : text,
      }
      responseBody.append(items)

      if (entry_num == ENTRY_NUM_MAX):
        # 3件のブログ記事を取得できたので処理を抜ける
        break

    response = {
      'statusCode': 200,
      'headers': {
        'Access-Control-Allow-Origin': origin
      },
      'body': json.dumps(responseBody)
    }

    return response

  except Exception:

    response = {
      'statusCode': 200,
      'headers': {
        'Access-Control-Allow-Origin' : origin
      },
      'body': []
    }

    return response

def formatPublishedDate(published):
  # 投稿日（Thu, 14 Feb 2019 15:10:07 +0900）をdatetime型で返却する
  word = published.split(" ")
  date = word[3] + " " + word[2] + " " + word[1]
  f_date = datetime.datetime.strptime(date, "%Y %b %d")
  return str(f_date.year) + "-" + str(f_date.month) + "-" + str(f_date.day)

```

### 外部サイト（ガチローカル）のコード （※クロスドメイン対策のコメントあり）

```jQuery:jimdo.js
/*
  JIMDOのRSSフィードを受信（直近3件）し、HTMLを動的に生成する
  取得情報:
    title: タイトル
    link : 記事へのリンク
    date : 投稿日
    img  : 記事のサムネイル画像のURL
    text : 記事の本文
*/
$(function(){
  $.ajax({
    url: 'API GatewayのURL',
    type: 'GET',
    dataType: 'json',
    xhrFields: {
      // クロスドメイン対策でローカル確認用にfalseにしている
      // trueの場合、python側で設定している'Access-Control-Allow-Origin'に
      // '*'(ワイルドカード)が使えず、ブラウザでクロスドメインのエラーが発生する
      withCredentials: false
    },
  })
  .done(function(data) {
    // ブログ情報は最新3件取得しているのでループさせる
    $.each(data, function(k, v) {
      // HTMLを動的に生成する（割愛）
      // データアクセス
      //   v.title: タイトル
      //   v.link : 記事へのリンク
      //   v.date : 投稿日
      //   v.img  : 記事のサムネイル画像のURL
      //   v.text : 記事の本文
    });
  });
});
```

# AWS Lambdaの問題点

一部ライブラリがLambdaに搭載されていないので、ロジックファイル(index.py)から参照するライブラリをアップロードする必要があります。
そのため、ロジックファイル(index.py)と同じディレクトリにライブラリをインストールします。
（同じディレクトリじゃなくてもいけるかもしれないけど試してません。。。）

# ライブラリのインストール〜アップロードまで
### python3をインストールします
　〜参考〜
　MacにPython3をインストールし環境構築【決定版】
　https://qiita.com/7110/items/1aa5968022373e99ae28
　→ Python3の導入、virtualenvによる環境構築まで実行してください
### ライブラリをインストールします（作業ディレクトリにて）
　1. feedparserをインストールする（RSSフィードを取得する）

```
  (env)[作業ディレクトリ] $ pip install feedparser -t .
```

　2. BeautifulSoupをインストールする（Webスクレイピング用）

```
  (env)[作業ディレクトリ] $ pip install beautifulsoup4 -t .
```

　3. html5libをインストールする（BeautifulSoupで指定するParser）

```
  (env)[作業ディレクトリ] $ pip install html5lib -t .
```

　こんなディレクトリ階層になりました

　<img width="310" alt="スクリーンショット 2019-02-22 19.14.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/158027/dc088784-068f-74ca-e571-9c464c28e815.png">


### ロジックファイル(index.py)とインストールしたライブラリをzipに固める

```
  (env)[作業ディレクトリ] $ zip -r ファイル名.zip ./*
```

### AWS Lambdaにアップロードする
　1. AWS Lambdaのコンソール画面を開く
　2. `設定`タブ -> `関数コード` -> `関数パッケージ` -> `アップロード`
　3. zipファイルを選択する
　4. `保存`ボタンを押下する

# AWS Lambdaの課題
　アップロードしたファイルサイズが10MBを超えると、
　インラインコード編集（Lambda管理画面でコード編集）ができなくなります。

# レスポンス結果

```
// Valueは割愛
[
  {title: "", link: "", date: "", img: "", text: "",}, // 1つめのブログ記事（最新）
  {title: "", link: "", date: "", img: "", text: "",}, // 2つめのブログ記事
  {title: "", link: "", date: "", img: "", text: "",}  // 3つめのブログ記事
]
```


# できなかったこと

### BeautifulSoupのParserに 'lxml' を使用する

`libxslt-devel` と `libxml2-devel` のインストールも必要らしく
Homebrewでインストールを試みたが、下記エラーが出た。

```
(env) [作業ディレクトリ] $ brew install libxslt-devel                                                    14:38:21
Error: No available formula with the name "libxslt-devel"
==> Searching for a previously deleted formula (in the last month)...
Error: No previously deleted formula found.
==> Searching for similarly named formulae...
Error: No similarly named formulae found.
==> Searching taps...
==> Searching taps on GitHub...
Error: No formulae found in taps.

----------------------------

(builder) [builder] brew install libxml2-devel                                                    14:39:43
Error: No available formula with the name "libxml2-devel"
==> Searching for a previously deleted formula (in the last month)...
Error: No previously deleted formula found.
==> Searching for similarly named formulae...
Error: No similarly named formulae found.
==> Searching taps...
==> Searching taps on GitHub...
Error: No formulae found in taps.
```

どうやらyumならパッケージがありそうです。
解決に至った経緯の質問を以下に載せておきます。（自分の質問です。）

*AWS Lambdaにて 'lxml' というライブラリが使用できないエラーを解決したい*
https://teratail.com/questions/173374

*ライブラリをbrew install でインストールする際 `No available formula with the name 〜` が出るエラーを解決したい*
https://teratail.com/questions/174026