Hatena::ブログ(Diary)

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

2014-11-15

Pharo3.0から言語解析WebAPIを叩くライブラリ

Pharo3.0からエクシング社の提供する言語解析WebAPI(https://lr.capio.jp/services/webapis/)を叩くラッパーライブラリ作成した。

現在のところ、言語解析WebAPIはなにかしらのイベント(ハッカソンコンテスト)に紐付けられた期間・用途しか利用許諾が下りないことになっているので、心に留めておくこと。


言語解析WebAPIは、形態素解析に加えて、ポジネガ解析・係り受け分析が利用できる。

XMLではなくJSONで結果を返してくれるので、Web上で手軽に使えるのは良い所だ。

形態素解析の結果(読みや品詞の選択等)が時折怪しいが、たくさんのデータを食べさせて傾向を取る場合に向いている。



なおJavaScript用のラッパー作成、更新している。適当に使ってもらいたい。


"Install"

Gofer new smalltalkhubUser: 'kaminami'
    project: 'Configuration';
    package: 'ConfigurationOfJsJapaneseLanguageAnalyzer';
    load.
(Smalltalk at: #ConfigurationOfJsJapaneseLanguageAnalyzer) load
"Example"

| key result |
key := 'YourAccessKey'.  "register here: https://lr.capio.jp/services/webapis/".
result := (JsAnalyzer accessToken: key) analyze: '今日はいい天気だ'.

result phrases do: [:each | Transcript cr; show: each printString].
"==>
a JsPhrase(今日は [評価なし])
a JsPhrase(いい [ポジティブ])
a JsPhrase(天気だ [評価なし])"

result morphemes do: [:each | Transcript cr; show: each printString].
"==>
a JsMorpheme(今日, 名詞, きょう)
a JsMorpheme(は, その他, は)
a JsMorpheme(い, 形容・形容動詞, い)
a JsMorpheme(い, その他, い)
a JsMorpheme(天気, 名詞, てんき)
a JsMorpheme(だ, その他, だ)"

2014-10-30

Pharo3.0からTwitterに投稿する

Pharo3.0からTwitter投稿するサンプル。

Twitter Developersでアプリを登録しておく必要がある。


ZnOAuth1TwitterUserAccessクラスには、投稿ユーザプロファイル取得以外のAPI実装されていないので、その他のことがやりたい場合は自前で実装すること。


"Zinc SSOの導入 (https://github.com/svenvc/docs/blob/master/zinc/zinc-sso-paper.md)"

Gofer it
    url: 'http://mc.stfx.eu/ZincHTTPComponents';
    package: 'ConfigurationOfZincHTTPComponents';
    load.

(Smalltalk at: #ConfigurationOfZincHTTPComponents) project latestVersion load: 'SSO'.
"Twitterに投稿"

| consumerKey consumerSecret accessToken accessTokenSecret tokenDic token consumerData service userAccess |

consumerKey := 'YOUR_CONSUMER_KEY'.
consumerSecret := 'YOUR_CONSUMER_SECRET'.
accessToken := 'YOUR_ACCESS_TOKEN'.
accessTokenSecret := 'YOUR_ACCESS_SECRET'.

tokenDic := Dictionary new
                at: 'oauth_token' put: accessToken;
                at: 'oauth_token_secret' put: accessTokenSecret;
                yourself.
token := ZnOAuth1Token newFromDictionary: tokenDic.

consumerData := ZnOAuth1ConsumerData newForTwitter
                    consumer: consumerKey;
                    consumerSecret: consumerSecret;
                    yourself.

service := ZnOAuth1Service new
                providerAccount: consumerData;
                yourself.

userAccess := ZnOAuth1TwitterUserAccess new
                oauth1Service: service;
                accessToken: token;
                yourself.
	
^ userAccess statusesUpdate: 'Pharo3.0から投稿'

2014-10-11

デバッガの内容をそのままHTML出力 DebugReport for Pharo

半年ローカル環境放置していたDebugReportのPharo3.0対応版をSmalltalkHubに置いた。

問題発生時のエスパー対応がなくなるので、結構便利なものである

"導入用スクリプト"
Gofer new smalltalkhubUser: 'kaminami'
    project: 'Configuration';
    package: 'ConfigurationOfDebugReport';
    load.
(Smalltalk at: #ConfigurationOfDebugReport) load

2014-06-07

ポケット・ミクで極めて棒読みなText to Speechをやる

ケミクがなんかイロイロ読み上げてくれる。ような。

「PokeMikuTTS」

https://github.com/kaminami/PokeMikuTTS


var sentence = '犬も歩けば棒に当たる';
var engine = new PokeMikuTTS.TTSEngine();
engine.speech(sentence);

2014-05-25

JavaScriptで言語解析WebAPIを叩くライブラリを作成した

株式会社エクシング提供している言語解析WebAPIを、JavaScriptから利用するためのライブラリ作成した。

JSONPデータを取得して、ガワを被せるだけのものだが、生データを捏ねくり回すよりは多少マシであろう。

言語解析WebAPIを使ってアプリ作成すれば、言語解析WebAPI賞に引っかかるかもしれないので、まぁ興味のある人は適当にどうぞ。

なお、このサービスHTML5 Japan Cupの期間のみ一般に公開ということなので、おそらく7月には 8月31日で利用できなくなる。


「JoysoundJLP」

https://github.com/kaminami/JoysoundJLP


var config = new JoysoundJLP.Config('YOUR_USERNAME', 'YOUR_PASSWORD');

var tagger = new JoysoundJLP.Tagger(config);
var sentence = '本日の光り物になります。今日は、おすしを食べたい。なんてブリリアントなコハダ。';
tagger.parse(sentence, function(resultSet) {
    console.log(sentence);
    console.log(resultSet);
    console.log(resultSet.readings());
});

2014-04-24

Squeak Smalltalkで、チャットサービスTypetalkのAPIを叩く

Typetalk Hack Tokyoなるイベントが開催されたのであるが、それに参加してきたわけで。

クライアントライブラリ的な何かを書き、APIを4つばかり扱えるようにしたわけで。

APIの残りは8つ。そのうち続きを書いて、SqueakSourceかSmalltalkHubかGitHubあたりに置く。かも。


Typetalkの開発者サイトには、メッセージ投稿を行うサンプルコードが並んでいる*1。これらと等価Smalltalkコードを残す。


| accessToken authDic authRes clientId clientSecret json messageDic messageString topicId |

clientId := 'xxxxxxxxxxxxxxxxxxxx'.
clientSecret := 'xxxxxxxxxxxxxxxxxxxx'.
topicId := 'nnnn'.
messageString := 'Hello, Typetalk!'.

authDic := Dictionary new
                at: 'client_id' put: clientId;
                at: 'client_secret' put: clientSecret;
                at: 'grant_type' put: 'client_credentials';
                at: 'scope' put: 'topic.post';
                yourself.
authRes := WebClient new
                httpPost: 'https://typetalk.in/oauth2/access_token'
                content: (WebUtils encodeUrlEncodedForm: authDic)
                type: 'application/x-www-form-urlencoded'.
json := Json readFrom: authRes content readStream.
accessToken := json at: 'access_token'.


messageDic := Dictionary new
                at: 'message' put: messageString;
                yourself.
WebClient new
    httpPost: ('https://typetalk.in/api/v1/topics/' , topicId)
    content: (WebUtils encodeUrlEncodedForm: messageDic)
    type: 'application/x-www-form-urlencoded'
    do: [:request | 
        request 
            headerAt: 'Authorization' 
            put: ('Bearer ' , accessToken)].

2013-06-10

Herokuのアカウントが止められたわけだが

土曜日(約48時間前)からHerokuで動かしていたサービスが止まり、ログインしようにも

Internal Server Error

Our apologies, but it appears that something has gone wrong. Our engineers have been notified. If this problem persists, please e-mail support.

と、利用不能になっていた。

週が明けて、極めて拙い問い合わせメールへの回答によると、ここ数日で行ったスパマー/フィッシングサイト対応に引っかかっていたそうだ。アプリバックアップされているそうだから、そのうち利用できるようになるはずだが、まだアカウントは回復していない。


スパマー/フィッシングサイトの検出アルゴリズムfixされるというから今後はどうなるか分からないが、今回どの程度のことをしてスパマー/フィッシングサイトに認定されたのかをここに記録する。

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)
        }
    }
}

