【sshec2】Github によるソースコード管理

前回のエントリで私が開発した OSS ツールの sshec2 をご紹介しました。

sfujimoto.hatenablog.com

今回から何回かに分けて、OSS ツールを開発する上で考慮した点を備忘録で記載していきます。

まずはソースコード管理。ソースコード管理(ソースコードリポジトリ)には Github を利用しました。

ソースコード管理

一人で開発する、誰とも共有する必要がない、一回作って終わりなプログラムであれば、ソースコード管理は必要ないでしょう。ただし逆に複数人で開発する必要がある、誰かと共有したい、継続的にプログラムを運用したい、といった用途のプログラムはソースコードリポジトリを利用すると非常に便利です。

  • 複数人で開発する
    例えば、一つのプログラムファイルを複数人で同時並行に修正していて、それをファイルサーバーで管理していた場合、何が起きるでしょうか?1人目が修正を終えてファイルサーバーに上書きアップロードしました。2人目も修正を終えてファイルサーバーに上書きアップロードしました。1人目の修正が上書きされています。もちろん、こういう環境では気をつけて、アップロードする前に Diff なりして、差分を確認することが一般的でしょうが、差分を忘れない保証はありません。

  • 誰かと共有したい
    今回は OSS ツールを開発しました。どうせなら多くの人に使っていただき、フィードバックを受け、より良いものにしたいと思っています。一般公開可能なソースコードリポジトリを利用することでプログラムを公開し、色んな人に見ていただくことが可能です。場合によってはより良いものに修正したプログラムを提案いただけるかもしれません。

  • 継続的にプログラムを運用したい
    プログラムを継続的に運用するのは簡単ではありません。バグが見つかれば改修する必要があります。新しい機能を追加したければ新しい機能を実装する必要があります。

ソースコード管理手法

ソースコード管理で良く話に上がるのは Git です。他にも SubversionMercurial という手法もありましたが、今は Git がデファクトスタンダードで利用できる人が多いと思うので特に比較せずに Git によるソースコード管理を選択しました。Git を利用することで上記を解決することができます。

  • 複数人で開発
    Git を利用すると、自身の更新内容を自身で自動で管理してくれます。それにより、ソースコードリポジトリにアップロードする時に自身の更新内容と、他の人が更新していないか、更新があった場合は内容を自動で比較し、バッティングしていること、どうバッティングしているのかを表示します。

  • 誰かと共有したい
    Git リポジトリSaaS があり、それらで公開することができます。

  • 継続的にプログラムを運用したい
    プログラムを運用する上で多くのツール、SaaS と連携することで圧倒的に楽にすることができます。Git リポジトリは多くの選択肢があり、ツールとの連携、他サービスとの連携も選択するポイントの一つとなります。

Git リポジトリのセキュリティレベル

次に Git を利用する上で Git のリポジトリのセキュリティレベルをどうするかです。Git リポジトリを利用する方法は多く存在します。Git リポジトリの比較の前に管理するプログラム・ソースコードのセキュリティレベルを考える必要があります。大きく分けて、3段階あるかと思います。アクセス制御のレベルが低い順に 3つ書きます。あくまでアクセス制御の話で Git リポジトリを運営する会社がどうのこうの言うつもりはありません。また閲覧権限と変更権限は別の問題ですので今回のスコープにはしません。命名はオレオレで分かりやすい名前を付けただけですのであしからず。

パブリックリポジトリ

ソースコードは誰でも閲覧できます。

プライベートリポジトリ

ソースコードは登録したユーザーだけが閲覧できます。

プライベートサーバー

決まったユーザーだけサーバにアクセスでき、閲覧できます。

パブリックリポジトリと、プライベートリポジトリ/サーバーの違いは分かりやすいかと思います。認証が必要化かどうかです。
次にプライベートリポジトリとプライベートサーバーの違いを簡単に説明します。どちらも認証されたユーザーのみがアクセスできます。プライベートリポジトリは認証情報(例えば、ユーザー名とパスワード)のみで閲覧可否を判断します。一方、プライベートサーバーは認証情報に加えて、ネットワークの疎通性が追加されます。例えば社内ネットワークからのみアクセスできる、VPN を通してのみアクセスできるなど。大きな違いは認証情報が漏れた時の影響です。プライベートリポジトリは認証情報が漏れた時に悪用されます。しかし、プライベートサーバーは認証情報が漏れてもそもそもリポジトリを持つサーバーにアクセスできないので影響はありません。

今回は OSS ツールとしてプログラムを公開したかったのでパブリックリポジトリを選択しました。

パブリックリポジトリ

パブリックリポジトリもいくつか選択肢があります。

有名どころは下記の 2つです。

決めては今後のブログエントリの話に影響するのですが、Github の優れたインテグレーションです。sshec2 の開発には多くの SaaS を利用しています。それらは全てソースコードリポジトリと連携することで自動でサービスの処理が動作します。その上で多くの SaaSGithub と連携しているということで Github を選択しました。

Github を使う

それでは簡単に Github の利用方法をご紹介します。

github.com

ユーザー登録

トップページにある入力欄(ユーザー名、メールアドレス、パスワード)を入力するだけでユーザー登録できます。Github は色々なサービスと連携するのでログインはユーザー名、パスワードだけでなく、多要素認証(ワンタイムパスワード)を追加することを推奨します。

