AndroidでKuromojiを使う
Kuromoji
Androidでオフラインでも即座に形態素解析ができるように組み込んでみました。
LuceneのJapaneseTokenizerではなく、AtilikaのKuromojiを組み込みました。
はじめに言っておくと、組み込めたものはAtilikaのKuromoji-1.0-SNAPSHOTで、ipadicの辞書を利用したものです。
開発環境はKotlinです。
Atilika
0.7.7
公式で紹介されてる 0.7.7
を試してみました。
MavenCentralに上がっていないので、リポジトリを追加します。
- build.gradle
allprojects { repositories { jcenter() maven { url "http://www.atilika.org/nexus/content/repositories/atilika" } } }
必要なライブラリをGradleのDependenciesに追加します。
- app/build.gradle
compile "org.atilika.kuromoji:kuromoji:0.7.7"
必要なコードを記述してビルドします。
val tokenizer = Tokenizer.builder().build() val tokens = tokenizer.tokenize("我輩は猫である。名前はまだない。"); tokens.forEach { Log.d("Kuromoji", "${it.allFeatures}") }
実行はできたのですが、空白文字以外形態素解析が機能していませんでいた。 特定したわけではないですが、おそらく辞書の読み込みに失敗しているようです。
0.9.0
調べたら、MavenCentral
に0.9.0
が公開されてたので、そちらを使ってみました。
必要なライブラリをGradleのDependenciesに追加します。パッケージ名が異なるので注意してください。
- app/build.gradle
compile "com.atilika.kuromoji:kuromoji-ipadic:0.9.0"
必要なコードを記述してビルドします。
先ほどとは異なり、Builder
が大文字になっています。Tokenizer
のimport文ももちろん異なります。
val tokenizer = Tokenizer.Builder().mode(TokenizerBase.Mode.NORMAL).build() val tokens = tokenizer.tokenize("我輩は猫である。名前はまだない。"); tokens.forEach { Log.d("Kuromoji", "${it.allFeatures}") }
次のエラーがでました。
Caused by: java.lang.RuntimeException: Could not load dictionaries. at com.atilika.kuromoji.ipadic.Tokenizer$Builder.loadDictionaries(Tokenizer.java:231) at com.atilika.kuromoji.TokenizerBase.configure(TokenizerBase.java:77) at com.atilika.kuromoji.ipadic.Tokenizer.<init>(Tokenizer.java:74) at com.atilika.kuromoji.ipadic.Tokenizer.<init>(Tokenizer.java:59) at com.atilika.kuromoji.ipadic.Tokenizer$Builder.build(Tokenizer.java:203) Caused by: java.lang.IllegalArgumentException: capacity < 0: -4 at java.nio.ByteBuffer.allocate(ByteBuffer.java:54) at com.atilika.kuromoji.io.IntegerArrayIO.readArray(IntegerArrayIO.java:38) at com.atilika.kuromoji.buffer.WordIdMap.<init>(WordIdMap.java:35) at com.atilika.kuromoji.dict.TokenInfoDictionary.setup(TokenInfoDictionary.java:168) at com.atilika.kuromoji.dict.TokenInfoDictionary.newInstance(TokenInfoDictionary.java:160) at com.atilika.kuromoji.ipadic.Tokenizer$Builder.loadDictionaries(Tokenizer.java:219) at com.atilika.kuromoji.TokenizerBase.configure(TokenizerBase.java:77) at com.atilika.kuromoji.ipadic.Tokenizer.<init>(Tokenizer.java:74) at com.atilika.kuromoji.ipadic.Tokenizer.<init>(Tokenizer.java:65)
辞書の読み込みに失敗しているようです。 実際に問題となっているのは次のメソッドのようです。
- IntegerArrayIO#readArray
public static int[] readArray(InputStream input) throws IOException { DataInputStream dataInput = new DataInputStream(input); int length = dataInput.readInt(); ByteBuffer tmpBuffer = ByteBuffer.allocate(length * INT_BYTES); ReadableByteChannel channel = Channels.newChannel(dataInput); channel.read(tmpBuffer); tmpBuffer.rewind(); IntBuffer intBuffer = tmpBuffer.asIntBuffer(); int[] array = new int[length]; intBuffer.get(array); return array; }
このIssueで、フォークして解決したとあります。
しかし、別のIssueによると解決してMasterにマージされているようなので、Masterからビルドして必要なjarを生成してみます。
1.0-SNAPSHOT
試した時点でのコミットは9ce40b605990f38854a4b08c38dc08a85cc3bb8bです。
ソースを取り込んでjarを生成します。
$git clone git@github.com:atilika/kuromoji.git $cd kuromoji $mvn clean package
私の環境だと次のエラーがでました。
1.7 は無効な VM バージョンです。
JAVA_HOME
の設定が正しくないようだったので、設定します。
時間がかかりますが、無事生成できました。
kuromoji/kuromoji-core/target/kuromoji-core-1.0-SNAPSHOT.jar
kuromoji/kuromoji-ipadic/target/kuromoji-ipadic-1.0-SNAPSHOT.jar
を自分の形態素解析を行いたいプロジェクトのlibs
に配置します。
必要なライブラリをGradleのDependenciesに追加します。
- app/build.gradle
compile fileTree(dir: 'libs', include: ['*.jar']) compile files("libs/kuromoji-core-1.0-SNAPSHOT.jar") compile files("libs/kuromoji-ipadic-1.0-SNAPSHOT.jar")
必要なコードを記述します
val tokenizer = Tokenizer.Builder().mode(TokenizerBase.Mode.NORMAL).build() val tokens = tokenizer.tokenize("我輩は猫である。名前はまだない。"); tokens.forEach { Log.d("Kuromoji", "${it.allFeatures}") }
無事形態素解析した結果が得られました!
D/Kuromoji: 名詞,一般,*,*,*,*,我輩,ワガハイ,ワガハイ D/Kuromoji: 助詞,係助詞,*,*,*,*,は,ハ,ワ D/Kuromoji: 名詞,一般,*,*,*,*,猫,ネコ,ネコ D/Kuromoji: 助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ D/Kuromoji: 助動詞,*,*,*,五段・ラ行アル,基本形,ある,アル,アル D/Kuromoji: 記号,句点,*,*,*,*,。,。,。 D/Kuromoji: 名詞,一般,*,*,*,*,名前,ナマエ,ナマエ D/Kuromoji: 助詞,係助詞,*,*,*,*,は,ハ,ワ D/Kuromoji: 副詞,助詞類接続,*,*,*,*,まだ,マダ,マダ D/Kuromoji: 形容詞,自立,*,*,形容詞・アウオ段,基本形,ない,ナイ,ナイ D/Kuromoji: 記号,句点,*,*,*,*,。,。,。
まとめ
Android特有の問題もあってか、少し手間取りましたが、辞書が内包されているものは少し試すには便利です。
しかし、生成したjarが11MBもあったり、辞書展開時にOOBを引き起こしたりするので、特定の環境で専用に使う場合は良いと思いました。
それ以外の環境では、辞書のアップデートもされないし迅速なレスポンスを求めないのであればWEB APIで形態素解析を行うのがAndroidだと現実的だと思いました。
検証したサンプルです。
参考
LuceneのAnalyzer、KuromojiのModeごとの挙動を確認する / CLOVER
Kuromoji IPADIC / MVN REPOSITORY
Android runtime exception when creating new Tokenizer using kuromoji-ipadic / GitHub
忘れてしまったAndroidの署名情報は見つかるかもしれない
Androidのアプリの署名に使う情報を忘れてしまうことが、稀にあります。
やってはいけないことですが、稀にあります。
別PCで作業してていつもMaster Passwordに任せっきりだったりとか、久しぶりに更新をかけようとしたアプリで起きたりします。
同じ署名のkeystoreファイルが使えないとなると、別アプリ扱いとなるので同じアプリのストアの更新はできなくなります。なので、別アプリとしてストアに上げ直すことになります。
もちろんそれは意図した場合を除いては避けたいことなので、なんとか署名情報を思い出します。
これが助けになるかもしれないし、助けにならないかもしれません。 藁にもすがる思いでこの投稿をみつけた方には一度試してもらいたいと思います。
keystoreファイルをなくしてしまった方には今回の方法は使えません。教訓として同じことを繰り返さないようにしましょう。
署名情報
それぞれをなんと呼ぶのかわかりにくいと思ったので、この投稿では次のように扱います。
画像のまんまですが、
1に入力するものを Key store password
2に入力するものを Key alias
3に入力するものを Key password
とします。
プロジェクトルートから探る
アプリのプロジェクトルートに.gradle
という隠しフォルダがあると思います。
その中の、Gradleのバージョン/taskArtifacts/taskArtifacts.bin
というファイルを、エディターなどで無理やり開きます。
signingConfig.storePassword
, signingConfig.keyAlias
あたりで検索すると、
Key store password
とKey alias
が見つかります。私の環境では、Key password
は見つかりませんでした。
Key password
は次のツールを使えば探すことができます。
力技をサポートしてくれるツールです。
Android-keystore-password-recover by MaxCamillo
パスワードに使われていそうな文字列にあたりがある場合は見つけられる確率は少しあると思いますが、非常に時間がかかります。
上のツールを使うと、Key alias
はすぐに表示してくれます。
Android Studioのログから探る
~/Library/Logs/
に、いろんなアプリケーションのログがあります。AndroidStudio
のものは複数見つかると思いますが、Releaseビルドを成功させたバージョンのログをみます。idea.log
というファイルをエディターで開きます。こちらもidea.log.1
など複数あると思いますが、大まかに日ごとに分かれているので、作成日などをヒントにあたりをつけましょう。
android.injected.signing.store
と検索すると、近くに見つかると思います。GUI上で****と入力された履歴の下に、実際に入力されていた文字列がそのまま表示されています。
こちらは、Key store password
, Key alias
, Key password
すべてが見つかります。
まとめ
keystoreファイルと同じく、それに使う署名情報もバックアップが取れる形で保存しておきましょう。
GUIでapkを生成するのではなく、CLIで生成できるようにしておき、人力による入力漏れを無くすのも効果的です。
Please Advice Needed, Lost KeyStore password / StackOverFlow
android KeystorePassとaliasPassを忘れた時の対処法 / Game Factory
jsoupのデフォルトのUser-Agentはなんなのか。
jsoup
JavaでHTMLをいい感じにParseできる便利なライブラリです。 WebページにアクセスしてHTMLを取得し、それを加工することでWebページ内のコンテンツを簡単に取得することができます。
スクレイピング先が、User-Agent(UA)によって表示を変えている場合、それに応じてjsoupのUAを変更しなければ、思っていたコンテンツが取得できない場合があります。
User-Agentの設定
Connection#userAgent(String)
で設定できます。次のようにすることで設定されたUAを確認できます。
String url = "http://www.useragentstring.com/"; Connection con = Jsoup.connect(url); con.userAgent("Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0"); Doc doc = con.get(); doc.getElementById("uas_textfeld").val());
メソッドで指定した通り、
Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101 Firefox/23.0
が設定されているのがわかります。
未設定時
未設定のときにどうなるのか調べてみました。 Androidのエミュレータで前述のようにし、UAの設定をしないと次のようになりました。
- Genymotion
Dalvik/2.1.0 (Linux; U; Android 5.0; Nexus 5 - 5.0.0 - API 21 - 1080x1920_1 Build/LRX21M)
どこで誰が設定しているんだろうと思い、jsoupのコードを見ていたのですが、AndroidのSDKHttpURLConnection
が使われているところで詰んでしまいました。
frameworkのところまで調べていくと、 RuntimeInit
というクラスで、デフォルトのUAに使われる値が生成されていました。
- RuntimeInit.java
/** * Returns an HTTP user agent of the form * "Dalvik/1.1.0 (Linux; U; Android Eclair Build/MASTER)". */ private static String getDefaultUserAgent() { StringBuilder result = new StringBuilder(64); result.append("Dalvik/"); result.append(System.getProperty("java.vm.version")); // such as 1.1.0 result.append(" (Linux; U; Android "); String version = Build.VERSION.RELEASE; // "1.0" or "3.4b5" result.append(version.length() > 0 ? version : "1.0"); // add the model for the release build if ("REL".equals(Build.VERSION.CODENAME)) { String model = Build.MODEL; if (model.length() > 0) { result.append("; "); result.append(model); } } String id = Build.ID; // "MASTER" or "M4-rc20" if (id.length() > 0) { result.append(" Build/"); result.append(id); } result.append(")"); return result.toString(); }
他のやり方ではどのように表示されるのか、いろんな方法でUAが確認できるサイトにアクセスしてみました。
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36
Mozilla/5.0 (Linux; Android 6.0.1; Nexus 6 Build/MMB29V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.105 Mobile Safari/537.36
curl/7.43.0
- httpie
HTTPie/0.8.0
httpieが一番わかりやすく、ソースを見ると、
... DEFAULT_UA = 'HTTPie/%s' % __version__ ... def get_default_headers(args): default_headers = { 'User-Agent': DEFAULT_UA } ...
となっていたので、アクセスするプログラムが決めているのだとわかりました。 curlやブラウザなどは中がわからなかったのですが、おそらく内部で同じように自分のUAを設定しているのだと思います。
間接的で、直接はわかっていないのですが、少しスッキリしました。
jsoupのデフォルトのUA
Androidから使うときは、Android SDKのHttpURLConnection
のデフォルトのUAが適応され、Android端末のUAとなります。
参考
jsoup: Java HTML Parser / jsoup
JSoup UserAgent, how to set it right? / StackOverFlow
User Agent String.Com / UserAgentString.com
What's the default Jsoup User Agent string? / StackOverFlow