2013-05-28

Groovy/Grailsでフィードアグリゲータを作成し、Herokuで運用してみた

ここ20日間ほど、Groovy/Grailsフィードアグリゲータを作っていた。

これにどれほどの需要があるかはさておき、自分の見たい情報が整理されて出てくるところまでは来た。


ハンドボールナビ」

http://handballnavi.com


JVMで動くアクション型のフレームワークを調査するところから始めて、練習課題的にフィードアグリゲータを設定し、10日後に公開、その後10日で肉付けして、現在に至る、というのはそう悪いペースではないだろう。


以下、チラシの裏


Webフレームワーク

もともとやりたいことは別にあって、それにはSeasideフレームワークが向いていないことは分かっていた。Smalltalk環境を活かした生産性は抜群で、実行速度も十分(Javaの1/5程度の速度)だと思うが、コンポーネントベースフレームワークは内向きのプロダクトに向いていて、外向きにはイマイチだ。現状、SmalltalkWeb開発となると、実質Seaside一択になってしまうのは辛いところで、WAFの自作からやるのは遠すぎる。


Webフレームワークは、以下の条件で調べていた。


Railsは実行速度で、PHP系統クロージャで外した。残るのはJVMGroovyScalaV8JavaScriptといったところだったが、JVMを選択した。JavaScriptクロージャは気に入らない。