リポジトリ作成

New Repository からリポジトリ名を入力するだけでリポジトリを作成できます。リポジトリを作成すると、初期コミットまでに必要なコマンドが表示されるのでそのまま実行するだけでリポジトリにプログラムを配置することができます。

ソースコードの修正

一人でソースコードを修正する場合、一つのブランチで運用すればよいです。が、複数人が修正する場合、各自が好き勝手にソースコードをコミットすると、どこかで不整合が発生します。しかもそれは多々あります。そこで別のブランチを切って、修正が完了したタイミングで本番ブランチにマージするといった運用がよくあるパターンです。Github ではプルリクエストという機能があり、プルリクエストを利用することで掲示板のやり取りのようにマージ前に修正内容をレビューすることができます。

まとめ

簡単にですが、ソースコードリポジトリについて調べたことを簡単にまとめました。

【sshec2】EC2 への SSH 接続の管理を楽にするツールを公開しました

こんにちは、久しぶりのブログエントリです。
会社でもブログ書いてて、最近はほとんどそちらに書いてました。今回は会社のブログに書くような内容じゃなかったのでこちらのブログにエントリします。

私はインフラ屋で仕事ではほとんどプログラムを書かない趣味プログラマーですが、周りの刺激を受けて OSS 開発したくなりました。

主な目的は、、

  • プログラムを書くこと
  • プログラムを運用すること
  • CI/CD を取り入れたナウい自動化の仕組みを導入・運用すること

です。

欲しい成果物があったわけではなく、上記目的を体験するために開発しました。

本日は成果物の報告ということでエントリします。

sshec2

ec2ssh を Google 検索すると、色々なツールが出てきます。cookpad の方や、海外の方のリポジトリが多く引っかかります。

私が中でも「これいいな」と思ったのは下記リポジトリ内にある ec2ssh です。

GitHub - tilfin/homebrew-aws: AWS commands easy to manipulate on terminal

コマンド実行することで EC2 インスタンスの一覧を取得し、対象のインスタンスを選択することで SSH ログインできるというものです。すごくいいです。ただ私の業務上、色々な環境に、色々な方法でログインするケースがあり、マッチしないケースが多々ありました。そこでより柔軟なオプションで利用できる sshec2 を開発しました。

機能

基本機能は上記 ec2ssh と同じで、EC2 インスタンスを一覧表示し、インスタンスを選択することで SSH ログインできます。

加えて、柔軟なオプションが多くあります。

  • SSH ログインユーザー名の指定
    • デフォルトは ec2-user
  • SSH ログインユーザーの秘密鍵の指定
    • デフォルトは ~/.ssh/ 配下の
  • 踏み台サーバを介した多段 SSH 接続
    • 踏み台サーバも個別のユーザー名、秘密鍵を指定可能
  • ソフトウェア VPN 接続対応(対象インスタンスへの自動ルーティング追加)
  • SCP モード(ローカル←→EC2 双方向対応)

詳細はドキュメントに記載していますので是非ご覧ください。

Github

github.com

ドキュメント

Welcome to sshec2’s documentation! — sshec2 0.1.0 documentation

今後は主に CI/CD のところで何をやってきたかをエントリしています。予定は下記のようなエントリを書いていく予定です。

Elasticsearch クラスタをマルチノードでインストールする

前回はElasticsearch を Linux上、Dockerコンテナでシングルノードでインストール起動しました。

前回のエントリは下記をご参照ください。

sfujimoto.hatenablog.com

今回も Linux上、Dockerコンテナそれぞれでのクラスタ構築を実施します。

Elasticsearch クラスタ on Linux

まずは Linux OS 上で Elasticsearch クラスタを構築します。ノード数は2台とします。

環境

Elasticsearch インストール

Elasticsearch のインストールはシングルノードと同じです。

Java 8、Elasticsearch をインストールします。

# echo "[elasticsearch-5.x]
name=Elasticsearch repository for 5.x packages
baseurl=https://artifacts.elastic.co/packages/5.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md" > /etc/yum.repos.d/elastic.repo

# rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch

# yum install -y java-1.8.0 elasticsearch
:

# rpm -q elasticsearch java-1.8.0-openjdk
elasticsearch-5.1.1-1.noarch
java-1.8.0-openjdk-1.8.0.111-2.b15.el7_3.x86_64

2台ともにインストールします。

クラスタ設定

Elasticsearch の設定をします。クラスタを組むためには以下の設定が必要です。

項目 説明
discovery.zen.ping.unicast.hosts Elasticsearch のクラスタを構成する相手のホスト名、もしくはIPアドレスを指定します。
cluster.name Elasticsearch のクラスタ名を指定します。クラスタ構成とする場合、クラスタを構成する全てのノードで名前を合わせる必要があります。指定しない場合、デフォルトの```elasticsearch```が設定されます。
node.name Elasticsearch のノード名を指定します。指定は必須ではないですが、設定する場合はクラスタ内で異なるノード名を指定します。

今回は下記のような設定としています。

