ほんじゃらねっと

ダイエット中プログラマのブログ

RSS(Atom)データをPostgreSQLに取り込むGroovyスクリプト

特定のサイトが提供している情報を分析するために RSSからデータを取得するスクリプトを書いた。

XMLSlurper、GroovySQL、あとGroovyのクロージャのおかげで 結構シンプルに作成することができた。慣れたらもう少し Groovyらしいコードにできそう。

保存用のテーブル定義

create table entries (
    id serial not null,
    site character varying(100),
    rssid character varying(100),
    title character varying(100),
    link character varying(255),
    published timestamp,
    updated timestamp,
    summary text,
    category character varying(255),
    content text,
    primary key(id)
);

メインスクリプト

@Grapes([
    @Grab(group='org.postgresql', module='postgresql', version='9.3-1102-jdbc41'),
])

import java.text.SimpleDateFormat
import groovy.sql.Sql
import org.postgresql.ds.PGSimpleDataSource

def dbServer = '<DBサーバアドレス>'
def dbName = '<DB名>'
def dbPort = <DBポート番号>
def dbUser = '<DBユーザー名>'
def dbPass = '<DBパスワード>'

//データベース接続
def sql = new Sql(new PGSimpleDataSource(serverName: dbServer, portNumber: dbPort, databaseName: dbName, user: dbUser, password: dbPass))

def url = '<Atom RSSのURL>'

//Atom XMLを取得
def rss = new XmlSlurper().parse(url)

//Atomの日付型に対応するフォーマット
def dateFormat = new SimpleDateFormat('yyyy-MM-dd'T'HH:mm:ss'Z'')

//println rss.title
//println rss.updated

def entryList = []
rss.entry.each {
    //カテゴリはカンマ区切りのテキストに変換
    category = it.category.collect { it.@term.text() }.join(',')
    entryList.push([
        id: it.id.text(),
        site: rss.title.text(),
        title: it.title.text(),
        link: it.link.@href.text(),
        published: dateFormat.parse(it.published.text()).toTimestamp(),
        updated: dateFormat.parse(it.updated.text()).toTimestamp(),
        summary: it.summary.text(),
        category: category,
        content: it.content.text()
    ])  
}

//重複チェック用にURLを抽出
def linkList = entryList.collect { ''${it.link}'' }

//重複URLリストを生成
//GStringだと勝手にpreparedstatementに変換されてinがうまく動作しないので文字列結合を使用
def checkSql = 'select link from entries where link in (' + linkList.join(',') + ')'
def dupLinkList = []
sql.eachRow(checkSql) {
    dupLinkList.push(it.link)
}

def insertSql = ''
entryList.each {params ->
    //重複URLを除外してinsert
    if (!(params.link in dupLinkList)) {
        insertSql = 'insert into entries(rssid, site, title, link, published, updated, summary, category, content) '
        insertSql += 'values(?, ?, ?, ?, ?, ?, ?, ?, ?)'
        sql.execute(insertSql, [
            params.id,
            params.site,
            params.title,
            params.link,
            params.published,
            params.updated,
            params.summary,
            params.category,
            params.content
        ])
    }
}

このスクリプトを定期的に実行することで、RSSを収集できる。 1サイトからの収集にしか対応してないけど、URLをリスト化してループで 回せば複数サイト対応もすぐにできそう。

ハマったところは、

  • Atomの日付型対応
  • GroovySQLでのin検索

の2点。

GroovySQLはGStringでSQLを書くとGSQLという機能によって自動的に PreparedStatement化され、inの要素として渡した文字列も?変換されて うまく検索されないみたい。

Gradle徹底入門 次世代ビルドツールによる自動化基盤の構築

Gradle徹底入門 次世代ビルドツールによる自動化基盤の構築