Grails、Play、SpringMVC、SAStrutsの資料を眺め、いくらかサンプルを書いたところでGrailsに決めた。

SpringMVC、SAStrutsGroovyScalaから使うには冗長で、他の2つに比べて設定記述も多く、面倒な印象を受けた。

PlayはViewテンプレートは素晴らしいが、Javaから使うには冗長な記述で、Scalaの構文は読みにくかった。なにより検索結果にPlay1とPlay2の情報が錯綜するのに参った。

消去法的にGrails。受けが広い気がした。


そこそこの数の資料を見て回ったが、特に意志決定を後押ししたのは以下の3つ。

Grails vs. Play Framework comparison」にあった、 "Scala is for writing, not for reading"には、まぁそうよね、とPlayのサンプルを読み書きした後に軽く同意した。


Grails

ドメインオブジェクトリファクタリングに起因すると思われるHibernate周りの異常動作、変更時の再起動必要性の分かりにくさ、CoC(Conversion of Control)なフレームワーク記述が少ない反面「覚えゲー」なのではないかという疑問等々、引っかかる部分があるにはあったが、全体に良いモノだった。Seasideのように、問題発生時は潜って自力解決しつつ仕組みを徐々におさえていく、ということができないのは残念だが、公式ドキュメント書籍Blog、StackOverflow等々、日本語では少ないものの、必要情報がたいてい手に入るのは素晴らしい。


Grails in Action」にちゃんと目を通すのが良いようだが、必要なところだけつまんで、今のところはサボっている。

数年前にデブサミで頂いた「Grails徹底入門」は、M、V、Cで別々の章立てになっている関係や、コード解説が部分部分になっているためか、少々全体像が把握しづらかった。特にWebFlowのあたり。一緒に頂いたサンプル入りCD-ROMを紛失してしまったのが悔やまれる。


IDEにはIntelliJ Idea Ultimateを使用した。他は試していない。大層賢くて、Grails名前解決な部分もちゃんと補完し、リファクタリング時も追随してくれる。昨年使用していたEclipseに比べると雲泥の差だ。2日前に199ドルを支払ったが、応分価値はある。

とはいえ、デバッグ環境はイマイチだ。Java系はどれも及第点を与えられない。

昨年作成したGInspectorがそれなりに役立った。


Heroku

公開しないか、VPSのお試し期間で試すかで迷っていたが、Heroku無償で使えることが分かったのでそこに乗らせてもらった。コンパイル時にメモリオーバーでコケたり、ログが最新100行分しかとれなかったり(注:ちゃんとコマンドを調べれば取れることがわかる)、デプロイに失敗するがappを作り直すと同じコード成功するとか、なにかと不可解で厳しいこともあったが、無償には代えられない。


データは基本オンメモリ設定のH2 Databaseに保持し、残したいものは、これもHerokuRESTサーバをたて、PostgreSQLのTEXTにJSONを突っ込んでいる。無償範囲は10000行に制限されているが、データ容量の制限は明記されていないのでこのようにした。運用が面倒だ。失って困るデータはないので、あまり真剣対応していない。


Herokuは1時間アクセスのないサービスアイドリング状態にする。それを避けるため、cronで定期アクセスするのが定番のようだが、これもGrailsのquartz pluginを使用し、設定されたURLに定期アクセスするサーバを2つ立て、相互にアクセスさせるようにした。オーバーだが、Grailsに慣れる過程でやってみた。それなりに役立ってはいるし、WorkerDynoも要らない。


本体の更新は、2つのappに交互にデプロイし、初期設定と動作確認を終えた時点でDNSの設定を切り替えている。


Grails on Heroku無償

イニシャルの状態でもアプリサイズ(Slug)が80MBを超え、コンパイル時のメモリ使用量も制限の512MBを超えるのは難。

アプリサイズ最大200MB、メモリも基本512MB以内という制限があり、メモリ使用量がこの1.2倍(だったか?)を超えるとプロセスが止められてしまい、なにかとイライラさせられる。Railsだとアプリサイズで30MB(某所でちょっと目にしただけだが)程度に収まるようなので、当たり前だが、Ruby向けに作られたサービスということだろう。


Herokuの印象