ノード1
# echo "network.host: 10.255.0.100, 127.0.0.1
cluster.name: my-cluster
node.name: node1
discovery.zen.ping.unicast.hosts: 10.255.0.101" > /etc/elasticsearch/elasticsearch.yml
ノード2
# echo "network.host: 10.255.0.101, 127.0.0.1
cluster.name: my-cluster
node.name: node2
discovery.zen.ping.unicast.hosts: 10.255.0.100" > /etc/elasticsearch/elasticsearch.yml

Elasticsearch 再起動

Elasticsearch サービスを再起動します。

# systemctl restart elasticsearch

動作確認

クラスタのノードを確認します。

# curl localhost:9200/_cat/nodes
10.255.0.101 3 97 1 0.12 0.08 0.08 mdi - node2
10.255.0.100 3 97 1 0.03 0.04 0.08 mdi * node1

2つのノードが表示され、クラスタが構成されていることがわかります。

Elasticsearch コンテナ クラスタ on Docker

続いて、Docker上で Elasticsearch クラスタを構築します。今回もノードは2台です。

環境

  • Docker ホスト : Docker for Mac 1.12
  • Elasticsearch : 5.1.1

Docker コンテナ起動

Elastic 社の Github リポジトリdocker-compose.ymlが提供されていますので利用します。

$ git clone https://github.com/elastic/elasticsearch-docker.git
:

$ tree elasticsearch-docker
elasticsearch-docker
├── LICENSE
├── Makefile
├── README.md
├── build
│   ├── elasticsearch
│   │   ├── Dockerfile
│   │   ├── bin
│   │   │   └── es-docker
│   │   ├── elasticsearch.yml
│   │   └── log4j2.properties
│   └── python
│       └── Dockerfile
├── docker-compose.hostports.yml
├── docker-compose.yml
└── tests
    ├── __pycache__
    │   └── test_xpack_basic_index_crud.cpython-27-PYTEST.pyc
    ├── es_acceptance.py
    ├── es_acceptance.pyc
    ├── test_basic_index_crud.py
    ├── test_xpack_basic_index_crud.py
    └── testdata.json

6 directories, 16 files

Makefilemakeコマンド)で簡単に利用できるのですが、このまま利用するとバージョンが 5.0.0-beta1 という若干古めのバージョンです。最新を利用したいので、Makefile に記載されているバージョンを 5.1.1 に書き換えます。

$ cd elasticsearch-docker
$ sed -i 's/ELASTIC_VERSION=5.0.1/ELASTIC_VERSION=5.1.1/g' Makefile

docker-compose でクラスタを起動します。

$ make run-es-cluster
docker pull docker.elastic.co/elasticsearch/elasticsearch-alpine-base:latest
latest: Pulling from elasticsearch/elasticsearch-alpine-base
:

数分で起動します。

動作確認

クラスタのノードを確認します。

$ curl -u elastic:changeme localhost:9200/_cat/nodes
172.18.0.2  7 87 4 0.47 0.43 0.27 mdi * P7MWTJw
172.18.0.3 11 87 4 0.47 0.43 0.27 mdi - QF4Caww

2つのノードが表示され、クラスタが構成されていることがわかります。

まとめ

いかがでしたでしょうか?

データストアのクラスタリングといえば、非常に設定が面倒なイメージがありますが、Elasticsearch は非常に簡単です。インストールだけでなく、設定も簡単なのは嬉しいですね。

Python で並行処理(マルチコア、マルチスレッド)を実装する

Python のマルチコア、マルチスレッドの実装を勉強する機会がありましたので、メモ書きします。

簡単に違いを説明すると、

  • マルチコア
    • 複数のプロセスを立ち上げることで複数のCPUコアを利用することができる
    • 得意なもの:CPUバウンドな並列処理も複数のCPUコアを利用することで効率化できる
    • 苦手なもの:並列処理間でプロセスが異なり、データの受け渡しが難しい
  • マルチスレッド
    • 一つのプロセスを立ち上げ、一つのCPUコアを時分割で利用することができる
    • 得意なもの:並列処理間も同じメモリ空間を利用することができる
    • 苦手なもの:CPUバウンドな並列処理はCPUがボトルネックになり、並列処理の効果が出ない

Python による並列処理

Python はマルチコア、マルチスレッドを実装するパッケージを標準で持っています。マルチコアにはmultiprocssingパッケージ、マルチスレッドにはconcurrentパッケージを利用します。マルチスレッドはthreadingパッケージもありますが、3.2系から追加されたconcurrentパッケージが簡単だったので、今回はconcurrentパッケージで説明します。

マルチコア

まずはマルチコアの実装方法です。

同時実行数制御

マルチコアの同時実行数制御はmultiprocessing.Poolクラスの引数でプロセス数を設定するだけです。

from multiprocessing import Pool
pool = Pool(4)

こちらは 4プロセスを起動します。

並列処理実行

Pool から別プロセスで処理を実行する場合、apply_asyncメソッドを利用します。

from multiprocessing import Pool
import time

def f():
    time.sleep(1)
    print(1)

pool = Pool(4)
pool.apply_async(f)
print(2)
time.sleep(2)

こちらは実行プロセスとは別のプロセスでf関数が実行されます。普通にf関数を呼び出すだけであれば、1 -> 2 の順に表示されますが、apply_asyncで別プロセスで処理を開始しているため、2 -> 1 の順に表示されます。

python ./multi_core_test.py
2
1

非同期処理に引数を渡す

