やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

NewsApiのJSONからSQLite3DBファイルへ挿入する(未登録のみ)

 DB内にある最新より新しいニュースだけを取り込む。

成果物

コード

# $1: JSONテキスト, $2: JSONパス
json_extract() { sqlite3 :memory: 'select json_extract(readfile('\'"$1"\''), '\'"$2"\'');'; }
make_insert_stmt() { echo 'insert into news(published,url,title,body) values('\'"$1"\'','\'"$2"\'','\'"$3"\'','\'"$4"\'');'; }
# $1: DB内最新ニュース一意特定値(published,url,title。デリミタ\n)
# $2..3: NewsApi JSON(publishedAt,url)
is_new() {
    local latest_published="`echo "$1" | sed -n 1P`"
    local latest_url="`echo "$1" | sed -n 2P`"
    echo "$latest_published $2 `[[ "$2" < "$latest_published" ]]`"
    [[ "$2" < "$latest_published" ]] && return 0 || [[ "$3" = "$latest_url" ]] && return 0;
    return 1;
}
# $1: NewsApiJSONパス
run() {
    local json_path="$1"
    local insert_sql="insert.sql"
    [ 'ok' != "`json_extract "$json_path" '$.status'`" ] && { echo 'エラー。JSONのstatusがokでない。: '"`json_extract "$json_path" '$.status'`" 1>&2; exit 1; }
    # DB内の最新ニュースを一意特定するデータを取得する(published,url,title)
    local db="news.db"
    local latest_news="`sqlite3 "$db" < 'get_latest.sql'`"
    # SQLファイル内容を空にする(さもなくば連続使用時に前の分と合わせて追記されてしまう)
    : > "$insert_sql"
    local totalResults="`json_extract "$json_path" '$.totalResults'`"
    for idx in $(seq 0 $(expr $totalResults - 1)); do
        # JSONから項目を抽出する
        local published="`json_extract "$json_path" '$.articles['"$idx"'].publishedAt'`"
        local url="`json_extract "$json_path" '$.articles['"$idx"'].url'`"
        local title="`json_extract "$json_path" '$.articles['"$idx"'].title'`"
        local body="`json_extract "$json_path" '$.articles['"$idx"'].description'`" # とりあえずdescriptionで代用する
        is_new "$latest_news" "$published" "$url"; [ $? -eq 0 ] && break;
        # totalResultsが多すぎたとき各項目はNULL(空文字)になる。このときは終了する。JSONが正しい限り起こり得ない。
        [ -z "$title" ] && { echo "JSON不正。titleが空。totalResults:$totalResults,idx:$idx" 1>&2; break; }
        # insert文を作る
        make_insert_stmt "$published" "$url" "$title" "$body" >> "$insert_sql"
    done
    sqlite3 "$db" < "$insert_sql"
}
run "$1"

追加

 前回から追加された部分は以下。

is_new() {
    local latest_published="`echo "$1" | sed -n 1P`"
    local latest_url="`echo "$1" | sed -n 2P`"
    echo "$latest_published $2 `[[ "$2" < "$latest_published" ]]`"
    [[ "$2" < "$latest_published" ]] && return 0 || [[ "$3" = "$latest_url" ]] && return 0;
    return 1;
}
run() {
    ...
    # DB内の最新ニュースを一意特定するデータを取得する(published,url,title)
    local db="news.db"
    local latest_news="`sqlite3 "$db" < 'get_latest.sql'`"
    ...
    for idx in ...
        ...
        is_new "$latest_news" "$published" "$url"; [ $? -eq 0 ] && break;
        ...
    ...
}

 JSONarticles配列を1件ずつ、DB内最新ニュースと見比べる。新しいなら挿入する。同じか古いならそこで終了。ニュースの一意特定にはpublishedAturlを用いている。

動作確認

 以下のような手順でテストデータを用意する。

  1. NewsApiを取得する(NewsApiでカテゴリ別にニュースを取得する
  2. 1で取得したJSONファイルをnews.jsonとでもリネームしておく
  3. 2のJSONファイルをコピーする
  4. 3のうち最新(先頭)の2件くらいを削除してnews_old.jsonとでもリネームしておく
  5. SQLite3DBファイル作成する(NewsApiで得たニュースを保存するSQLite3テーブルを考える
  6. 3のJSONファイルをDBファイルに取り込む(NewsApiのJSONからSQLite3DBファイルへ挿入する

 この状態で、上記コードを実行し、最新2件だけが追記されることを確認する。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1218 SMP Tue May 14 00:48:17 BST 2019 armv7l GNU/Linux

前回まで