ちょっと触っただけで言うのもなんだが、遠くて、小さい。

なにかと割り切りが必要で、継続的な前進や、日常的なメンテ必要となるシステムに、Herokuは使いにくい。

枠をはみ出す毎に課金必要になる。課金必要な程度にシステムが膨れた場合は、国内VPSクラウドサービスを借りるのが現実的だと思う。

フリーライダーがどの口で言うか、という話ではあるが印象はそうだ。


終わり

  • Grails/Groovyは良いバランスだと思う。Javaの肩に乗れるのも良い
    • Hibernateは気に入らない。テーブル3つJoinして、、、な処理は、いまのところあきらめて無駄SQLを発行している。本当に必要ならMyBatisを使う
  • Herokuは限定的に、割り切って使う

2013-05-22

キーワードにマッチした2chスレのリストを取得するGroovyスクリプト

Groovyで、キーワードマッチした2chスレリストを取得するスクリプトを書いた。スクレイピングにjsoup、並行処理にGParsを使っている。

板の数は800を超えるが、wait無しの複数スレッドでドカンとやれば、パズーの身支度より早く終る。ただし、調子に乗ると規制されてアクセスできなくなるので注意すること。

えーとその、もうしないので許してください。ゴメンナサイ。


import org.jsoup.*
import org.jsoup.nodes.*
import org.jsoup.select.*
import groovyx.gpars.GParsPool

List keywords = ['iPhone', 'アイフォン', 'アイフォーン']


String bbsTable = 'http://menu.2ch.net/bbstable.html'
List ignoreBBSList = [
    '2ch総合案内', '地震ヘッドライン', '地震速報', '臨時地震', '臨時地震+', '緊急自然災害@超臨時',
    'テレビ番組欄',
    '2chプロジェクト',
    'いろいろランキング',
    '削除依頼', '批判要望',
]

Document doc = Jsoup.connect(bbsTable).get()
Elements anchors = doc.getElementsByTag('a')

List bbsList = []
String reg = /^http:\/\/\w+\.2ch\.net\/\w+\/$/
anchors.each {Element elem ->
    String name = elem.text()
    String url = elem.attr('href')
    if ((url ==~ reg) &&  (! ignoreBBSList.contains(name))) {
        bbsList << [name, url]
    }
}


List threads = []
GParsPool.withPool(1) { // 規制上等で一気に取得したい場合は増やす
    bbsList.eachParallel {String bbsName, String bbsUrl ->
        sleep(1000) // ここも調整
        println("取得中: " + bbsName)

        try {
            String subjects = new URL(bbsUrl + 'subject.txt').getText('SHIFT_JIS')
            subjects.eachLine {String line ->
                (line =~ /^(\d+)\.dat<>(.+)\(\d+\)$/).each { String all, String threadId, String threadName ->
                    if (keywords.any {String keyword -> threadName.matches(/(?i).*$keyword.*/)}) {
                        threads << [bbsName:bbsName, bbsUrl:bbsUrl, threadName:threadName, threadId:threadId]
                    }
                }
            }
        } catch (e) {
            println("!!! ERROR !!! " + [bbsName, bbsUrl, e.message])
        }
    }
}


println()
println()
threads.each {
    println it
}

取得中: 一人暮らし
取得中: プロバイダー
取得中: インスタント麺
取得中: 美容
取得中: 癒し
取得中: Walker+
取得中: 面白ネタnews
    <snip>
取得中: 天国
取得中: ニー速


[bbsName:プロバイダー, bbsUrl:http://engawa.2ch.net/isp/, threadName:【IP電話】Vivafon(iPhone/Win用)【Skype/アジルフォン】 , threadId:1321980902]
[bbsName:面白ネタnews, bbsUrl:http://kohada.2ch.net/be/, threadName:iPhoneぶっ壊れたwwwww , threadId:1356962936]
[bbsName:面白ネタnews, bbsUrl:http://kohada.2ch.net/be/, threadName:iPhoneアプリ「笑えるコピペ」に採用されそうな面白い話を教えて! , threadId:1338139697]
[bbsName:ビジネスnews+, bbsUrl:http://anago.2ch.net/bizplus/, threadName:【決算】KDDI、前3月期は増収増益 iPhone効果生かす[13/04/30] , threadId:1367305621]
[bbsName:ビジネスnews+, bbsUrl:http://anago.2ch.net/bizplus/, threadName:【携帯】iPhone、日本の携帯電話出荷1位に[13/04/25] , threadId:1366859028]
    <snip>
[bbsName:天国, bbsUrl:http://anago.2ch.net/heaven4vip/, threadName:妹が風呂入ってたからiPhoneで写真撮ったら , threadId:1368793734]
Connection: close