apply_asyncメソッドの第二引数にタプル形式で渡すだけです。引数が一つでも、二つでもタプルに並べるだけです。

from multiprocessing import Pool
import time

def f(num):
    time.sleep(num)
    print(num)

pool = Pool(4)
pool.apply_async(f, (1, ))
print(2)
time.sleep(2)
python ./multi_core_test.py
2
1

非同期処理の終了を待機

先ほどのサンプルで最後に 2秒のスリープを入れています。これには理由があり、別プロセスは実行プロセスの子プロセスとして起動します。そのため、実行したプロセスは処理を終えると、別プロセスで処理が実行中であろうと、プロセスを終了します。ただ今回は 1秒のスリープと print による標準出力だけなので、スリープに必要な秒数が分かりますが、複雑な処理の場合、適切なスリープの設定は難しいでしょう。そこでmultiprocessingパッケージは並列処理と同期を取ることができます。

from multiprocessing import Pool
import time

def f():
    time.sleep(1)
    print(1)

pool = Pool(4)
result = pool.apply_async(f)
print(2)
result.wait()
print(3)

apply_asyncが返すApplyResultwaitメソッドにより、並列処理の実行の終了を待つことができます。

python ./multi_core_test.py
2
1
3

非同期処理の戻り値を受け取る

先ほどのサンプルでは戻り値を返していませんでしたが、今度はf関数で返した値を取得してみましょう。

from multiprocessing import Pool
import time

def f():
    time.sleep(1)
    print(1)
    return 4

pool = Pool(4)
result = pool.apply_async(f)
print(2)
print(result.get())
print(3)

apply_asyncが返すApplyResultgetメソッドにより、並列処理の実行の終了を待ち、戻り値を取得することができます。

python ./multi_core_test.py
2
1
4
3

マルチスレッド

まずはマルチスレッドの実装方法です。

同時実行数制御

マルチスレッドの同時実行数制御はconcurrent.futures.ThreadPoolExecutorクラスの引数でプロセス数を設定するだけです。

from concurrent.futures import ThreadPoolExecutor
pool = ThreadPoolExecutor(4)

こちらは 4プロセスを起動します。

並列処理実行

ThreadPoolExecutor から別スレッドで処理を実行する場合、submitメソッドを利用します。第一引数に並列実行したい関数を渡すだけです。

from concurrent.futures import ThreadPoolExecutor
import time

def f():
    time.sleep(1)
    print(1)

pool = ThreadPoolExecutor(4)
pool.submit(f)
print(2)
time.sleep(2)

こちらは別スレッドでf関数が実行されます。

python ./multi_thread_test.py
2
1

非同期処理に引数を渡す

submitメソッドの第二引数以降に引数となる値を渡すだけです。引数が一つの場合は第二引数に、引数が二つの場合は第二・第三引数に渡します。

from concurrent.futures import ThreadPoolExecutor
import time

def f(num):
    time.sleep(num)
    print(num)

pool = ThreadPoolExecutor(4)
pool.submit(f, 1)
print(2)
time.sleep(2)
python ./multi_thread_test.py
2
1

非同期処理の終了を待機

concurrent.futuresパッケージは並列処理と同期を取ることができます。as_completedを利用します。

from concurrent.futures import ThreadPoolExecutor, as_completed
import time

def f(num):
    time.sleep(num)
    print(num)

pool = ThreadPoolExecutor(4)
result = pool.submit(f, 1)
print(2)
as_completed([result]).next()
print(3)
python ./multi_thread_test.py
2
1
3

非同期処理の戻り値を受け取る

先ほどのサンプルでは戻り値を返していませんでしたが、今度はf関数で返した値を取得してみましょう。

from concurrent.futures import ThreadPoolExecutor, as_completed
import time

def f(num):
    time.sleep(num)
    print(num)
    return 4

pool = ThreadPoolExecutor(4)
result = pool.submit(f, 1)
print(2)
print(as_completed([result]).next().result())
print(3)

as_completedresultメソッドで戻り値を取得することができます。

python ./multi_thread_test.py
2
1
4
3

まとめ

簡単な紹介となりましたが、標準パッケージでも様々な機能を備えています。また公式ドキュメントに分かりやすく色々な機能を紹介されていますので是非そちらもご参考ください。

http://docs.python.jp/3/library/concurrent.futures.html#module-concurrent.futures http://docs.python.jp/3.5/library/multiprocessing.html

Scrapy でページングをクローリングする

前回は Scrapy の簡単な機能、最低限の実装方法をご紹介しました。

sfujimoto.hatenablog.com

今回はページングへの対応方法をご紹介したいと思います。

前回は当ブログのトップページからエントリタイトルを取得し、表示するという Scrapy の実装をご紹介しました。ただし、あくまでトップページのみでトップページの 6件以外は表示されませんでした。今回はトップページからスクレピングした結果が次のページへのリンクがあり続ける限り、次のページのエントリタイトル一覧を取得し続ける実装方法をご紹介します。この実装方法を覚えることで、次のページに限らず、一覧ページから詳細ページへの深掘りなども実装できることとなります。

今回の目的

  • スクレイピング結果の URL からページを取得することで、動的な複数ページへのクローリング方法を学ぶ
  • 次のページや、一覧ページから詳細ページへのクローリングに対応できるようにする

