【ChromeOSインストール版】Dockerを公式チュートリアル”Get Started with Docker”で学ぶ【前編】

ChromeOSにDockerをインストールしてみたので公式のドキュメントを見ながら学んで見たいと思います。

【参考】 uepon.hatenadiary.com

正直QiitaなどではDockerのMAC版の資料は検索に引っかかってくるのですが、LinuxというかChromeOSでの情報が見つかりにくい状況だったので、 いいタイミングなので公式のチュートリアルをみながら学んで見ようと思います。

【公式チュートリアル

docs.docker.com

f:id:ueponx:20190227225438p:plain

Get Started, Part 1: Orientation and setup

このパートではDockerの説明とセットアップに関して記載されています。

チュートリアルの流れは以下のようになっているようです。

  1. Docker環境を設定する
  2. 1つのコンテナとしてイメージを構築する
  3. 複数のコンテナを実行するようにアプリをスケールする
  4. クラスタ全体にアプリを分散する
  5. バックエンドデータベースを追加してスタックサービス化
  6. アプリをプロダクションに

個人的には3つ目ぐらいで理解の限界がきそうなのですが、できるだけ頑張ってみます。

この章の最初の方は以下の説明がされています。

  • Dockerとはどんなものなのか?
  • イメージとコンテナとは?
  • コンテナとVMの違い

そのあたりは結構わかりやすいサイトもあるのでググっていただければ…とにかく利点はいっぱいあるのでとりあえず進めていきましょう。

インストール

DockerにはCEとEEの2つのエディションがあります。個人で使うとなるとCE(Community Edition )になると思います。EEはEnterprise Editionの略です。 (注)インストールするときにKubernetesを使う前提であればバージョンに注意が必要のようです。

インストールに関しては過去のエントリを参考にしてください。WindowsとかOSXな方はインストーラがあるので大丈夫でしょう。

【参考:Dockerのインストール】

uepon.hatenadiary.com

参考のエントリ内で、インストール後にバージョンの確認やhello-worldイメージの実行などは行っていますので、このパートはこれで終わりとなります。

Get Started, Part 2: Containers

次のパートででは実際にコンテナを作成して行くお話になります。 前提としてはDockerのインストールと動作に関しては事前確認しておくことですが、前パートが動作できていればOKです。

Dockerで考えるアプリはコンテナ、サービス、スタックというレイヤーがあり、その一番下層にくるのがコンテナになるそうです。

  • Stack
  • Services
  • Container (←ここをやる)

これまでの開発では環境づくりが結構めんどくせー感じだったので、それを解決するんだ〜って感じの内容になっています。 Dockerを使えば、開発環境はイメージの取得だけで、インストールは不要。開発側のアプリも、依存関係、そしてランタイムを含めて移行できるみたいです。

これらのイメージの設定はDockerfileと呼ばれるものによって定義します。 Dockerファイルで定義されていればどの環境に行っても同じ実行環境を再構築できるというのがメリットです。環境構築するのが目的ではないのでこれは便利!

今回のチュートリアルではpythonのアプリケーションを通してコンテナの扱い方を学びます。 まずは作業を行うディレクトリを作成して、移動します。

$ mkdir friendlyhello
$ cd friendlyhello

移動したディレクトリでdockerfileを作成していきます。チュートリアルのdockerfileのテキストをそのまま使用すればいいのですが、今回はこれを少し変えて進めます。 チュートリアルではdockerfileに記述するベースイメージがpython:2.7-slimとなっていますが、ChromeOSではリソースが小さいため Alpine Linuxpythonイメージを使用することになりました。これでかなりのディスクスペースの軽量化も図れます。

alpinelinux.org

【dockerfile】

# Use an official Python runtime as a parent image
# FROM python:2.7-slim # オリジナルの記述
FROM python:2.7-alpine

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Run app.py when the container launches
CMD ["python", "app.py"]

これで定義できました。あとは、開発するアプリの本体app.pyと依存関係の部分(pipのインストール設定)requirements.txtを設定します。 基本的にはチュートリアルそのままで大丈夫です。

【requirements.txt】

Flask
Redis

今回のアプリではRedisというデータベースを使用しています。

Redisは、ネットワーク接続された永続化可能なインメモリデータベース。連想配列、リスト、セットなどのデータ構造を扱える。いわゆるNoSQLデータベースの一つ。オープンソースソフトウェアプロジェクトであり、Redis Labsがスポンサーとなって開発されている。(Wikipediaより

redis.io

自分も初めて使用するものなのであんまりよくわかっていないですが、自分が前々から欲しかった機能はこれかも。今後はRedisを使っていこうかな。

【app.py】

from flask import Flask
from redis import Redis, RedisError
import os
import socket

# Connect to Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)

app = Flask(__name__)

@app.route("/")
def hello():
    try:
        visits = redis.incr("counter")
    except RedisError:
        visits = "<i>cannot connect to Redis, counter disabled</i>"

    html = "<h3>Hello {name}!</h3>" \
           "<b>Hostname:</b> {hostname}<br/>" \
           "<b>Visits:</b> {visits}"
    return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80)

ここまでできて、ディレクトリの内容が以下のような状態になったことを確認します

$ ls
Dockerfile      app.py          requirements.txt

続いてはdockerfileの情報をもとにビルドを行います。

$ docker build --tag=friendlyhello .

※ 末尾のドット.も忘れずに!

--tagオプションまたは-tオプションで作成するイメージにタグ付けができるので、今回はfriendlyhelloという名前をつけています。

【ログ】

$ docker build --tag=friendlyhello .
Sending build context to Docker daemon   5.12kB
Step 1/7 : FROM python:2.7-alpine
 ---> 028b1c040d1e
Step 2/7 : WORKDIR /app
 ---> Using cache
 ---> eae50d1926e5
Step 3/7 : COPY . /app
 ---> 5769976c4b92
Step 4/7 : RUN pip install --trusted-host pypi.python.org -r requirements.txt
 ---> Running in 19bcbd6b1e65
DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7.
Collecting itsdangerous>=0.24 (from Flask->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/76/ae/44b03b253d6fade317f32c24d100b3b35c2239807046a4c953c7b89fa49e/itsdangerous-1.1.0-py2.py3-none-any.whl
Collecting Jinja2>=2.10 (from Flask->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/7f/ff/ae64bacdfc95f27a016a7bed8e8686763ba4d277a78ca76f32659220a731/Jinja2-2.10-py2.py3-none-any.whl (126kB)
Collecting MarkupSafe>=0.23 (from Jinja2>=2.10->Flask->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/b9/2e/64db92e53b86efccfaea71321f597fa2e1b2bd3853d8ce658568f7a13094/MarkupSafe-1.1.1.tar.gz
Building wheels for collected packages: MarkupSafe
  Building wheel for MarkupSafe (setup.py): started
  Building wheel for MarkupSafe (setup.py): finished with status 'done'
  Stored in directory: /root/.cache/pip/wheels/f2/aa/04/0edf07a1b8a5f5f1aed7580fffb69ce8972edc16a505916a77
Successfully built MarkupSafe
Installing collected packages: Werkzeug, click, itsdangerous, MarkupSafe, Jinja2, Flask, Redis
Successfully installed Flask-1.0.2 Jinja2-2.10 MarkupSafe-1.1.1 Redis-3.2.0 Werkzeug-0.14.1 click-7.0 itsdangerous-1.1.0
Removing intermediate container 5d012755c159
 ---> 9fbdfb77b37c
Step 5/7 : EXPOSE 80
 ---> Running in cc219cd8b3ab
Removing intermediate container cc219cd8b3ab
 ---> 4b2f986a13a9
Step 6/7 : ENV NAME World
 ---> Running in 69f49359148c
Removing intermediate container 69f49359148c
 ---> a602cc6d6905
Step 7/7 : CMD ["python", "app.py"]
 ---> Running in 005ad4362edb
Removing intermediate container 005ad4362edb
 ---> 1ad3fbdd80bf
Successfully built 1ad3fbdd80bf
Successfully tagged friendlyhello:latest

Dockerファイルでは以下のようなことを行っています。

  1. イメージの取得(ローカルにない場合にはDocker Hubから取得)
  2. 作業用ディレクトリの設定
  3. ローカルで開発したアプリのファイルをイメージにコピー
  4. pipでアプリが依存するモジュール環境構築
  5. 公開ポートの設定
  6. 環境変数の設定
  7. アプリの実行

イメージ作成結果の確認すると…

$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
friendlyhello       latest              1ad3fbdd80bf        40 minutes ago      73MB
python              2.7-alpine          028b1c040d1e        4 days ago          61.2MB

イメージが2つになっていますが、Alpain Linuxのベースイメージを取得してから、それに追加する形でオリジナルのイメージを作成するので、 ローカル側には実質2つのイメージが保存されます。

タグがlatestになっています。 tagオプションの完全な構文は--tag = friendlyhello:v0.0.1と記述するようです。 ベースにAlpine Linuxのイメージを利用していることもあり、イメージのサイズも非常に小さくなっています。

ここまででimageの作成が完了できたので、以降は実行となります。

注意点

Proxyサーバ経由の環境の場合

Proxyサーバの設定が必要な場合にはdockerfileに以下の行を追加してProxyのポートを設定する必要があります。

# Set proxy server, replace host:port with values for your servers
ENV http_proxy host:port
ENV https_proxy host:port

DNSの設定ミスがあった場合

DNSの設定ミスでpipの実行がエラーになった場合には /etc/docker/daemon.jsonに以下のようにDNSの設定を追加してdockerのプロセスを再起動し、再度buildを行ってください。

/etc/docker/daemon.json

{
  "dns": ["your_dns_address", "8.8.8.8"]
}

【dockerサービスの再起動】

$ sudo service docker restart

アプリケーションの実行

いよいよアプリケーションを実行します。実行にはdocker runコマンドを使用します。 その際、-pオプションを使用して、ローカルマシンのポート4000をアプリケーションのコンテナマシンのポート80にマッピングします。 この指定を行うことでローカルマシンのhttp://localhost:4000/のアクセスを行うとコンテナマシンのhttp://localhost:80/にアクセスしたことと同じように動作させることができます。多分Linuxでこの動作を行う際はポート番号に注意が必要のようです。port4000ではエラーが発生したので、port8080などの比較的大きな番号のものに差し替えたほうがいいかと思います。

$ docker run -p 8080:80 friendlyhello

【ログ】

$ docker run -p 8080:80 friendlyhello
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:80/ (Press CTRL+C to quit)

動作後にローカルPC側のブラウザからhttp://localhost:8080/へアクセスします。

f:id:ueponx:20190227225542p:plain

【ログ】

$ docker run -p 8080:80 friendlyhello
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:80/ (Press CTRL+C to quit)
172.17.0.1 - - [26/Feb/2019 15:12:43] "GET / HTTP/1.1" 200 -
172.17.0.1 - - [26/Feb/2019 15:12:45] "GET /favicon.ico HTTP/1.1" 404 -

動作が確認できたらコンソールで【Ctrl+C】として終了します。

アプリはデタッチモードでも起動することもできます。(イメージ起動後がバックグラウンドで行われています。)

$ docker run -d -p 8080:80 friendlyhello

実行を確認するためにはdocker container lsを実行して状況を確認します。

$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
56b0547a29b9        friendlyhello       "python app.py"     37 seconds ago      Up 33 seconds       0.0.0.0:8080->80/tcp   determined_joliot

このように表示されます。この状態で先ほどと同様にブラウザからのアクセスにも応答され動作を確認することもできます。

デタッチモードでの動作を終了する場合には、docker container lsコマンド実行時に表示されたCONTAINER IDを使用してdocker container stopコマンドで停止を行います。

【ログ】

$ docker run -d -p 8080:80 friendlyhello
56b0547a29b91f3ec0c14f02ce5beea34b67d7db24e415419270e25547c59d97

$ docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                  NAMES
56b0547a29b9        friendlyhello       "python app.py"     37 seconds ago      Up 33 seconds       0.0.0.0:8080->80/tcp   determined_joliot

$ docker container stop 56b0547a29b9
56b0547a29b9

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

f:id:ueponx:20190227225819p:plain

あれ?

先程、Dockerのコンテナの実行を行いましたが、なにか変な感じしませんでしたか?

f:id:ueponx:20190227225542p:plain

counter disabledってなんだよw なんと!公式のドキュメント内でも同じ表示になっています。

f:id:ueponx:20190228003531p:plain

この部分はapp.pyから使用されているRedisが動作できていないことに起因しているようです。 簡単に言うとRedisはインストールしているのにredis-serverを起動していないので、Redisの処理に失敗しているというわけです。

こりゃいかんということでdockerfileの見直しとアプリを起動するシェルスクリプトを作成します。 一見dockerfile内の末尾にあるCMDのエントリにredis-serverの起動を追加すればいいのではないかと思うのですが、CMDは設定に含まれる最後の1行のみが実行されるので 起動シェルを作成するのがよさそうです。

【startup.sh ファイル作成】

#!/bin/sh
redis-server &
python app.py

作成したファイルに実行権限付与をします。

chmod +x ./startup.sh

【dockerfile 要修正】

# Use an official Python runtime as a parent image
# FROM python:2.7-slim # オリジナルの記述
FROM python:2.7-alpine

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt \
    && apk --update --no-cache add redis

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Run app.py when the container launches
# CMD ["python", "app.py"] # オリジナルの記述
CMD ["./startup.sh"]

アプリ側の記述も修正を行います。Redisのホストとポート名の引数を変更・追加しています。

【app.py 要修正】

from flask import Flask
from redis import Redis, RedisError
import os
import socket

# Connect to Redis
#redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)
redis = Redis(host="localhost", port=6379, db=0, socket_connect_timeout=2, socket_timeout=2)

app = Flask(__name__)

@app.route("/")
def hello():
    try:
        visits = redis.incr("counter")
    except RedisError:
        visits = "<i>cannot connect to Redis, counter disabled</i>"

    html = "<h3>Hello {name}!</h3>" \
           "<b>Hostname:</b> {hostname}<br/>" \
           "<b>Visits:</b> {visits}"
    return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80)

再ビルド、再実行を行う

f:id:ueponx:20190301082302p:plain

ブラウザでアクセスすると…

f:id:ueponx:20190301082319p:plain

無事動作できました。

作成したイメージの共有

こうして作成したイメージは別環境でも使用できるようにレジストリに登録して共有を行えるようにすれば利便性が上がります。 Gitに似たようなものだと思います。Dockerにもオリジナルのレジストリがありますが、それ以外のものも使用することは可能です。

まずは以下のサイトでDocker.Hubのユーザー登録を行います。

hub.docker.com

ユーザー登録が終わったら、コマンドラインで以下を実行し、Dockerの公開レジストリにログインを行います。

f:id:ueponx:20190303113325p:plain

無事にログインができたらレジストリへのレポジトリの関連付けを行うタグ登録を行います。 推奨のタグ付けの規則としてはusername/repository:tagという形式を使うようです。

【一般化】

$ docker tag image username/repository:tag

【例:ユーザー名がhogeの場合】

$ docker tag friendlyhello hoge/get-started:part2

実行がうまく行くと新しいタグイメージが生成されます。(確認はdocker image lsで行えます)

$ docker tag friendlyhello hoge/get-started:part2
$ docker image ls
REPOSITORY           TAG                 IMAGE ID            CREATED             SIZE
hoge/get-started   part2               6321d6a7a1cd        2 days ago          74.4MB
friendlyhello        latest              6321d6a7a1cd        2 days ago          74.4MB
python               2.7-alpine          028b1c040d1e        8 days ago          61.2MB

f:id:ueponx:20190303113503p:plain

つづいてはイメージの公開になります。

【一般化】

$ docker push username/repository:tag

【例:ユーザー名がhogeの場合】

$ docker push hoge/get-started:part2

f:id:ueponx:20190303113423p:plain

この処理が完了するとpushしたイメージは一般に公開されます。以下のように実行すればローカルにイメージがなければ ネットからイメージを取得して実行が行われます。

$ docker run -p 8080:80 hoge/get-started:part2

実際にイメージを削除して実行すると以下のようになります。

f:id:ueponx:20190303113130p:plain

f:id:ueponx:20190303113020p:plain

とりあえず、これでパート2は終了です。

おわりに

長くなったので一旦ここで終了です。実際に触ってみた感じですが、ぼっち開発な自分でもかなり使えそうな感じです。 問題があるとすればネット回線が太いところでないとむずかしいのかなと。

【関連】 uepon.hatenadiary.com

ProtoPediaに作品を登録してみる

ハッカソンに出場すると、作った作品をなにかに残しておきたいことがあります。そんなときにはProtoPediaに作品の情報を残しておきましょう!

作品を一過性ものではなく、記録しておくことで開発の継続や思い出とともに残しておくことができます。また、ハッカソンによっては事前の審査をするために、書いてほしいということもありますが、初めて使うときには戸惑いもあるかもしれません、作品作りに一生懸命になるのも重要ですが、登録自体は簡単なのでぜひ残しておきましょう。

その他、作品の情報も載っているので次の作品の参考にもなるかもしれませんよ。

ProtoPediaprotopedia.net

f:id:ueponx:20190221113408p:plain

今回の項目は以下の通りです。

  • ProtoPediaにログインする
  • 作品を投稿する
  • 登録ができたら
  • 過去に登録した自分の作品一覧を確認する

ProtoPediaにログインする

Protopediaにログインすると以下のような画面になります。

f:id:ueponx:20190220233614p:plain

ProtoPediaは作品の記録ユーザー登録が必要になるので、まずはユーザー登録の作業をします。画面の右上にある【ログイン】ボタンをクリックします。

f:id:ueponx:20190221092954p:plain

クリックすると以下のような画面に遷移します。この画面でSNS連携をしたログインを行うか、それらとは関連の無い登録を行うかを選択することになります。SNS連携したログインを行う場合には以下の登録のステップは不要です。(連携するSNSのアカウントのボタンをクリックして先に進んでください。)

f:id:ueponx:20190220233639p:plain

【アカウント作成】のタブをクリックして登録します。

f:id:ueponx:20190221094040p:plain

クリックするとアカウントの作成画面に遷移します。登録作業はこの画面の右側のフォームに入力をすることになります。

f:id:ueponx:20190220233650p:plain

全パラメータは以下の様になっています。

f:id:ueponx:20190221095431j:plain

必要となる情報は以下の様になっています。赤字は必須項目です。

  • メールアドレス(必須)
  • Username(必須)
  • 名前・姓名(必須)
  • プロフィール写真
  • URL(ブログなどの)
  • 自己紹介文
  • 利用規約のチェック(必須)

入力が終わったら、【アカウントの作成】のボタンをクリックします。これで登録は完了です。

f:id:ueponx:20190221100445p:plain

登録して、ログインすると以下のような画面に遷移します。

f:id:ueponx:20190221113316p:plain


作品を投稿する

ログイン状態になっていなかった場合にはログインを行ってください。

f:id:ueponx:20190221114434p:plain

ログイン状態で画面上部のメニューにある【Prototypes】をクリックしてプルダウンから【投稿する」をクリックする。

f:id:ueponx:20190221101517p:plain

クリックするとPrototypeの作成画面に遷移します。

f:id:ueponx:20190220233807p:plain

フォームは以下の様になっていて

f:id:ueponx:20190221102037j:plain

登録する項目は以下の通りになります。

  • Title(必須)
  • ステータス(必須)【アイデア段階・開発中・完成作品】のいずれか
  • 画像(5画像まで)
  • 動画(動画をアップロードしたURL)
  • Body(作品の概要)
  • API・素材等
  • タグ
  • プロトタイプのURL(もし作品のホームページを作っているようであれば登録)
  • チーム名
  • チームメンバー(一人以上の登録が必須)
  • 連絡先メールアドレス(必須)
  • Wow(この作品のアピール)

API・素材等、タグ、チームメンバーの登録

API・素材等】の入力

入力の補完が行われますので関連するものは出てきます。(前方一致というわけではないようです。)

f:id:ueponx:20190221111020p:plain

【タグ】の入力

タグに関しても関連する技術などを入力しておくといいかなと思います。あとハッカソンによっては、ここにハッカソンの名前などを入力することを言われることもあります。入力中の補完も可能のようです。

f:id:ueponx:20190221111405p:plain

【チームメンバー】の入力

チームメンバーに関してもすでに登録をしていれば、入力の保管(アルファベットのみ)が行われます。自分以外のメンバーにもできればProtoPediaに登録してもらってください。

f:id:ueponx:20190221111412p:plain

メンバーを増やす際は【チームメンバーを追加】ボタンをクリックしてチームメンバーのエントリー行を増やします。

f:id:ueponx:20190221112147p:plain

登録ができたら

登録が終了したら【保存】ボタンをクリックします。事前に【Preview】ボタンを押せば、登録した作品がどのように見えるかわかります。登録したあとも編集はできますので、こまめに編集していくのがいいと思います。

f:id:ueponx:20190221104331p:plain

作品の登録が終わると以下のような画面になります。これで登録は完了となります。

f:id:ueponx:20190222001329j:plain

【編集】のタブをクリックすれば再編集も可能です。

f:id:ueponx:20190221112732p:plain

過去に登録した自分の作品一覧を確認する

ログインした状態で【アカウント情報】をクリックすると、これまでに過去に作られた自分の作品が一覧化された状態で登録が確認できます。

f:id:ueponx:20190222004431p:plain

クリックすると以下のように一覧で表示されます。

f:id:ueponx:20190222005006j:plain

終わりに

ProtoPediaの簡単な使い方を書いてみました。ハッカソンなどでは締切が見えた段階で書くようにいわれるので、事前に使い方を知っておくことや、製作途中でも途中経過を入れておくといいかなと思います。

Togetterの内容をGoogleChromeの検証機能でスクレイピングし、WordCloudで可視化してみる

会社での一山越えたのですが、まだまだいろいろと作業があるので微妙なところです。

【イベントページ】

www.ctv.co.jp

さてその山となったイベントがTogetterのまとめページにまとめられたので、今回のエントリーではそのまとめページスクレイピングして、 Wordcloudで処理してみようと考えました。簡単に考えてTogetterのデータはTitter経由で書き込まれているので、以前行ったのと同様に Twitterの書き込みを取得すればいいかなと思ったのですが、同じことをしても面白くないなと思ったのでTogetterのWebサイトをスクレイピングして データの取得を行おうと思います。

【Togetterのまとめページ

togetter.com

WordCloudに関しては下記のエントリーを参考にすればできるかと思います。

【関連】 uepon.hatenadiary.com

Togetterからのスクレイピング処理

基本的にはhttps://togetter.com/li/というURLの末尾にIDをつけることでそれぞれのまとめページに飛ぶことができるようになっています。 今回のターゲットのURIhttps://togetter.com/li/1317653となります。

WebのスクレイピングなのでpythonRequestsモジュールとBeautifulSoup4モジュールを使用して取得したあとにHTMLの解析を行うという形が一般的ですが、 今回は別の方法としてGoogle Chromeの検証機能を使用してスクレイピングを行うことにしました。これだとほとんどコードを書く必要がないのも魅力です。 ただページごとに取得処理を行う必要がある点は面倒ですが…。

【参考】

luuluuul.com

ページを右クリックして表示されるメニューの一番下にある【検証】を選択すると

f:id:ueponx:20190217230549p:plain

検証ツールがWindow内に表示されます。

f:id:ueponx:20190217231359p:plain

その中の【Elements】のタブのHTMLのDOMのツリー構造からTwitterの書き込み部分のものを探していきます。

f:id:ueponx:20190217233342p:plain

該当する部分が見つかったら【console】のタブに移動して出力をさせていきます。ツリー上の要素を表示させるには

$$("【CSSセレクタ】")

とすれば要素の表示が行われます。bodyタグの内容を表示させる場合には

$$("body")

とすればOKです。

f:id:ueponx:20190217233418p:plain

この仕組みを使用すれば、あとはループ処理を使うことで書き込みの文書をすべて取得することができます。 TogetterのTitterでの書き込みの本文だけを取得する場合には以下のようなスクリプトをコンソール上にコピーペーストすれば 出力をクリップボード上にコピーすることができます。

var result = "";
for (var i = 0; i < $$("div.tweet_box div.list_box.type_tweet div.tweet").length; i++) {
    result += $$("div.tweet_box div.list_box.type_tweet  div.tweet")[i].innerText + "\r";
}
//console.log(result);

//クリップボードにコピー
var ta = document.createElement('textarea');
ta.value = result;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
ta.parentElement.removeChild(ta);

f:id:ueponx:20190217233549p:plain

このスクリプトの実行を行うと以下のようなデータが取得されます。(クリップボードにコピーされます。)

【hackchu2019_log.txt】

IBMは〇〇テレビハッカソン【HACK-CHU! 2019】を応援します
今週末土曜日は予選【アイデアソン】天気予報では雪マークが出ているようなので当日まで体調を整えて挑みましょう!
ctv.co.jp/hackathon2019/
#hackchu #中京テレビ #IBM #TryIBMDev pic.twitter.com/92WKLrA1Ip
都内は雪が降ってきました
今日は長い一日になりそうです。
#hackchu pic.twitter.com/GFhUn3lelJ
今日は〇〇テレビ主催のハッカソン「Hack-Chu(読み方:ハックチュウ)」のスポンサーメンターとして参加します。昨年の写真見てるとみんな良い笑顔でいらっしゃる今年はどんな感じになるか楽しみです
…(以下略)…

複数ページにまたがる場合にはこれをページ数ごとに行うことでまとめられたすべてを取得できます。

WordCloudでの処理

あとは文面を取得できたのでこれをWordCloudで処理させていけばOKです。 必要であれば以下のパッケージを事前にインストールしておいてください。

(venv) >pip install wordcloud
(venv) >pip install matplotlib
(venv) >pip install janome

WordCloudの事前処理

WordCloudで処理する前にJanome形態素解析を行います。 形態素解析をそのままやってしまうとノイズの多いデータになってしまうので、できるだけここでいらない語句は省くのがいい結果を得られるコツだと思います。

形態素解析の処理】

from janome.tokenizer import Tokenizer
import codecs

wakachi = ''

with codecs.open('./wakachi_log.txt', mode='w', encoding='utf-8') as fw:
    None

t = Tokenizer()
with open('hackchu2019_log.txt', 'r', encoding="utf-8") as f:
    for tweet in f:
        if tweet.startswith('#'):
            continue
        tweet = tweet.split('pic.twitter.com')[0].strip()
        tweet = tweet.split('ctv.co.jp')[0].strip()
        tweet = tweet.replace('#hackchu', '')
        # print(tweet)
        word_list = []
        tokens = t.tokenize(tweet)
        for token in tokens:
            word = token.surface
            word_base = token.base_form
            partOfSpeech = token.part_of_speech.split(',')[0]
            if token.base_form in ["ある", "なる", "こと", "よう", "そう",
                                   "これ", "それ", "する", "いる", "いい",
                                   "やる", "わかる","自分", "こちら","いう","サービス",
                                   "くる","いう","かける","行く","やつ","中京",
                                   "使える", "来る","ない","ww", "思う", "ハッカソン","予選",
                                   "見る", "持つ", "〇〇TV","チーム","TV"]:
                continue
            if partOfSpeech in ["形容詞", "動詞", "名詞", "代名詞", "副詞"]:
                if (partOfSpeech == "名詞"):
                    if (token.part_of_speech.split(',')[1] in ["数", "接尾", "助数詞", "非自立"]):
                        continue
                elif (partOfSpeech == "動詞"):
                    if (token.part_of_speech.split(',')[1] not in ["自立"]):
                        continue
                elif (partOfSpeech == "形容詞"):
                    if (token.part_of_speech.split(',')[1] not in ["自立"]):
                        continue
                elif (partOfSpeech == "副詞"):
                    if (token.part_of_speech.split(',')[1] in ["助詞類接続"]):
                        continue
                word_list.append(word_base)

        wakachi = " ".join(word_list)
        with codecs.open('./wakachi_log.txt', mode='a', encoding='utf-8') as fw:
            print(wakachi, file=fw)

この処理で以下のような形態素解析後の分かち書きされたテキストデータを得ることができます。

【wakachi_log.txt】

IBM テレビハッカソン HACK - CHU ! 応援
今週 土曜日 アイデアソン 天気 予報 雪 マーク 出る 当日 体調 整える 挑む
都内 雪 降る
今日 長い
今日 テレビ 主催 Hack - Chu ( 読み方 : ハックチュウ )」 スポンサーメンター 参加 昨年 写真 みんな 良い 笑顔 いらっしゃる 今年 感じ 楽しみ
テレビハッカソン HACK - CHU !」
…(以下略)…

あとはこの出力をWordCloudで処理させるだけです。

WordCloudでの処理

あとは、WordCloudで処理するだけです。 以下のコードで日本語のフォント指定をすれば無事に処理されます。

import os
from wordcloud import WordCloud
contents = open('wakachi_log.txt', encoding="utf-8").read()
fpath = 'C:\\Windows\\Fonts\\meiryo.ttc'
wordcloud = WordCloud(background_color="white", font_path=fpath, width=900, height=500).generate(contents)
wordcloud.to_file("./wordcloud.png")

処理後に以下のような画像データが表示されます。

f:id:ueponx:20190217022855p:plain

無事にデータが取得できました。

終わりに

Pythonを使ってスクレイピングするのもいいですが、Javascriptなどを実行しないと行けないような場合は単純な処理ではだめですが、 Google Chromeの検証機能を使えばあまり気にせずにスクレイピングを行い、該当のデータを取得することはできそうです。 どちらが手間かを考えて取捨選択していけばいいかなとは思いました。

ChromeOSにDockerをインストールしてみる

自分の中で旬になっているChromeOSネタです。 今回はChromeOSに折角入っているDebianがあるので、そこにDockerをインストールしてみたというものです。 Dockerがあれば、ある程度開発もできるかな?と。ただ、Dockerをあまり知らないのでこれからのお勉強になりますけどね。

Dockerのインストールを行う

ChromeOSにインストールされているのはDebianなので、Debianのインストールの手順を参考にしていきます。 Dockerのページに既にあるのでこれを参考にしてインストールをしていきます。

$ uname -a
Linux penguin 4.19.4-02480-gd44d301822f0 #1 SMP PREEMPT Thu Dec 6 17:45:27 PST 2018 x86_64 GNU/Linux

$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux 9.7 (stretch)
Release:        9.7
Codename:       stretch

【参考】 docs.docker.com

手順としては

  1. 必要となるパッケージをインストール
  2. aptリポジトリへの追加の事前作業(apt-keyはパッケージを認証するのに使用するキーの一覧を管理するコマンドで、このキーで認証されたパッケージは信頼するできるものになります。)
  3. aptリポジトリへ追加
  4. aptリポジトリ更新(apt update
  5. aptリポジトリの追加
  6. dockerパッケージのインストール

公式ページの手順を参考にインストールを行っていきます。

$ sudo apt update
$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg2 \
    software-properties-common
$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
$ sudo apt-key fingerprint 0EBFCD88
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/debian \
   $(lsb_release -cs) \
   stable"
$ sudo apt update
$ sudo apt install docker-ce docker-ce-cli containerd.io

インストールができたか確認してみます。

$ sudo docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

f:id:ueponx:20190213223314p:plain

無事に動作しました!

インストールが終わったのでhttpサーバを起動してみる!

では、nginxのコンテナイメージを使ってhttpサーバーを起動してみます。 使用するコンテナは以下のものです。

hub.docker.com

では起動してみます。

$ docker run -d -p 8080:80 --name webserver nginx
docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.39/containers/create?name=webserver: dial unix /var/run/docker.sock: connect: permission denied.
See 'docker run --help'.

permissionエラーがでてしまいます。その他のコマンド(docker inforやdocker versionなど)でも同様のエラーがでてしまうので、一般ユーザーでもdockerを使用できるように設定をします。 permissionエラーの理由はDockerではUnixポートを使用していますが、このUNIXポートがrootユーザのみアクセスできるためというのが理由になります。 そのため、新たにdockerを使用するユーザーグループを作成し、そのグループに使用するユーザーを追加する必要があります。

公式ページにも一般ユーザーで使用するための設定方法が記述されています。

docs.docker.com

$ sudo groupadd docker
$ sudo usermod -aG docker $USER

公式ページにはコンソールのログアウトを行えばいいと記載されていましたが、実際にはログアウトした後再ログインする必要がありました。(コンソールからlogoutコマンドではだめでした) この部分は、ChromeOS固有の部分だと思います。

ログインしたあとにコンソールで先程エラーになっていたコマンドを実行してみると…

$ docker version
Client:
 Version:           18.09.1
 API version:       1.39
 Go version:        go1.10.6
 Git commit:        4c52b90
 Built:             Wed Jan  9 19:35:59 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          18.09.1
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.6
  Git commit:       4c52b90
  Built:            Wed Jan  9 19:02:44 2019
  OS/Arch:          linux/amd64
  Experimental:     false

無事に動作するようになりました。

改めてnginxを使用してwebserverを起動します。その際、ポートフォワードをしてローカルからの8080へのアクセスをコンテナの80ポートにattachするようにしています。

$ docker run -d -p 8080:80 --name webserver nginx

オプション

  • --name 【使用したいイメージ名】 : 使用したいイメージ名を指定する
  • -d : コンテナ実行時にバックグラウンドで起動する
  • -p 【ホストのポート番号】:【コンテナのポート番号】 : ポートフォワード設定

イメージがない場合には自動的にダウンロードが行われます。

f:id:ueponx:20190213223644p:plain

docker psで確認するとコンテナの動作が確認できます。

f:id:ueponx:20190213223849p:plain

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

$ docker run -d -p 8080:80 --name webserver nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
6ae821421a7d: Pull complete 
da4474e5966c: Pull complete 
eb2aec2b9c9f: Pull complete 
Digest: sha256:dd2d0ac3fff2f007d99e033b64854be0941e19a2ad51f174d9240dda20d9f534
Status: Downloaded newer image for nginx:latest
59a28a1f0da119d574ecf910be18bbeebe32a5a0f86542d906b264e32ca40e26

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
59a28a1f0da1        nginx               "nginx -g 'daemon of…"   7 seconds ago       Up 3 seconds        0.0.0.0:8080->80/tcp   webserver

docker psコマンドで動作を確認できていますが、実際にこのコンテナへWebブラウザからアクセスを行います。ポートフォワードの設定を行っているのでローカルの8080ポートへアクセスします。

f:id:ueponx:20190213223914p:plain

問題なくアクセスできるようになりました。

おわりに

ChromeOSにもDockerは動作できました。今回は非力なPCなので重たい処理はさせられませんが、 Webのチェックなどでは使用できないこともなさそうです。

ここまでで、Chrome拡張でVNCの動作、Linuxのコンテナ内でのNode.js、Python、Docker、Visual Studio Codeまでインストール・動作確認できているので ライトな開発環境としてもわりと本格的に考えてもいいのかなと思いはじめています。あとはBonjour(Avahi)が使えると本当にいいのですけど…

【関連記事】

uepon.hatenadiary.com

uepon.hatenadiary.com

Word2vecで遊んでみた

今年の大きめの仕事が一段落ついた(本当は来月までやる)こともあり、時間があったのでちょっと触ってみましたというエントリーです。

年末ぐらいからWord2vecを使ってみたいなあとは思っていたのですが、なかなか手をつけられませんでしたがようやく手をつけることができました。

Word2vecとは…

en.wikipedia.org

ということです。よくわかっていない…

なんとなく大量のテキストデータを解析し、各単語の意味をベクトル表現化し、コーパス内の単語同士の意味の近さを求めたり、 単語同士の意味を演算ができるようにするものなのかなと思います。ここでいう意味というのは一般的な言葉で言う意味とは少し違う 概念(概念的な近さ?)だと思ったほうがいいのかもしれません。ちなみにコーパスはテキストや発話を大規模に集めてデータベース化した言語資料されたものをいうようです

とはいっても、まずは触ってみたほうがいいので

f:id:ueponx:20190210115552p:plain

って感じで使ってみることにします。以下にとてもよいエントリーがあったので参考にしました。

【参考】 qiita.com

参考にしたエントリーは青空文庫に入っているデータを取得しコーパスとして使用していますが、そんな高尚なことにあまり興味がないので、今回は平成仮面ライダー20周年というメモリアルイヤーでもあるので、Wikipediaにある平成仮面ライダーの情報をコーパスとして取得していろいろ遊んで見ようと思います。

Word2vecの使い方は以下のような手順になると思います。

  1. コーパスとなるデータの取得(大規模なテキストデータ)
  2. 取得されたテキストデータの形態素解析を行い単語単位で取得
  3. Word2vecの処理を行い、単語のベクトル化
  4. ベクトル化されたデータを元に行いたい演算を行う

これが簡単な手順になるかと思います。 単語のベクトル化はかなりの計算量があるのでRaspberryPiなどではかなり時間がかかる処理になります。可能であればPCやクラウドのマシンパワーで なんとかしたほうがいいかなと思います。参考にしたエントリーではIBM Cloudを使用しているのでかなり理にかなった感じの処理になっているかなと思います。 ただJupyter Notebook経由でのテキストデータの処理に関しては少し癖があるように感じたので、自分は手元にあるPCを使用しました。 (ベクトル化されたデータは保存して再利用可能なので別の処理系で演算することは可能です。)

では進めてみます。

今回は

  • テキストの取得処理に関しては…requestsBeautifulSoup
  • 形態素解析には…janome
  • ベクトル化には…word2vec

を使用しています。インストールされていない場合にはpipでモジュールのインストールを行いましょう。

$ pip install requests
$ pip install beautifulsoup4
$ pip install janome
$ pip install word2vec

Wikipediaからデータを取得する

ホントはこういうやり方はあまりオススメしないのですが(Wikipediaに不要なアクセスが増えるので本来であればDBをローカルに持ってくるのが望ましい) この処理は機械的にやるのではなく、ちょっと使う程度で控えましょう。

requestsモジュールでデータを引っ張ってきたHTMLファイル内にある

タグの中身をBeautifulSoupで取得します。

【getWikipedia.py】

import requests
from bs4 import BeautifulSoup

link = "https://ja.wikipedia.org/wiki/"
keyword = ["仮面ライダークウガ", "仮面ライダーアギト", "仮面ライダー龍騎", "仮面ライダー555", "仮面ライダー剣",
           "仮面ライダー響鬼", "仮面ライダーカブト", "仮面ライダー電王", "仮面ライダーキバ", "仮面ライダーディケイド",
           "仮面ライダーW", "仮面ライダーオーズ/OOO", "仮面ライダーフォーゼ", "仮面ライダーウィザード", "仮面ライダー鎧武/ガイム",
           "仮面ライダードライブ", "仮面ライダーゴースト", "仮面ライダーエグゼイド", "仮面ライダービルド", "仮面ライダージオウ"]

for word in keyword:
    with requests.get(link + word) as response:
        # responseはhtmlのformatになっている
        html = response.text
        soup = BeautifulSoup(html, "lxml")
        # <p>タグを取得
        p_tags = soup.find_all('p')
        print(p_tags)
        print('----')

以下のような感じデータが取得されます。

> python getWikipedia.py
[<p class="mw-empty-elt">
</p>, <p><b>仮面ライダークウガ</b>』(かめんライダークウガ)は、<a href="/wiki/2000%E5%B9%B4" title="2000年">2000</a><a href="/wiki/%E5%B9%B3%E6%88%90" title="平成">平成</a>12年)<a href="/wiki/1%E6%9C%8830%E6%97%A5" title="1月30日">130</a>から<a href="/wiki/2001%E5%B9%B4" title="2001年">2001</a>(平成13年)<a href="/wiki/1%E6%9C%8821%E6%97%A5" title="1月21日">121</a>まで、<a href="/wiki/%E3%83%86%E3%83%AC%E3%83%93%E6%9C%9D%E6%97%A5" title="テレビ朝日">テレビ朝日</a>系で毎週日曜8:00 - 8:30<a href="/wiki/%E6%97%A5%E6%9C%AC%E6%A8%99%E6%BA%96%E6%99%82" title="日本標準時">JST</a>)に全49話が放映された、<a href="/wiki/%E6%9D%B1%E6%98%A0" title="東映">東映</a>制作の<a href="/wiki/%E7%89%B9%E6%92%AE%E3%83%86%E3%83%AC%E3%83%93%E7%95%AA%E7%B5%84%E4%B8%80%E8%A6%A7" title="特撮テレビ番組一覧">特撮テレビドラマ</a>作品。

(中略)

</p>, <p>監督は本編チーフ助監督の大峯靖弘が、台本は東映プロデューサーの白倉伸一郎がそれぞれ担当した<sup class="reference" id="cite_ref-U16344_141-3"><a href="#cite_note-U16344-141">[84]</a></sup>。大峯はチーフ助監督としてスケジュール管理も行っていたことから、本編撮影の合間を縫って短時間で撮るという体制であった<sup class="reference" id="cite_ref-U16344_141-4"><a href="#cite_note-U16344-141">[84]</a></sup>。第4.5話の飯島寛騎と瀬戸利樹は別日に撮影しており、編集で共演しているように演出している<sup class="reference" id="cite_ref-U16344_141-5"><a href="#cite_note-U16344-141">[84]</a></sup>。第13.5話で使用しているユルセンのぬいぐるみは大峯の私物である<sup class="reference" id="cite_ref-U16344_141-6"><a href="#cite_note-U16344-141">[84]</a></sup></p>]
----

HTMLのタグが結構はいっているのでもう少しクレンジングをしたほうがいいのかなと思いますが今回はこのまま使用します。

続いては形態素解析を行います。

janomeによる形態素解析

取得されたデータをjanomeモジュールを使用して形態素解析を行います。

janomeに関しては以前のエントリーで触れているので参考にします。

【参考】 uepon.hatenadiary.com uepon.hatenadiary.com

先程、取得できたデータを形態素解析していきます。 形態素解析を行った結果はpwiki.txtというテキストファイルに入れて、以降はWebアクセスなしでも後続の処理ができるようにしています。

【getWiki2wakachi.py】

from janome.tokenizer import Tokenizer
import requests
from bs4 import BeautifulSoup
import codecs

link = "https://ja.wikipedia.org/wiki/"
keyword = ["仮面ライダークウガ", "仮面ライダーアギト", "仮面ライダー龍騎", "仮面ライダー555", "仮面ライダー剣",
           "仮面ライダー響鬼", "仮面ライダーカブト", "仮面ライダー電王", "仮面ライダーキバ", "仮面ライダーディケイド",
           "仮面ライダーW", "仮面ライダーオーズ/OOO", "仮面ライダーフォーゼ", "仮面ライダーウィザード", "仮面ライダー鎧武/ガイム",
           "仮面ライダードライブ", "仮面ライダーゴースト", "仮面ライダーエグゼイド", "仮面ライダービルド", "仮面ライダージオウ"]

corpus = []
wordslist = []
t = Tokenizer()
for word in keyword:
    with requests.get(link + word) as response:
        # responseはhtmlのformatになっている
        html = response.text
        soup = BeautifulSoup(html, "lxml")
        # <p>タグを取得
        p_tags = soup.find_all('p')
        for p in p_tags:
            tokens = t.tokenize(p.text)
            for token in tokens:
                # print("表層形:",token.surface,"\n"
                #       "品詞:",token.part_of_speech.split(',')[0],"\n"
                #       "品詞細分類1:",token.part_of_speech.split(',')[1],"\n"
                #       "品詞細分類2:",token.part_of_speech.split(',')[2],"\n"
                #       "品詞細分類3:",token.part_of_speech.split(',')[3],"\n"
                #       "活用型:",token.infl_type,"\n"
                #       "活用形:",token.infl_form,"\n"
                #       "原形:",token.base_form,"\n"
                #       "読み:",token.reading,"\n"
                #       "発音:",token.phonetic)
                word = ''
                if (token.part_of_speech.split(',')[0] in ['代名詞', '名詞', '固有名詞', '動詞', '形容詞', '形容動詞']) and \
                    (token.part_of_speech.split(',')[1] not in ['数','自立', 'サ変接続', '非自立', '接尾', '副詞可能']) and \
                    (token.base_form not in ['これら', 'せる', 'これ']):
                            word = token.base_form
                            wordslist.append(word)
        corpus.append(wordslist)
        # print(corpus)

with codecs.open("pwiki.txt", "w", "utf-8") as f:
    f.write(str(corpus))

プログラム内で以下のような処理を行っていますがが、明らかに不要(解析が難しい数詞や接尾などの単語)やで代名詞のこれなどの単語に関しては コーパスに入らないようにしています。

                word = ''
                if (token.part_of_speech.split(',')[0] in ['代名詞', '名詞', '固有名詞', '動詞', '形容詞', '形容動詞']) and \
                    (token.part_of_speech.split(',')[1] not in ['数','自立', 'サ変接続', '非自立', '接尾', '副詞可能']) and \
                    (token.base_form not in ['これら', 'せる', 'これ']):
                            word = token.base_form
                            wordslist.append(word)

テキストデータによってはこのような処理も必要かと思います。出来上がったファイルを見ると

【pwiki.txt】

仮面 ライダークウガ ライダークウガ 平成 月 平成 月 テレビ朝日 JST 東映 特撮 テレビ ドラマ 作品
A New Hero A New Legend 英雄 伝説 OP 最後 タイトル 左上 右
テレビ シリーズ 仮面ライダー BLACK RX テレビ シリーズ 仮面ライダー J 仮面ライダー 作品 平成 仮面ライダー シリーズ クウガ 漢字 空 我 名 漢字 名前 石森 プロ 社長 小野寺 章 クワガタ 語感
昭和 仮面ライダー シリーズ 昭和 ライダー 世界 昭和 ライダー オマージュ 台詞 随所 注 昭和 ライダー 違い 仮面ライダー 怪人 人間 世界 悪 秘密 結社 注 劇 仮面ライダー 名称 人間 医療 技術 臓器 人間 東映 人間 影 主人公 平成 ライダー シリーズ
作品 特撮 ヒーロー 番組 新た 試み 随所 身近 現実 特撮 ヒーロー 番組 グロンギ 独自 言語 文化 クウガ 警察 技 作 劇 スポット 回 周囲 人々 社会 ヒーロー 悪 過程 ヒーロー ドラマ 視点 一般 ドラマ 視点 エピソード 基本 エピソード スタイル スタイル 作品
商業 ベルト 人気 好成績 ドラマ 作 劇 シーン 回 月 クウガ 最終 形態 アルティメットフォーム 雄介 幻影 月 最終 直前 作中 注 出番 逆 スポンサー 玩具 会社 形態 アメイジングマイティ 商品 販促 番組 異例 め 最終 A パート B パート 間 CM ED 主役 ヒーロー シーン 主人公 五代 雄介 出番 注

(以下略)

いい感じで出力されています。

形態素解析を行ったあとのコーパスからWord2vecのモデルを生成する

pwiki.txtを使用してモデルを生成します。

モデルの生成はword2vecモジュールのgensim.modelsを使用します。 Word2vecを使用すると意外とたくさんWarningが発生するので、問題がなさそうだったら冒頭にwarningsモジュールをimportして抑止すると見やすくなります。

【wakachi2model.py】

import warnings
warnings.filterwarnings(action='ignore', category=UserWarning, module='gensim')
warnings.filterwarnings(action='ignore', category=FutureWarning)
from gensim.models import word2vec
import codecs

corpus = word2vec.LineSentence("pwiki.txt")
model = word2vec.Word2Vec(corpus, size=100 ,min_count=5,window=5,iter=100)
print(model)
model.save("pwiki.model")

これでモデル(pwiki.model)が生成されました。このモデルファイルがあれば再度データの取得など行わず、すぐに演算を行うことができます。

モデルを使用して演算を行う

以下のようにすれば類似度のランキングや2つのキーワードの類似度を見ることができます。

【oprateWord2vec.py】

import warnings
warnings.filterwarnings(action='ignore', category=UserWarning, module='gensim')
warnings.filterwarnings(action='ignore', category=FutureWarning)
from gensim.models import word2vec

model = word2vec.Word2Vec.load("pwiki.model")

# ドライバーと類似している単語を見る
print('ドライバーと関連する単語ベスト10')
similar_words = model.wv.most_similar(positive=[u"ドライバー"], topn=10)
for key,value in similar_words:
    print('{}\t\t{:.2f}'.format(key, value))

print('-----')
# 2つの単語の類似度を計算
similarity = model.wv.similarity(w1=u"クウガ", w2=u"アギト")
print('クウガとアギトの類似度=>' + str(similarity))

similarity = model.wv.similarity(w1=u"ショッカー", w2=u"ディケイド")
print('ショッカーとディケイドの類似度=>' + str(similarity))

similarity = model.wv.similarity(w1=u"ジオウ", w2=u"ディケイド")
print('ジオウとディケイドの類似度=>' + str(similarity))

このような演算を行うと以下の様な結果が得られます。

Word2Vec(vocab=1819, size=100, alpha=0.025)
ドライバーと関連する単語ベスト10
凌     0.98
ヘルヘイム     0.96
戒斗      0.96
ゲネシスドライバー     0.96
戦     0.96
アーマードライダー     0.95
森     0.95
紋     0.95
不能      0.94
果実      0.94
-----
クウガとアギトの類似度=>0.890038
ショッカーとディケイドの類似度=>0.70078796
ジオウとディケイドの類似度=>0.8640115

上記のような値がでました。

考察

  • ドライバーという単語に近い意味を持つ単語は鎧武ってこと?…人数が多くてドライバーの種類も多かったということなんでしょうか?
  • クウガとアギトの類似度は結構高いようです…ストーリーでもそういう雰囲気はありましたね~
  • ショッカーとディケイドは近いけどそこまでではない?…アポロガイストあたりの影響かな?
  • ジオウとディケイドの類似度はまあ高い…お祭りライダーだからかも?ディケイドがジオウにでているのもあるかな。

なんとなく面白いデータが取れたような気がします。こういうのを考えてみるのもわりと面白いですねえ。

今回のコードをまとめたものを以下においておきます。

import warnings
warnings.filterwarnings(action='ignore', category=UserWarning, module='gensim')
warnings.filterwarnings(action='ignore', category=FutureWarning)
from gensim.models import word2vec
from janome.tokenizer import Tokenizer
import requests
from bs4 import BeautifulSoup
import codecs

# https://www.pytry3g.com/entry/gensim-word2vec-tutorial

link = "https://ja.wikipedia.org/wiki/"
keyword = ["仮面ライダークウガ", "仮面ライダーアギト", "仮面ライダー龍騎", "仮面ライダー555", "仮面ライダー剣",
           "仮面ライダー響鬼", "仮面ライダーカブト", "仮面ライダー電王", "仮面ライダーキバ", "仮面ライダーディケイド",
           "仮面ライダーW", "仮面ライダーオーズ/OOO", "仮面ライダーフォーゼ", "仮面ライダーウィザード", "仮面ライダー鎧武/ガイム",
           "仮面ライダードライブ", "仮面ライダーゴースト", "仮面ライダーエグゼイド", "仮面ライダービルド", "仮面ライダージオウ"]

corpus = []
t = Tokenizer()
for word in keyword:
    with requests.get(link + word) as response:
        print(link + word)
        # responseはhtmlのformatになっている
        html = response.text
        soup = BeautifulSoup(html, "lxml")
        # <p>タグを取得
        p_tags = soup.find_all('p')
        for p in p_tags:
            r = []
            tokens = t.tokenize(p.text)
            for token in tokens:
                # print("表層形:",token.surface,"\n"
                #       "品詞:",token.part_of_speech.split(',')[0],"\n"
                #       "品詞細分類1:",token.part_of_speech.split(',')[1],"\n"
                #       "品詞細分類2:",token.part_of_speech.split(',')[2],"\n"
                #       "品詞細分類3:",token.part_of_speech.split(',')[3],"\n"
                #       "活用型:",token.infl_type,"\n"
                #       "活用形:",token.infl_form,"\n"
                #       "原形:",token.base_form,"\n"
                #       "読み:",token.reading,"\n"
                #       "発音:",token.phonetic)
                word = ''
                if (token.part_of_speech.split(',')[0] in ['代名詞', '名詞', '固有名詞', '動詞', '形容詞', '形容動詞']) and \
                    (token.part_of_speech.split(',')[1] not in ['数','自立', 'サ変接続', '非自立', '接尾', '副詞可能']) and \
                    (token.base_form not in ['これら', 'せる', 'これ']):
                            r.append(token.base_form)
            rl = (' '.join(r)).strip()
            corpus.append(rl)
            # print(corpus)

with codecs.open("pwiki.txt", "w", "utf-8") as f:
    f.write("\n".join(corpus))

corpus = word2vec.LineSentence("pwiki.txt")
model = word2vec.Word2Vec(corpus, size=100 ,min_count=3,window=5,iter=30)
print(model)
model.save("pwiki.model")

model = word2vec.Word2Vec.load("pwiki.model")

# ドライバーと類似している単語を見る
print('ドライバーと関連する単語ベスト10')
similar_words = model.wv.most_similar(positive=[u"ドライバー"], topn=10)
for key,value in similar_words:
    print('{}\t\t{:.2f}'.format(key, value))

print('-----')
# 2つの単語の類似度を計算
similarity = model.wv.similarity(w1=u"クウガ", w2=u"アギト")
print('クウガとアギトの類似度=>' + str(similarity))

similarity = model.wv.similarity(w1=u"ショッカー", w2=u"ディケイド")
print('ショッカーとディケイドの類似度=>' + str(similarity))

similarity = model.wv.similarity(w1=u"ジオウ", w2=u"ディケイド")
print('ジオウとディケイドの類似度=>' + str(similarity))
/* -----codeの行番号----- */