はじめてのdocker+cli+Kotlin~南ことりの画像を収集~


はじめに

・JAVAは未経験
・qiita初投稿
・kotlinはを1冊読んで手を動かして遊んだ程度
・普段はPHPやrails
・理解度が浅いため、全体的にふんわりしています。ご指摘等頂ければ幸いです

dockerで環境を作る。

まずはkotlinがコンパイル出来るよう、環境を作っていきます。
docker-hubのdockerfileをベースにしますが
gradleが足りないので入れます(gradleについては後述)
gradle公式dockerfileを参考にしました

alpine-linuxは軽量なため、コマンドが最小限です。
curlなども入っていないので必要なものはapkで入れています。


FROM openjdk:8-jdk-alpine
RUN apk update \
    && apk upgrade \
    && apk add coreutils curl   


ARG BUILD_DATE
ARG VCS_REF

# Set Appropriate Environmental Variables
ENV GRADLE_HOME /usr/lib/gradle
ENV GRADLE_VERSION 4.7
ENV GRADLE_FOLDER=/root/.gradle

ARG GRADLE_DOWNLOAD_SHA256=fca5087dc8b50c64655c000989635664a73b11b9bd3703c7d6cabd31b7dcdb04
RUN set -o errexit -o nounset \
    && echo "Installing build dependencies" \
    && apk add --no-cache --virtual .build-deps \
        ca-certificates \
        openssl \
        unzip \
    \
    && echo "Downloading Gradle" \
    && wget -O gradle.zip "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" \
    \
    && echo "Checking download hash" \
    && echo "${GRADLE_DOWNLOAD_SHA256} *gradle.zip" | sha256sum -c - \
    \
    && echo "Installing Gradle" \
    && unzip gradle.zip \
    && rm gradle.zip \
    && mkdir /opt \
    && mv "gradle-${GRADLE_VERSION}" "${GRADLE_HOME}/" \
    && ln -s "${GRADLE_HOME}/bin/gradle" /usr/bin/gradle \
    \
    && apk del .build-deps \
    \
    && echo "Adding gradle user and group" \
    && addgroup -S -g 1000 gradle \
    && adduser -D -S -G gradle -u 1000 -s /bin/ash gradle \
    && mkdir /home/gradle/.gradle \
    && chown -R gradle:gradle /home/gradle \
    \
    && echo "Symlinking root Gradle cache to gradle Gradle cache" \
    && ln -s /home/gradle/.gradle /root/.gradle

LABEL org.label-schema.build-date=$BUILD_DATE \
      org.label-schema.description="Kotlin docker images built upon official openjdk alpine images" \
      org.label-schema.name="alpine-kotlin" \
      org.label-schema.schema-version="1.0.0-rc1" \
      org.label-schema.usage="https://github.com/Zenika/alpine-kotlin/blob/master/README.md" \
      org.label-schema.vcs-url="https://github.com/Zenika/alpine-kotlin" \
      org.label-schema.vcs-ref=$VCS_REF \
      org.label-schema.vendor="Zenika" \
      org.label-schema.version="1.2-jdk8"