概要

前回の記事で説明した通り、ページへのアクセスや、アクセス結果の引き渡しは scrapy 側で実装されています。start_urls に URL を書いたら、その URL へアクセスされ、アクセスした結果のレスポンスが parse メソッドの引数に渡されて、実行されます。start_urls に全ての URL を記載しても可能ですが、そんなのはサイトに変更がある度に手動で URL を追加、削除する必要があるため、現実的ではありません。

追加のリクエストは簡単です。parse メソッドから scrapy.Request を返すことで、引き続き リクエストを実行することができます。

試してみた

環境

実装

まずは次のページの URL を取得する XPATH を考えます。はてなブログの次のページの URL は以下となりました。

//div[@id="main-inner"]/div/span/a/@href

この XPATH から取得した URL を scrapy.Request で追加するだけです。

# -*- coding: utf-8 -*-
import scrapy


class TechSpider(scrapy.Spider):
    name = "tech"
    allowed_domains = ["sfujimoto.hatenablog.com"]
    start_urls = ['http://sfujimoto.hatenablog.com/']

    def parse(self, response):
        titles = response.xpath('//article/div/header/h1/a/text()')
        [ print(title) for title in titles.extract() ]
        
        for url in response.xpath('//div[@id="main-inner"]/div/span/a/@href').extract():
            yield scrapy.Request(url)

前回の説明から下2行を追加しただけです。print がリスト内包表記に変わっていることは気にしないでください。

指定した XPATH から次のページの URL を取得し、scrapy.Request の引数に URL を渡すことで、URL へアクセスし、その結果を同じく parse メソッドへ渡してくれます。これにより、次のページのリンクがあり続ける限りはページの取得を続けます。

動作確認

# scrapy runspider --nolog first_scrapy/spiders/tech.py
Elasticsearch をインストールして起動する
Scrapy でクローリング、スクレイピングする
Elasticsearch の概要を調べてみた
AWS CloudFormation
AWS S3
AWS ElasticCache
AWS ELB
「Amazon Web Services徹底活用ガイド」を読みました
脱vSphereClient (Apple信者向け)
AWS AMI
AWS RDS
AWS EC2
Webアプリケーション on EC2 and RDS
Fluentd(インストール編)
CentOS7(ネットワーク編)
Techブログをはじます

まとめ

Scrapy は多機能でクローリング、スクレイピングで想定される実装の多くを Scrapy で吸収してくれるため、実装が少なく済んで助かります。

Elasticsearch をシングルノードでインストールする

本日は Elasticsearch をいくつかのパターンで立ち上げる方法をご紹介します。

Elasticsearch は基本的にパッケージが用意されており、コマンド一発でインストール、コマンド一発で起動することができます。

Elasticsearch シングルノード on Linux

まずは Linux OS 上で Elasticsearch を起動します。

環境

事前準備

  • CentOS の起動までは終わっていること

Java インストール

Elasticsearch 5 系は Java 1.8系のみをサポートしています。(2016/1/3現在)Oracle Java、OpenJDK はどちらでも問題ありません。
Java をインストールします。

# yum install -y java-1.8.0
:
=============================================================================================================================================================================================================================================
 Package                                                             Arch                                           Version                                                            Repository                                       Size
=============================================================================================================================================================================================================================================
Installing:
 java-1.8.0-openjdk                                                  x86_64                                         1:1.8.0.111-2.b15.el7_3                                            updates                                         231 k
:
Installed:
  java-1.8.0-openjdk.x86_64 1:1.8.0.111-2.b15.el7_3
:
Complete!

Elasticsearch インストール

Elasticsearch は Yum、Apt といったパッケージリポジトリを提供しています。

今回は CentOS なので Yum のパッケージリポジトリを利用します。パッケージリポジトリの定義ファイルを作成します。

# echo "[elasticsearch-5.x]
name=Elasticsearch repository for 5.x packages
baseurl=https://artifacts.elastic.co/packages/5.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md" > /etc/yum.repos.d/elastic.repo

GPG キーをインポートします。

# rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch

Elasticsearch をインストールします。

# yum install -y elasticsearch
:
=============================================================================================================================================================================================================================================
 Package                                                    Arch                                                Version                                                 Repository                                                      Size
=============================================================================================================================================================================================================================================
Installing:
 elasticsearch                                              noarch                                              5.1.1-1                                                 elasticsearch-5.x                                               32 M
:
Installed:
  elasticsearch.noarch 0:5.1.1-1

Complete!

Elasticsearch を設定する

Elasticsearch のメインの設定は/etc/elasticsearch/elasticsearch.ymlというYAML形式のファイルで設定します。デフォルトでは全てコメントアウトされていて、デフォルト設定が割り当てられています。いくつか重要な設定を記載します。

