Hatena::ブログ(Diary)

駄文生産所 このページをアンテナに追加 RSSフィード

2013-05-31

リンク集からRSS/Atomフィードを収集するWebアプリ/スクリプト

Feed TrawlerというWebアプリ作成した。


「Feed Trawler」

http://feedtrawler.devgoodies.net/


ブログリンク集的なページのURLから、一段階分リンクをたどり、RSSAtomフィードがあった場合、「ページのタイトル」「リンクURL」「フィードURL」を抜き出し、リストする。

こんなものを使うのは、ろくでなしに決っているので、広告の配置もろくでもない感じにした。一度やってみたかった。


環境は、Groovy/Grails + Heroku。それに、GParsとjsoup。

すぐにHerokuの制限であるメモリ512MB、30秒の制限に引っかかってしまうのが残念なところ。

GParsで30スレッド並行で処理しようとすると、メモリが溢れ、強制停止されてしまった。いまはとりあえず5スレッドに設定している。


やると決めて制作に一日。作業時間は、コア部分、Web部分、見た目調整、雑務で、各3時間程度。

以下は最初期に殴り書きした整理前のスクリプトGroovyの実行環境が準備できるなら、これで十分役には立つ。



import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Elements
import groovyx.gpars.GParsPool

def url = "http://www.j-pfa.or.jp/blog"

Document rootDoc = Jsoup.connect(url).get()
Elements rootAnchors = rootDoc.getElementsByTag('a')

List candidates = []
rootAnchors.each {Element elem ->
    def text =  elem.text()
    def link = elem.attr("href")

    if (link =~ /^https?\:\/\//) {
        candidates << link
    }
}
candidates.unique() // 副作用

// 若いインデックスのものほど優先
List feedTypes = [
    'application/atom+xml',
    'application/rss+xml',
    'application/rdf+xml',
    'application/x.atom+xml',
    'application/xml',
    'text/xml',
]

def selectFeed(List types, List elements) {
    def selected = null
    types.each {type ->
        if (selected == null) {
            selected = elements.find {Element elem -> elem.attr('type') == type}
        }
    }
    return selected
}

GParsPool.withPool(30) {
    candidates.eachParallel {String linkUrl ->
        try {
            Document doc = Jsoup.connect(linkUrl).get()
            Elements links = doc.head().getElementsByTag('link').findAll {Element elem -> elem.attr('rel') == 'alternate'}

            List feedLinks = links.findAll {Element elem -> feedTypes.any {type -> elem.attr('type') == type}}
            feedElem = selectFeed(feedTypes, feedLinks)

            if (feedElem) {
                String feedUrl = feedElem.attr('href')
                if (! feedUrl.startsWith('http')) {
                    URL u = new URL(linkUrl)
                    def protocol = u.protocol
                    def host = u.host
                    feedUrl = "${protocol}://${host}${feedUrl}"
                }

                println ("${doc.title()}\t${linkUrl}\t${feedUrl}")
            }
        } catch (e) {
            // println(e.message)
        }
    }
}