RUN apk add --no-cache bash && \
    apk add --no-cache -t build-dependencies wget && \
    cd /usr/lib && \
    wget https://github.com/JetBrains/kotlin/releases/download/v1.2.41/kotlin-compiler-1.2.41.zip && \
    unzip kotlin-compiler-*.zip && \
    rm kotlin-compiler-*.zip && \
    rm kotlinc/bin/*.bat && \
    apk del --no-cache build-dependencies

ENV PATH $PATH:/usr/lib/kotlinc/bin


RUN apk del coreutils curl
# コンテナのワークディレクトリの指定
WORKDIR /app
CMD ["kotlinc"]



VOLUME  $GRADLE_FOLDER
docker-compose.yml
version: '2'

services:

  kotlin:
    build: .
    command: bash -c "gradle run"
    volumes:
    - .:/app

これでdocker-compose build後、
docker-compose upをすれば
gradle runを実行してくれます

2 gradleを書く

gradleはビルドの自動化ツールです。

Android開発者ならbuild.gradleを見たことがあると思います。
「ライブラリとか追加するときに編集する奴」程度の認識で
あまりgradleの恩恵を分かっていませんでした

今回はAndroidではなくコマンドライン上での実行になるため、
kotlincコマンド でコンパイルし、kotlinjavaで実行をしますが
ライブラリ等の依存関係管理がとても面倒です。
ここでgradleが登場します。

gradleを使えばライブラリの管理やビルド、実行を全て行ってくれます。
(厳密にはそれ以外にもなんでも出来るみたいです)
build.gradleに依存関係やmainクラス名を書きます。

その後gradle runを叩けばでビルドと実行をやってくれます
android studioで開発するときもエディタが自動で認識して
コーディングヒントを与えてくれます
gradleの理解が曖昧だったためここに一番時間がかかりましたが、結局こうなりました。

build.gradle

apply plugin: 'kotlin'
apply plugin: 'application'
mainClassName = "ScrapingKt"
buildscript {
    ext.kotlin_version = '1.2.41'
    repositories {
        jcenter()
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}
dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    compile "org.jsoup:jsoup:1.10.3"
    compile "org.apache.httpcomponents:httpclient:4.5"
}
allprojects {
    repositories {
        jcenter()
    }
}

4 動くか確認

くそコードで動くか確認。
build.gradleでスクレイピング用ライブラリJsoupを足しているので
それが使えるかの確認です。

src/main/kotlin/scraping.kt
import org.jsoup.Jsoup

//Jsoupが使えているかのテスト。引数とかはまだ特に関係ない。
class Hoge(val url: String) {
    fun test() {
        val doc = org.jsoup.Jsoup.parse("<div id='a'>aiueo</div><div id='b'>bbb</div>")
        println(doc.select("#a"))
    }
}


fun main(args: Array<String>) {
    val foo = Hoge("test")
    foo.test()

}

docker-compose upを叩き、<div id='a'>aiueo</div>とコンソールに出れば成功です

3 スクレイピングする。

準備が整ったのでスクレイピングしていきます。
まずはkotlinうんぬんの前に、
「どのURLにどういう値を投げれば、何が返ってくるのか」

ChromeのDeveloper Toolで確認

https://search.yahoo.co.jp/image/paginationjsというAPIが関係してそう。

GETなのでブラウザのURLに直接入れて確認。
投げる値を変更したり、余計そうなのを無くしてみたり。
.crumb
.ncrumb
が必須みたい?

てかjsonで帰ってくるけど、中身はほぼhtmlなのかー。(エスケープ処理はしている?)

エスケープシーケンスの\を取り除いてjsoupに流し込めば、セレクタ使ってほしいものがとれそう。

やっかいなのは。
.crumb
.ncrumb
最初はGET値決め打ちで通信出来たけど、時間がたつと失敗してしまう。
.ncrumbが時間とともに変わってるくさい。
じゃあこいつらはどこで生成されてるんだ、どうせhiddenだろうと思ったが
https://search.yahoo.co.jp/image/search中でjavascriptにより生成されている
セレクタ使えないじゃんーってことで正規表現でゴリゴリ取得。
ちなみにUser Agent見てるらしく、適当だとこの部分だけ生成してくれない。
かなり苦戦。

APIの仕様が分かれば、後はセレクタで欲しいものを指定して、
ループ処理をするだけ。
ファイル保存関連はググればJAVAのコードがたくさん出てきます

最終的に

最終的にこんな感じになりました
docker-compose up
をすると南ことりちゃんの画像が自動でsaveディレクトリに保存されるようになりました

以下感想

エディタについて

最初はatomでやってましたが、
最終的にはandroid-studioで作業しました。
この神IDEを無料で使えるのが強みの1つだと思います。
JAVAのコードを貼ると自動でkotlinに変えてくれるので、JAVA未経験マンにとってはとても助かりました

スクレイピング

結局はセレクタ使うので、ある程度ベースができればjavascriptやpythonでのスクレピングとあんまり変らないかも
Javaの広大な知見がそのまま使えるのが非常に助かります、

画像の質

ディープラーニング用の画像を集める という目的もあったのですが
個々の画像の精度に関してはyahoo画像検索よりもpinterestのほうが良さそうでした。
pinterestからディープラーニング用二次元画像を集める
とは言えyahoo画像検索の画像数は大量のため、絞り込み条件をうまく使えば
実用的だなぁと思いまいた

最後に

・kotlin、名前に惹かれて勉強したけどかなり良い言語
・Android Studio神
・間違い等あると思いますのでご指摘お待ちしております
・rootが取れなくてインテリアになった【作ってみた】スクフェス専用コントローラー