項目 説明
network.host 接続を待ち受けるネットワークを設定します。デフォルトはローカルホストとなっており、自身からの接続のみを許可します。外部から接続したい場合、外部とのインタフェースのネットワークを指定します。どこからの接続からも許可する場合、```0.0.0.0```を設定します。
network.port 接続を待ち受けるポートを設定します。デフォルトは9200です。
path.data Elasticsearch のデータを保管するディレクトリパスを指定します。CentOS のデフォルトは```/var/lib/elasticsearch```です。
path.logs Elasticsearch のログを保管するディレクトリパスを指定します。CentOS のデフォルトは```/var/log/elasticsearch```です。
cluster.name Elasticsearch のクラスタ名を指定します。クラスタ構成とする場合、クラスタを構成する全てのノードで名前を合わせる必要があります。
node.name Elasticsearch のノード名を指定します。
discovery.zen.ping.unicast.hosts Elasticsearch のクラスタを構成する相手のホスト名、もしくはIPアドレスを指定します。Elasticsearch 1系まではブロードキャストでクラスタの相手を探していましたが、2系からはユニキャストとなりました。

今回は外部からの接続を許可する設定のみを行います。

# vi /etc/elasticsearch/elasticsearch.yml
network.host: 0.0.0.0

Elasticsearch を起動する

Elasticsearch を起動します。パッケージでインストールしている場合、サービスに登録されていますので、サービス起動コマンドで起動します。

# systemctl start elasticsearch

# tail /var/log/elasticsearch/elasticsearch.log
[2017-01-03T02:21:50,199][INFO ][o.e.n.Node               ] [lpOOTTv] starting ...
[2017-01-03T02:21:50,533][INFO ][o.e.t.TransportService   ] [lpOOTTv] publish_address {10.0.2.15:9300}, bound_addresses {[::]:9300}
[2017-01-03T02:21:50,538][INFO ][o.e.b.BootstrapCheck     ] [lpOOTTv] bound or publishing to a non-loopback or non-link-local address, enforcing bootstrap checks
[2017-01-03T02:21:53,786][INFO ][o.e.c.s.ClusterService   ] [lpOOTTv] new_master {lpOOTTv}{lpOOTTv4R2agyojuaC3V3g}{Imr1IzFnRMuGZ00FFOz6HA}{10.0.2.15}{10.0.2.15:9300}, reason: zen-disco-elected-as-master ([0] nodes joined)
[2017-01-03T02:21:53,864][INFO ][o.e.h.HttpServer         ] [lpOOTTv] publish_address {10.0.2.15:9200}, bound_addresses {[::]:9200}
[2017-01-03T02:21:53,864][INFO ][o.e.n.Node               ] [lpOOTTv] started

接続確認

Elasticsearch へ WebAPI を実行して接続します。

# curl localhost:9200
{
  "name" : "lpOOTTv",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "mOYwZU7STJSIMjg1P4lXLw",
  "version" : {
    "number" : "5.1.1",
    "build_hash" : "5395e21",
    "build_date" : "2016-12-06T12:36:15.409Z",
    "build_snapshot" : false,
    "lucene_version" : "6.3.0"
  },
  "tagline" : "You Know, for Search"
}

クラスタ名、ノード名、Elasticsearch、Luceneのバージョンなどが返ってきました。

インストール、起動は簡単ですね。

Elasticsearch コンテナ シングルノード on Docker

2016年11月頃から Elastic 社公式の Docker イメージの提供を開始しました。これにより、起動がより簡単になりました。

環境

  • Docker ホスト : Docker for Mac 1.12
  • Elasticsearch : 5.1.1

事前準備

  • Docker for Mac が起動していること

Elasticsearch コンテナを起動する

# docker run --rm -p 9200:9200 docker.elastic.co/elasticsearch/elasticsearch:5.1.1
Unable to find image 'docker.elastic.co/elasticsearch/elasticsearch:5.1.1' locally
5.1.1: Pulling from elasticsearch/elasticsearch
3690ec4760f9: Pull complete
f52154c3d3fc: Pull complete
4075cc5db14a: Pull complete
93889b68d3f9: Pull complete
33720f77e849: Pull complete
d1ddd1420cac: Pull complete
61d61543041b: Pull complete
4c746b62ddf3: Pull complete
2246369d1afe: Pull complete
81ea6699fce5: Pull complete
:
[2017-01-03T02:33:16,576][INFO ][o.e.n.Node               ] [rDoz9s3] starting ...
[2017-01-03T02:33:16,801][INFO ][o.e.t.TransportService   ] [rDoz9s3] publish_address {172.17.0.2:9300}, bound_addresses {[::]:9300}
[2017-01-03T02:33:16,806][INFO ][o.e.b.BootstrapCheck     ] [rDoz9s3] bound or publishing to a non-loopback or non-link-local address, enforcing bootstrap checks
[2017-01-03T02:33:19,965][INFO ][o.e.c.s.ClusterService   ] [rDoz9s3] new_master {rDoz9s3}{rDoz9s3QQQCl4LOLKun6aA}{ViB-UgFJQDy2drtkE1ry5Q}{172.17.0.2}{172.17.0.2:9300}, reason: zen-disco-elected-as-master ([0] nodes joined)
[2017-01-03T02:33:20,003][INFO ][o.e.h.HttpServer         ] [rDoz9s3] publish_address {172.17.0.2:9200}, bound_addresses {[::]:9200}
[2017-01-03T02:33:20,003][INFO ][o.e.n.Node               ] [rDoz9s3] started

起動しました。

Elasticsearch コンテナは X-Pack がインストール済みのため、認証が必須となります。デフォルトユーザーはelastic、パスワードはchangemeです。

# curl -u elastic:changeme localhost:9200
{
  "name" : "rDoz9s3",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "ZH9Rg2RXRJag_TAEuJ9n_w",
  "version" : {
    "number" : "5.1.1",
    "build_hash" : "5395e21",
    "build_date" : "2016-12-06T12:36:15.409Z",
    "build_snapshot" : false,
    "lucene_version" : "6.3.0"
  },
  "tagline" : "You Know, for Search"
}

Elasticsearch を設定して、コンテナを起動する

このままでは Elasticsearch はデフォルト設定のままでしか起動できません。Elasticsearch を設定した状態で起動したい場合、ローカルで設定ファイルを作成して、Dockerのマウント機能を利用して、コンテナを起動します。

  • 設定ファイル
  • elasticsearch.yml
  • jvm.options
  • log4j2.properties
# vi elasticsearch-conf/elasticsearch.yml
network.host: 0.0.0.0
xpack.security.enabled: false
discovery.zen.minimum_master_nodes: 1

# vi jvm.options
-Xms2g
-Xmx2g
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
-XX:+DisableExplicitGC
-XX:+AlwaysPreTouch
-server
-Xss1m
-Djava.awt.headless=true
-Dfile.encoding=UTF-8
-Djna.nosys=true
-Djdk.io.permissionsUseCanonicalPath=true
-Dio.netty.noUnsafe=true
-Dio.netty.noKeySetOptimization=true
-Dlog4j.shutdownHookEnabled=false
-Dlog4j2.disable.jmx=true
-Dlog4j.skipJansi=true
-XX:+HeapDumpOnOutOfMemoryError

# vi log4j2.properties
status = error
appender.console.type = Console
appender.console.name = console
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
rootLogger.level = info
rootLogger.appenderRef.console.ref = console

# docker run --rm -p 9200:9200 -v /Users/fujimoto.shinji/elasticsearch-conf:/usr/share/elasticsearch/config/ docker.elastic.co/elasticsearch/elasticsearch:5.1.1
:
[2017-01-03T03:04:11,101][INFO ][o.e.n.Node               ] [moCi9bY] starting ...
[2017-01-03T03:04:11,355][INFO ][o.e.t.TransportService   ] [moCi9bY] publish_address {172.17.0.2:9300}, bound_addresses {[::]:9300}
[2017-01-03T03:04:11,366][INFO ][o.e.b.BootstrapCheck     ] [moCi9bY] bound or publishing to a non-loopback or non-link-local address, enforcing bootstrap checks
[2017-01-03T03:04:14,503][INFO ][o.e.c.s.ClusterService   ] [moCi9bY] new_master {moCi9bY}{moCi9bYtQ4K9aWZLwStmMw}{6KQdzI9RQ-Cqyub9A6zpLw}{172.17.0.2}{172.17.0.2:9300}, reason: zen-disco-elected-as-master ([0] nodes joined)
[2017-01-03T03:04:14,535][INFO ][o.e.h.HttpServer         ] [moCi9bY] publish_address {172.17.0.2:9200}, bound_addresses {[::]:9200}
[2017-01-03T03:04:14,537][INFO ][o.e.n.Node               ] [moCi9bY] started

起動しました。

X-Pack を無効化したので、ベーシック認証なしで接続します。

$ curl localhost:9200
{
  "name" : "moCi9bY",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "B0iUdGhNQla06J3TnvRBTA",
  "version" : {
    "number" : "5.1.1",
    "build_hash" : "5395e21",
    "build_date" : "2016-12-06T12:36:15.409Z",
    "build_snapshot" : false,
    "lucene_version" : "6.3.0"
  },
  "tagline" : "You Know, for Search"
}

無事接続できました。

まとめ

Elastic社のプロダクトはインストール、起動が非常に簡単です。on Linux でも on Docker でも簡単に起動することができました。興味はあるけど、大量のドキュメントを読むのが面倒だという人もまずは立ち上げて、触ってみてはいかがでしょうか。

次回は Elasticsearch による CRUD を試してみます。

Scrapy でクローリング、スクレイピングする

寒くなってきましたね。23日から年末年始休暇で12連休なのに何も予定がないので近所のドトールで黙々とブログ書いています。

さて、今回は先日、クローリング、スクレイピングを実装する機会があったので、その時に利用した Scrapy に関して調べた備忘録です。

Scrapy

Scrapy は Python 実装によるクローリング、スクレイピングを簡単に構築するためのフレームワークです。

HTTP リクエストや、スケジューラがフレームワークとして用意されており、利用者はフレームワークのルールに則ることでコード量少なく、クローリング、スクレイピングを実装することができます。利用者はどのページにアクセスするのか、アクセスしたページから何をどのように抽出するかを記述するだけで欲しい結果を取得することができます。

以前、requestsBeautifulSoupで実装したことがありますが、Scrapy 知れば良かった。。

今回は簡単に本ブログから記事タイトルを取得してみましょう。

対象読者

  • クローリング、スクレイピングに興味があるけど、ちゃんと調べたことがない方
  • Scrapy を触ったことがない方
  • Python は触ったことがある方

今回の目的

Scrapy を利用して本ブログから記事タイトルを抽出、出力する

環境

初期設定

Scrapy インストール

今回は pip にて Scrapy をインストールしました。

$ pip install scrapy
:
Successfully installed cryptography-1.7.1 idna-2.2 lxml-3.7.1 scrapy-1.3.0 setuptools-32.3.0 zope.interface-4.3.3

Scrapy 初期設定

Scrapy はサブコマンドで設定、実行することができます。初期設定はscrapy startprojectコマンドを実行すると、対話形式で設定値を入力することで最低限必要なファイル群を生成することができます。

$ scrapy startproject first_scrapy
New Scrapy project 'first_scrapy', using template directory 'XXXXXXXXX/.pyenv/versions/3.5.1/envs/scrapy/lib/python3.5/site-packages/scrapy/templates/project', created in:
    XXXXXXXXXXX/scrapy/first_scrapy

You can start your first spider with:
    cd first_scrapy
    scrapy genspider example example.com

$ cd first_scrapy
$ tree .
.
├── first_scrapy
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-35.pyc
│   │   └── settings.cpython-35.pyc
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       ├── __init__.py
│       └── __pycache__
│            └── __init__.cpython-35.pyc
└── scrapy.cfg

4 directories, 10 files

実装

Spider 実装

Spider はどのURLへアクセスするか、アクセスした結果をどのように扱うか、つまりやりたいことを実装します。最低限 Spider を実装することでクローリング、スクレイピングできます。Spider はscrapy genspiderコマンドによりテンプレートから生成できます。

$ scrapy genspider tech sfujimoto.hatenablog.com
Created spider 'tech' using template 'basic' in module:
  first_scrapy.spiders.tech

テンプレートからどのようなコードができたのか見てみましょう。

$ cat first_scrapy/spiders/tech.py
# -*- coding: utf-8 -*-
import scrapy


class TechSpider(scrapy.Spider):
    name = "tech"
    allowed_domains = ["sfujimoto.hatenablog.com"]
    start_urls = ['http://sfujimoto.hatenablog.com/']

    def parse(self, response):
        pass

allowed_domainsはクローリング先としてアクセスしてもよい FQDN を設定します。誤ったサイトへアクセスが多発したら、サイトへ迷惑をかけることになりますが、ここで指定した FQDN 以外への URL アクセスを防止することができます。クローリングはマナーが大事です。start_urlsは最初にアクセスする URL を指定します。例えば、アクセス先にページングがあり、複数ページから取得する場合、start_urlsは最初のページだけ取得し、次のページの URL を抽出することで次のページの URL にリクエストを送信することも可能です。

Scrapy を実行すると、start_urlsへHTTPリクエストした結果がresponseに渡され、parseメソッドが処理されます。なので、今回の例であれば、parseメソッドにタイトルを抽出し、出力する処理を書くだけです。

XPATH

parse メソッドを実装する前に HTML をスクレイピングする XPATH について説明します。Scrapy はいくつかのスクレイピング方法があるのですが、今回は XPATH を利用しました。XPATHXML からパスを指定することでタグや、属性、値を抽出する記法です。

例えば、以下のような XML から色々と抽出するパスの書き方を説明します。

<A>
  <B id="1" class="test">
    <C>TEXT</C>
  </B>
  <B id="2">
    <D>MESSAGE</D>
  </B>
</A>
絶対パスでTEXTを抽出する
/A/B[1]/C/text()
短縮系でTEXTを抽出する
//C/text()

※ 全てのCタグを抽出するので、Cタグが複数ある場合は同じ結果にはなりません。

属性でフィルタして、TEXTを抽出する
//B[@id="1"]/C/text()
属性でアンド条件でフィルタして、TEXTを抽出する
//B[@id="1" and @class="test"]/C/text()

簡単な例ですが、様々な抽出方法があります。

タイトルのルール(XPATH)を見つける

抽出したい文字や、URL の XPATH のパスは HTML を読み解いて、パスを探すしかありません。。私はWebブラウザに Firefox を利用していて、Firebug、FirePath を利用することで自動で XPATH を生成することができました。

ちなみに今回利用する XPATH は以下となります。

//article/div/header/h1/a/text()

実装

それでは XPATH がわかったところで、抽出したタイトルを出力する実装をしましょう。

# -*- coding: utf-8 -*-
import scrapy


class TechSpider(scrapy.Spider):
    name = "tech"
    allowed_domains = ["sfujimoto.hatenablog.com"]
    start_urls = ['http://sfujimoto.hatenablog.com/']

    def parse(self, response):
        titles = response.xpath('//article/div/header/h1/a/text()')
        for title in titles:
            print(title)

下3行を追加しただけです。簡単!

xpath の結果は extract() で展開することができます。それを print で標準出力しているだけです。

動作確認

実装が終わったら、Scrapy を実行して動作確認します。scrapy runspiderで Spider を実行することができます。

$ scrapy runspider --nolog first_scrapy/spiders/tech.py
Elasticsearch の概要を調べてみた
AWS CloudFormation
AWS S3
AWS ElasticCache
AWS ELB
「Amazon Web Services徹底活用ガイド」を読みました
脱vSphereClient (Apple信者向け)

記事タイトルが出力されました!

まとめ

いかがでしたでしょうか。たった 3行の実装で簡単にクローリング、スクレイピングを実装することができました。今回は非常に簡単な例ですが、Scrapy は複雑な要件にも対応しており多機能です。次回はもっと深掘りしたいと思います。