end0tknr's kipple - 新web写経開発

http://d.hatena.ne.jp/end0tknr/ から移転しました

データの永続化(serialize)において pickle for python ≒ Storable for perl

そんな気がします

#!/usr/local/bin/python
# -*- coding: utf-8 -*-
import pickle


def main():
    test_vals = {"hoge":"hoge_val","foo":2}
    print test_vals

    # バイナリ書込みmodeでopen
    # 他modeには以下があり.
    # r:読み, w:書き, a:追記, +:読書き両方   t:text(default),b:binary
    # またwith構文により、close()不要にしています
    with open('sample.pickle', mode='wb') as f:
        pickle.dump(test_vals, f, protocol=2) #fileへの書込み(保存)

    restored_vals = {}
    with open('sample.pickle', mode='rb') as f:
        restored_vals = pickle.load(f)        #fileからの読込み

    print restored_vals


if __name__ == '__main__':
    main()

↑こう書くと↓こう表示されます

$ python test_pickle.py 
{'foo': 2, 'hoge': 'hoge_val'}
{'foo': 2, 'hoge': 'hoge_val'}

pythonでのfile open/closeやwithの勉強にもなりました

PIL / Pillow , numpy による画像処理入門 その3 - 平板化(1次元化)と主成分分析

www.oreilly.co.jp

更に↑こちらで提供されているサンプルコードのまんま

実行はできましたが、算出結果の正しさは確認していません

#!/usr/bin/python
# -*- coding: utf-8 -*-

from PIL import Image
import numpy
import os


def main():
    # 指定dir以下のjpg file名一覧を取得
    imlist = get_imlist('a_thumbs')

    im = numpy.array(Image.open(imlist[0]))
    m,n = im.shape[0:2] # jpg size

    imnbr = len(imlist) # files size


    # 平板化(1次元化)し行列へ格納
    immatrix = numpy.array([numpy.array(Image.open(im)).flatten()
                            for im in imlist],'f')

    # 主成分分析 - 返り値:写像行列(次元の重要度順), 分散, 平均
    V,S,immean = my_pca(immatrix)
    print V


def get_imlist(path):
  """ pathに指定されたフォルダのすべてのjpgファイル名のリストを返す """
  return [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.jpg')]


def my_pca(X):
  """ 主成分分析
   入力:X, 訓練データを平板化した配列を行として格納した行列
   出力:写像行列(次元の重要度順), 分散, 平均 """

  # 次元数を取得
  num_data,dim = X.shape

  # データをセンタリング
  mean_X = X.mean(axis=0)
  X = X - mean_X

  if dim>num_data: 
    # PCA - 高次元のときはコンパクトな裏技を用いる
    M = dot(X,X.T) # 共分散行列
    e,EV = numpy.linalg.eigh(M) # 固有値と固有ベクトル
    tmp = dot(X.T,EV).T # ここがコンパクトな裏技
    V = tmp[::-1] # 末尾の固有ベクトルほど重要なので、反転する
    S = sqrt(e)[::-1] # 固有値の並びも反転する
    for i in range(V.shape[1]):
      V[:,i] /= S
  else:
    # PCA - 低次元なら特異値分解を用いる
    U,S,V = numpy.linalg.svd(X) 
    V = V[:num_data] # 最初のnum_dataの分だけが有用

  # 写像行列と、分散、平均を返す
  return V,S,mean_X


if __name__ == '__main__':
    main()

pylab for python による画像処理入門 その2

www.oreilly.co.jp

またも↑こちらで提供されているサンプルコードのまんま

画像に対し、点や線、文字列の描画

#!/usr/bin/python
# -*- coding: utf-8 -*-

# from PIL import Image

# import numpy
# #from pylab import *
import matplotlib
matplotlib.use('Agg')
import matplotlib.pylab as plb


# # 配列に画像読込み
# im = numpy.array(Image.open('empire.jpg'))

# # 画像表示
# # が、私のcent7環境には x11がない為、comment out
# # imshow(im)

# 点の座標
x = [100,100,400,400]
y = [200,500,200,500]

plb.plot(x,y,'r*')    # 赤い星マークを4点に描画
plb.plot(x[:2],y[:2]) #最初の2点間に線を描画

plb.title('test pylab')  # タイトル追加
plb.axis('off')
# show()

# savefig()は、画像フォーマットを拡張子で判断しますが
# 引数で指定することも可能
# https://matplotlib.org/devdocs/api/_as_gen/matplotlib.pyplot.savefig.html

plb.savefig( '1_2_1.png' )

「import pylab」の代わりに「import matplotlib , matplotlib.pylab」

import pylab as plb

ヘッダ部を↑このように記載すると、 画面のない私のcentos on virtual boxでは実行時に

self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
_tkinter.TclError: no display name and no $DISPLAY environment variable

のようなエラーとなる為、次のようにimportしています。

import matplotlib
matplotlib.use('Agg')
import matplotlib.pylab as plb

等高線図とヒストグラム

#!/usr/bin/python
# -*- coding: utf-8 -*-

from PIL import Image
import numpy
import matplotlib
matplotlib.use('Agg')
import matplotlib.pylab as plb

# 画像を配列に読み込む
im = numpy.array(Image.open('empire.jpg').convert('L'))

plb.figure()                    # 新規図
plb.gray()                      # 色を使わない
plb.contour(im, origin='image') # 左上が原点の等高線
plb.axis('equal')               # X-Y軸の増分を同じに
plb.axis('off')                 # 座標軸を非表示
plb.savefig( '1_2_2_1.png' )

plb.figure()                    # 新規図
plb.hist(im.flatten(),128)      # ヒストグラム
plb.savefig( '1_2_2_2.png' )

画像のサイズと、型の抽出

#!/usr/bin/python
# -*- coding: utf-8 -*-

import numpy
from PIL import Image

im = numpy.array(Image.open('empire.jpg'))
print im.shape, im.dtype        #カラー画像のsizeと型

im = numpy.array(Image.open('empire.jpg').convert('L'),'f')
print im.shape, im.dtype        #白黒画像のsizeと型

画像のグレースケール化と、明度変換

#!/usr/bin/python
# -*- coding: utf-8 -*-

from PIL import Image
import numpy
import matplotlib
matplotlib.use('Agg')
import matplotlib.pylab as plb


im = numpy.array(Image.open('empire.jpg').convert('L'))
im2 = 255 - im                  # 画像反転
im3 = (100.0/255) * im + 100    # 100〜200の範囲に縮小?
im4 = 255.0 * (im/255.0)**2     # 2乗

#明度の最大値/最小値 表示
for i in (im,im2,im3,im4):
  print "(MIN MAX)=(", str( int(i.min()) ), str( int(i.max())) ,")"

plb.figure()
plb.gray()
plb.axis('off')
plb.imshow(im)          #画像の登録?
plb.savefig( '1_3_2-1.png' )

plb.figure()
plb.gray()
plb.axis('off')
plb.imshow(im2)         #画像の登録?
plb.savefig( '1_3_2-2.png' )

plb.figure()
plb.gray()
plb.axis('off')
plb.imshow(im3)         #画像の登録?
plb.savefig( '1_3_2-3.png' )

plb.figure()
plb.gray()
plb.axis('off')
plb.imshow(im4)         #画像の登録?
plb.savefig( '1_3_2-4.png' )

PIL / Pillow for python による画像処理入門

www.oreilly.co.jp

と言っても、↑こちらで提供されているサンプルコードのまんま

#!/usr/bin/python
# -*- coding: utf-8 -*-

from PIL import Image

# グレースケール変換
pil_im = Image.open('empire.jpg').convert('L')
# 拡張子に応じた形式で保存
pil_im.save( 'empire_1.jpg' )

# 私のcent7環境にx11はない為、out.show() を行うと、次のようなerror
#   display: unable to open X server
#   `' @ error/display.c/DisplayImageCommand/429.
# out.show()

# サムネイル画像作成
pil_im.thumbnail((128,128))
pil_im.save( 'empire_2.jpg' )

pil_im = Image.open('empire.jpg').convert('L')
# 画像の一部を切取り→反転→貼付け
box = (100,100,400,400)
region = pil_im.crop(box)
region = region.transpose(Image.ROTATE_180)
pil_im.paste(region,box)
pil_im.save( 'empire_3.jpg' )

pil_im = Image.open('empire.jpg').convert('L')
# 画像のサイズ変更
out = pil_im.resize((128,128))
out.save( 'empire_4.jpg' )

pil_im = Image.open('empire.jpg').convert('L')
# 画像全体の回転
out = pil_im.rotate(45)
out.save( 'empire_5.jpg' )

out.show() は使用していません

画面のない私のcentos on virtual boxで、out.show() を使用すると

display: unable to open X server `' @ error/display.c/DisplayImageCommand/429.

のようなエラーとなる為、これをコメントアウトし、out.save()を使用しています。

固有値、固有ベクトルからの主成分分析 オレオレ入門 (PCA: Principal Component Analysis)

主成分分析は、算出された固有値固有ベクトルを優先度付けすることで、 多次元の行列を次元削減する為のものですが、口では上手く説明できなかったのでメモ。

固有値固有ベクトル の算出方法?

end0tknr.hateblo.jp

以前のエントリに記載している通りです。

線形変換における固有ベクトルの特徴

行列は座標の変換に用いられますが、 座標変換を行った後も固有ベクトルは、その向きが変わることはありません。

上記urlにある線形変換の図(GIFアニメーション)が分かりやすいと思います。

deeplearning4j.org

(振動においては「固有振動数」という似た用語?もありますが)

行列は、次数に応じた固有値固有ベクトルを持つ

以前の私のエントリでは、2次行列に対する固有値固有ベクトルの 算出のみ記載していますが、3次行列では3種、4次行列では4種のように、 行列は、次数に応じた固有値固有ベクトルを持ちます。

f:id:end0tknr:20170919224645p:plain

主成分分析は、固有ベクトル固有値順にソートしたもの

例えば、5次行列の固有ベクトルを算出し、 これを固有値順に表示すると次のようになると思います。

f:id:end0tknr:20170919224655p:plain

結局?、主成分分析では、固有値の大きさ(寄与度)を参照しながら、 採用する固有ベクトルを決定することで、次元削減を行います。

Χ(カイ)2乗値と、カイ2乗検定

Χ(カイ)2乗値の定義

 \Large
  \chi^{2} = \frac{(x_1 - m_1)^2}{m_1} + \frac{(x_2 - m_2)^2}{m_2} +
  \cdots + \frac{(x_k - m_k)^2}{m_k}

ただし、x:実測値(観測値)、m:期待値

Χ(カイ)2乗分布表と、グラフ

上側有意確立(p)と自由度(k)に対応するΧ(カイ)2乗値一覧

自由度 p=0.90 p=0.50 p=0.10 p=0.05 p=0.01
1 0.016 0.455 2.710 3.840 6.630
2 0.211 1.386 4.610 5.990 9.210
3 0.584 2.370 6.250 7.810 11.300
4 1.060 3.360 7.780 9.490 13.300
5 1.610 4.350 9.240 11.070 15.100

上表にない自由度や上側有意確立に対応するカイ2乗値は、 インターネット検索等でご確認下さい。

Χ(カイ)2乗分布グラフもインターネット検索等でご確認下さい。

サイコロに対するカイ2乗検定

サイコロを120回振った結果、それぞれの目が10~27回出た場合を 例に2乗値を算出すると、12.4 となります

サイコロの目 x(実測値) m(期待値) x-m (x-m)2/m
1 25 20 5 1.25
2 27 20 7 2.45
3 20 20 0 0
4 10 20 -10 5
5 13 20 -7 2.45
6 25 20 5 1.25
120 120 12.4

ここで、サイコロの自由度が6-1=5 であることより、 Χ(カイ)2乗分布表のp=0.05と比較すると、12.4 > 11.070 である為、 このサイコロは均一でないと言える

/usr/bin/make -j

のように「-j」オプションで、make が早くなるだなんて、知りませんでした。

$ make --help
Usage: make [options] [target] ...
Options:
  -b, -m                      Ignored for compatibility.
  -B, --always-make           Unconditionally make all targets.
  -C DIRECTORY, --directory=DIRECTORY
                              Change to DIRECTORY before doing anything.
  -d                          Print lots of debugging information.
  --debug[=FLAGS]             Print various types of debugging information.
  -e, --environment-overrides
                              Environment variables override makefiles.
  --eval=STRING               Evaluate STRING as a makefile statement.
  -f FILE, --file=FILE, --makefile=FILE
                              Read FILE as a makefile.
  -h, --help                  Print this message and exit.
  -i, --ignore-errors         Ignore errors from recipes.
  -I DIRECTORY, --include-dir=DIRECTORY
                              Search DIRECTORY for included makefiles.
  -j [N], --jobs[=N]          Allow N jobs at once; infinite jobs with no arg.
  -k, --keep-going            Keep going when some targets can't be made.
  -l [N], --load-average[=N], --max-load[=N]
                              Don't start multiple jobs unless load is below N.
  -L, --check-symlink-times   Use the latest mtime between symlinks and target.
  -n, --just-print, --dry-run, --recon
                              Don't actually run any recipe; just print them.
  -o FILE, --old-file=FILE, --assume-old=FILE
                              Consider FILE to be very old and don't remake it.
  -p, --print-data-base       Print make's internal database.
  -q, --question              Run no recipe; exit status says if up to date.
  -r, --no-builtin-rules      Disable the built-in implicit rules.
  -R, --no-builtin-variables  Disable the built-in variable settings.
  -s, --silent, --quiet       Don't echo recipes.
  -S, --no-keep-going, --stop
                              Turns off -k.
  -t, --touch                 Touch targets instead of remaking them.
  -v, --version               Print the version number of make and exit.
  -w, --print-directory       Print the current directory.
  --no-print-directory        Turn off -w, even if it was turned on implicitly.
  -W FILE, --what-if=FILE, --new-file=FILE, --assume-new=FILE
                              Consider FILE to be infinitely new.
  --warn-undefined-variables  Warn when an undefined variable is referenced.
  --warn-undefined-functions  Warn when an undefined user function is called.

This program built for x86_64-redhat-linux-gnu
Report bugs to <bug-make@gnu.org>

国土交通省にあるGISデータをPostGISへインポート(2017年版)

先程のエントリの続きです。 postgisの動作確認の為、gisデータをimportし、画像ファイルとして表示します。

mac osx 10.6.3にpostgis, mapserverをinstall - end0tknr's kipple - 新web写経開発

国土交通省にあるGISデータをPostGISへインポート(改訂版) - end0tknr's kipple - 新web写経開発

GISデータのダウンロード & 解凍

http://nlftp.mlit.go.jp/ksj/index.html

上記urlより、行政区域面 (東京 N03-170101_13_GML.zip)をダウンロード。

$ unzip N03-170101_13_GML.zip
$ ls -l
-rw-rw-r-- 1    16232 Mar  4  2017 KS-META-N03-17_13_170101.xml
-rw-r--r-- 1  7497800 Sep  9  2017 N03-170101_13_GML.zip
-rw-rw-r-- 1   472154 Feb 17  2017 N03-17_13_170101.dbf
-rw-rw-r-- 1      145 Feb 17  2017 N03-17_13_170101.prj
-rw-rw-r-- 1  7486656 Feb 17  2017 N03-17_13_170101.shp
-rw-rw-r-- 1    49780 Feb 17  2017 N03-17_13_170101.shx
-rw-rw-r-- 1 18624682 Feb 17  2017 N03-17_13_170101.xml

前回行った際は、 shape形式への変換する必要がありましたが、 既に変換済のデータも提供されるようになったようです。

国土交通省にあるGISデータをPostGISへインポート(改訂版) - end0tknr's kipple - 新web写経開発

shapeファイルから、create table文 , insert文作成、更にimport

$ /usr/local/pgsql/bin/shp2pgsql -p \
  ~/tmp/gis/N03-17_13_170101 gyosei_kuiki > ~/tmp/gis/create_gyosei_kuiki.sql
$ /usr/local/pgsql/bin/shp2pgsql -W cp932 -a \
  ~/tmp/gis/N03-17_13_170101 gyosei_kuiki > ~/tmp/gis/insert_gyosei_kuiki.sql

$ /usr/local/pgsql/bin/psql -U postgres gis_test < ~/tmp/gis/create_gyosei_kuiki.sql
$ /usr/local/pgsql/bin/psql -U postgres gis_test < ~/tmp/gis/insert_gyosei_kuiki.sql

mapファイルの作成と、画像への出力

$ vi gyosei_kuiki.map

MAP
    SIZE 800 800           #画像size
    EXTENT 138.8 35.5 140 36   #出力範囲の座標
    STATUS ON              #地図を表示するか
    UNITS DD               #地図の単位(DD は緯度経度)
    IMAGECOLOR 255 255 255 #背景色R G B
    IMAGETYPE PNG          #地図画像を保存する形式
    LAYER
         NAME "gyosei"
         CONNECTIONTYPE POSTGIS
         CONNECTION "user=postgres password='' dbname=gis_test host=localhost"
         DATA "geom FROM gyosei_kuiki" #select文
         TYPE LINE
         STATUS ON
         CLASS
             COLOR 0 0 0
         END
    END
END

$ /usr/local/bin/shp2img -m gyosei_kuiki.map -o gyosei_kuiki.png

上記のmapファイル作成し、shp2img を実行すると、次のように出力されます。 f:id:end0tknr:20170909131918p:plain

install postgres 9.6 & postgis 2.3 & mapserver & mapserver 7.0

5~6年ぶりに postgres と postgis をinstall.

mapserverがcmakeに対応した点以外では、 しばらく使ってなかった割に変化点は多くありませんでした。

PostgreSQL: The world's most advanced open source database

PostGIS — Spatial and Geographic Objects for PostgreSQL

Welcome to MapServer — MapServer 7.0.6 documentation

mac osx 10.6.3にpostgis, mapserverをinstall - end0tknr's kipple - 新web写経開発

国土交通省にあるGISデータをPostGISへインポート(改訂版) - end0tknr's kipple - 新web写経開発

install postgres

$ wget https://ftp.postgresql.org/pub/source/v9.6.5/postgresql-9.6.5.tar.gz
$ tar -xvf postgresql-9.6.5.tar.gz
$ cd postgresql-9.6.5
$ less INSTALL
$ ./configure
$ make
$ make check
$ su
# make install
# adduser postgres
# mkdir /usr/local/pgsql/data
# chown postgres /usr/local/pgsql/data
# su - postgres
$ /usr/local/pgsql/bin/initdb -D /usr/local/pgsql/data
$ /usr/local/pgsql/bin/postgres -D /usr/local/pgsql/data >logfile 2>&1 &
$ /usr/local/pgsql/bin/createdb test
$ /usr/local/pgsql/bin/psql test

# cd /etc/rc.d/init.d/
# cp /tmp/postgresql-9.6.5/contrib/start-scripts/linux ./postgres
# vi postgres
# chmod 755 postgres
# /sbin/chkconfig --add postgres
# /sbin/chkconfig --list postgres

install postgis

http://postgis.net/source/

Prerequisites

## GEOS
$ wget http://download.osgeo.org/geos/geos-3.6.2.tar.bz2

## Proj.4
wget http://download.osgeo.org/proj/proj-4.9.3.tar.gz

## GDAL
wget http://download.osgeo.org/gdal/2.2.1/gdal-2.2.1.tar.gz

## JSON-C
yum install json-c-devel

postgis

テスト用に gis_test という名称でcreatedb しています。

$ wget http://download.osgeo.org/postgis/source/postgis-2.3.3.tar.gz
$ tar -xvf postgis-2.3.3.tar.gz
$ cd postgis-2.3.3
$ ./configure \
    --with-pgsql=/usr/local/pgsql/bin/pg_config \
    --with-pgconfig=/usr/local/pgsql/bin/pg_config

$ /usr/local/pgsql/bin/createdb -U postgres -E utf8 gis_test
$ /usr/local/pgsql/bin/createlang -U postgres plpgsql gis_test
$ /usr/local/pgsql/bin/psql -U postgres -d gis_test \
    -f /usr/local/pgsql/share/contrib/postgis-2.3/postgis.sql
$ /usr/local/pgsql/bin/psql -U postgres -d gis_test \
    -f /usr/local/pgsql/share/contrib/postgis-2.3/spatial_ref_sys.sql

mapserver

http://mapserver.org/installation/unix.html

Prerequisites

■libpng
$ wget ftp://ftp-osl.osuosl.org/pub/libpng/src/libpng16/libpng-1.6.32.tar.gz

■freetype
$ wget http://download.savannah.gnu.org/releases/freetype/freetype-2.8.tar.gz

■curl
$ wget https://curl.haxx.se/download/curl-7.55.1.tar.gz

■gdal
$ wget http://download.osgeo.org/gdal/2.2.1/gdal-2.2.1.tar.gz
vi include/fcgio.h
#include <stdio.h>   #なにやらerorになったので行追加

■fastcgi
$ wget http://www.fastcgi.com/dist/fcgi-2.4.0.tar.gz

■cairo
$ yum install cairo-devel

■harfbuzz
$ yum install harfbuzz-devel

■FriBidi
$ http://fribidi.org/download/fribidi-0.19.7.tar.bz2

■glib*
# yum install glib*
# yum update kernel
# yum update

mapserver

$ wget http://download.osgeo.org/mapserver/mapserver-7.0.6.tar.gz
$ tar xvf mapserver-7.0.6.tar.gz
$ cd mapserver-7.0.6
$ mkdir build
$ cd build
The following options are enabled by default
(version 7.0: WITH_PROJ, WITH_WMS, WITH_FRIBIDI, WITH_HARFBUFF,
              WITH_ICONV, WITH_CAIRO, WITH_FCGI, WITH_GEOS,
          WITH_POSTGIS, WITH_GDAL, WITH_OGR, WITH_WFS,
          WITH_WCS, WITH_LIBXML2, WITH_GIF.)

mapserverのdocumentに上記のようなcmakeのコメントがあったので、 以降は次のように実施。

$ cmake -DCMAKE_PREFIX_PATH=/usr/local/pgsql \
      -DFRIBIDI_INCLUDE_DIR="/usr/include/glib-2.0;/usr/lib64/glib-2.0/include;/usr/local/include/fribidi"  ..
$ make
$ su
# make install

mapservやshp2imgがinstallされたことが分かります

$ ls -l /usr/local/bin
total 176100
-rwxr-xr-x  1 root root    13480 Sep  9 05:41 shptreevis
-rwxr-xr-x  1 root root    18648 Sep  9 05:41 sortshp
-rwxr-xr-x  1 root root    18256 Sep  9 05:41 tile4ms
-rwxr-xr-x  1 root root    18448 Sep  9 05:41 shp2img
-rwxr-xr-x  1 root root    13592 Sep  9 05:41 shptree
-rwxr-xr-x  1 root root    13448 Sep  9 05:41 shptreetst
-rwxr-xr-x  1 root root    14312 Sep  9 05:41 mapserv
-rwxr-xr-x  1 root root    13320 Sep  9 05:41 msencrypt
-rwxr-xr-x  1 root root    13208 Sep  9 05:41 scalebar
-rwxr-xr-x  1 root root    13200 Sep  9 05:41 legend

headless browser(画面なしブラウザ)のphantomjsによる spider & scraping

これまでの「wget –mirror」では、うまくとれないページがあまりに増えてきた為。

PhantomJS | PhantomJS

API | PhantomJS

install

unzip するだけ、ただ、font installは必要

$ su -
# sudo yum -y install gcc gcc-c++ make flex bison gperf ruby \
  openssl-devel freetype-devel fontconfig-devel libicu-devel sqlite-devel \
  libpng-devel libjpeg-devel
$ cd ~/local  
$ wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2
$ tar -xvf phantomjs-2.1.1-linux-x86_64.tar.bz2
$ ln -s phantomjs-2.1.1-linux-x86_64.tar.bz2 phantomjs

### https://www.google.com/get/noto/
$ wget https://noto-website.storage.googleapis.com/pkgs/NotoSansCJKjp-hinted.zip
$ su -
# mkdir /usr/share/fonts/noto
# cd /usr/share/fonts/noto
# cp NotoSansCJKjp-hinted.zip
# unzip NotoSansCJKjp-hinted.zip
# chmod 644 *.otf

# fc-cache -fv

sample script

ポイントは、src内にcommentしてます

var system = require('system');
// request urlは引数でも指定ok
var url = system.args[1] || 'http://www.example.com/kodate/chuko/tokyo/list/';
console.log("request url: " + url);

var page = require('webpage').create();
page.settings.userAgent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36';
//for basic auth
page.customHeaders =
    {"User-Agent" : page.settings.userAgent,
     "Upgrade-Insecure-Requests":1,
     "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
     "Accept-Language":"ja,en-US;q=0.8,en;q=0.6",
     "Cache-Control":"max-age=0"
    };

page.viewportSize = { width: 1024, height: 768 };

// for (var atri_key in system.os) {
//     console.log(atri_key +' = '+system.os[atri_key]);
// }


page.open(url, function(status){
    if(status != "success") {
        phantom.exit();
    }
  
    console.log("Status: " + status);

    //page load後に、javascriptが動作してることもある為、sleep
    setTimeout(function(){

        //外部jsのload後の処理にjsonで引数指定ok
        var eval_params = {hoge:"hoge_val"};
        var eval_params_str = JSON.stringify(eval_params);
        var ret = evaluate_list_page(page, eval_params_str);

        console.log(ret['title']);
        console.log(ret['hoge']);
        console.log(ret['last_page']);

        // 一部分の画面キャプチャもok
        page.render('page_all.png');
        page.clipRect = {
            top:    ret['clip_rect']['top'],
            left:   ret['clip_rect']['left'],
            width:  ret['clip_rect']['width'],
            height: ret['clip_rect']['height']
        };        
        page.render('page_part.png');


        for (var i in ret['house_urls']) {
            console.log( ret['house_urls'][i] );
        }

        //htmlの保存
        var fs = require('fs');
        fs.write('page_all.html', page.content, 'w');
        
        phantom.exit();
    }, 5000);
});


//最近のsiteは、jqueryを含んでいることが多い為、実施していませんが
//phantomjsのpage.includeJs() で、外部のjquery等のloadもできます
function evaluate_list_page(page, params_json){
    var ret = page.evaluate(function(params_json) {
        var eval_params = JSON.parse(params_json);
        var ret = {};
    ret['title'] = $('title').text();
    ret['hoge'] = eval_params['hoge'];

        //画面上部にあるpagerから最終pageを取得
    ret['last_page'] = $('.mod-listPaging li.lastPage').eq(1).text();

    ret['house_urls'] = [];

        $('.mod-mergeBuilding--sale .moduleHead h3')
            .each(function(index, element){
                ret['house_urls'].push( $('a',element).attr('href'));
        });

        var clip_rect = $('.mod-mergeBuilding--sale').eq(1);
    ret['clip_rect'] =
            {top:    clip_rect.offset().top,
             left:   clip_rect.offset().left,
             width:  clip_rect.width(),
             height: clip_rect.height()   
            };

        return ret;
    }, params_json);

    return ret;
}

↑こう書いて↓こう実行

$ ~/local/phantomjs/bin/phantomjs --debug=true ./foo2js

␣ 空白記号 → Unicode:U+2423 文字参照:&#x2423; or &#9251;

知りませんでした。「何? この記号?」と思いました。 open boxとも言うらしい。

https://ja.wikipedia.org/wiki/%E7%A9%BA%E7%99%BD%E8%A8%98%E5%8F%B7

xlrd for python で excel (xlsx) を読む

またも、以前のperlでのentryのpython

Spreadsheet::ParseExcelでlinuxのperlからexcelファイルを読む - end0tknr's kipple - 新web写経開発

Mike Blackwell / Spreadsheet-XLSX - search.cpan.org

Douglas Wilson / Spreadsheet-ParseExcel - search.cpan.org

python用 xlsx module は、openpyxl と xlrd の2種類

www.python-excel.org には、 xlrd は xls用と記載されていますが、xlrdも xlsxを読めるようです。

http://www.python-excel.org/

openpyxl - A Python library to read/write Excel 2010 xlsx/xlsm files — openpyxl 2.4.8 documentation

xlrd documentation — xlrd 1.1.0 documentation

xlrd/openpyxl for python と Spreadsheet::XLSX/Spreadsheet::ParseExcel for perl の相違点

今回、気づいた範囲では、 数式のあるcellの値を参照した際の返り値が異なっていました。

lang module method 値or数式
perl Spreadsheet::XLSX $cell->value()
perl Spreadsheet::ParseExcel $cell->value()
python xlrd cell.value
python openpyxl cell.value 数式

※2017/9/3時点のSpreadsheet::ParseExcelのdocumentを読むと TODOに「Add Formula support」と記載されていました。

xlrd for python の sample script

#!/usr/local/bin/python
# -*- coding: utf-8 -*-
import getopt
import sys
import xlrd

def main():
    xlsx_path = sys.argv[1]
    wbook = xlrd.open_workbook(xlsx_path)
    
    for sheet_name in wbook.sheet_names():
        print '## SHEET_NAME=' + sheet_name
        wsheet = wbook.sheet_by_name(sheet_name)

        for row in range(wsheet.nrows):
            for col in range(wsheet.ncols):
                cell = wsheet.cell(row,col)
                val = cell.value

                if val == None:
                    continue

                print "(%d,%d)=%s" % (row,col,val)

if __name__ == '__main__':
    main()

↑こう書くと↓こう表示されます

## SHEET_NAME=Sheet1
(0,0)=1.0
(0,1)=2.0
(0,2)=3.0
(1,0)=4.0
(1,1)=5.0
(1,2)=6.0
(2,0)=5.0
(2,1)=7.0
(2,2)=9.0

openpyxl for python の sample script

数式がそのまま表示されることが分かります

#!/usr/local/bin/python
# -*- coding: utf-8 -*-
from openpyxl import load_workbook
import getopt
import sys


def main():
    xlsx_path = sys.argv[1]
    wbook = load_workbook(xlsx_path)
    
    for sheet_name in wbook.get_sheet_names():
        print '## SHEET_NAME=' + sheet_name
        wsheet = wbook.get_sheet_by_name(sheet_name)

        row = wsheet.min_row
        row_max = wsheet.max_row
        col_min = wsheet.min_column
        col_max = wsheet.max_column

        while row <= row_max:
            col = col_min
            while col <= col_max:
                cell = wsheet.cell(row=row,column=col)
                
                val = cell.value
                if val == None:
                    col += 1
                    continue
                val_2 = cell.internal_value
                print "(%d,%d)=%s,%s" % (row,col,val,val_2)
                col += 1
                
            row += 1

if __name__ == '__main__':
    main()

↑こう書くと↓こう表示されます

## SHEET_NAME=Sheet1
(1,1)=1,1
(1,2)=2,2
(1,3)=3,3
(2,1)=4,4
(2,2)=5,5
(2,3)=6,6
(3,1)==SUM(A1:A2),=SUM(A1:A2)
(3,2)==SUM(B1:B2),=SUM(B1:B2)
(3,3)==SUM(C1:C2),=SUM(C1:C2)

Spreadsheet::XLSX for perl の sample script

#!/usr/local/bin/perl
use strict;
use warnings;
use Encode;
use Spreadsheet::XLSX;
use Data::Dumper;

main(@ARGV);

sub main {
    my ($xlsx_path) = @_;

    my $wbook = Spreadsheet::XLSX->new($xlsx_path);
    for my $wsheet (@{$wbook -> {Worksheet}}) {
        print "## SHEET_NAME= $wsheet->{Name}\n";
        
        my $row =       $wsheet->{MinRow};
        my $row_max =   $wsheet->{MaxRow};
        my $col_min =   $wsheet->{MinCol};
        my $col_max =   $wsheet->{MaxCol};
        print "RANGE=$row,$row_max,$col_min,$col_max\n";

        while($row<=$row_max) {
            my $col = $col_min;
            while($col<=$col_max) {
                my $cell = $wsheet->{Cells}[$row][$col];
                if(not $cell){
                    $col += 1;
                    next;
                }

                my $val = $cell->value();
                my $val_2 = $cell->unformatted();
                print "($row,$col)=$val , $val_2\n";
                $col += 1;
            }
            $row += 1;
        }

    }
}

↑こう書くと↓こう表示されます

## SHEET_NAME= Sheet1
RANGE=0,2,0,2
(0,0)=1 , 1
(0,1)=2 , 2
(0,2)=3 , 3
(1,0)=4 , 4
(1,1)=5 , 5
(1,2)=6 , 6
(2,0)=5 , 5
(2,1)=7 , 7
(2,2)=9 , 9

python urllib による http getと yahoo ジオコーダapiで、住所→座標(緯度経度)変換

pythonにおけるhttpやjsonの練習の為、以前、perlで書いたentryをpython

geocoding (住所→座標(緯度経度))変換は、google map apiより Yahoo!ジオコーダAPIがよさそ - end0tknr's kipple - 新web写経開発

YOLP(地図):Yahoo!ジオコーダAPI - Yahoo!デベロッパーネットワーク

#!/usr/local/bin/python
# -*- coding: utf-8 -*-
import json
import socket
import ssl
import traceback
import urllib
import urllib2

def main():
    # オレオレ証明書のurlにアクセスする場合
    # SSL: CERTIFICATE_VERIFY_FAILED エラーになる為
    ssl._create_default_https_context = ssl._create_unverified_context

    socket.setdefaulttimeout(5);  # sec

    u_agent = urllib2.build_opener()
    u_agent.addheaders = [('User-Agent','python-urllib2-test')];

    api_end_point = 'https://map.yahooapis.jp/geocode/V1/geoCoder'
    params = {
        'appid' : 'ないしょ',
        'output' :'json',
        'ei':'UTF-8',
        "al":4,  #level -> 1=都道府県, 2=市区町村, 3=町/大字, 4=丁目/字
        "recursive": 'true', #見つからない場合、階層上位を探す
        "query": '東京都渋谷区渋谷1-11'
    }

    encoded_params = urllib.urlencode(params)
    req_url = api_end_point + "?" + encoded_params
#    print req_url

    try:
        # openの引数1コはGET, 2コはPOST
        response = u_agent.open(req_url)
    except urllib2.HTTPError as err:
        print("HTTP ERROR "+ err.reason)
        return
    except socket.timeout as err:
        print("SOCKET.TIMEOUT")
        return
    except Exception as err:
        print("UNKNOWN ERROR")
        print traceback.format_exc()
        return
    else:
        pass

    res_all_json = response.read()
    res_all = json.loads( res_all_json )
    res_geocodes = res_all["Feature"]

    for ret_geocode in res_geocodes:
        disp_line = "\t".join([ret_geocode["Geometry"]["Coordinates"],
                               ret_geocode["Property"]["Address"] ]);
        print disp_line

if __name__ == '__main__':
    main()

↑こう書くと、↓こうできます

$ ./foo.py
139.70199214,35.66043257    東京都渋谷区渋谷1丁目

scikit-learn for python による k-means 分類(クラスタリング)

基本的なクラスタリング法ですが、その内容を理解できていない為

k-meansのアルゴリズム

https://ja.wikipedia.org/wiki/K%E5%B9%B3%E5%9D%87%E6%B3%95

wikipediaに記載されている通り

  1. N個のデータに対し、ランダムにK個のクラスタを割り振る
  2. クラスタの重心/中心を算出
  3. 各データ ~ 各クラスタ重心の距離を算出し、最も近いクラスタへ再割り振り
  4. 上記の再割り振りがなくなるまで、上記2 & 3を繰り返す

k-nearest (k近傍法)との違い

名前はk-meansに似ていますが、 k近傍法では、予め用意された点に最も近い距離に分類します。 次のurlが分かりやすいです。 http://qiita.com/NoriakiOshita/items/698056cb74819624461f

pythonでは scikit-learn の KMeans() で

http://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html

install scikit-learn

依存moduleもあるので、pipでinstall

# /usr/local/bin/pip numy
# /usr/local/bin/pip scipy
# /usr/local/bin/pip pandas
# /usr/local/bin/pip scikit-learn

scikit-learn の KMeans() の使用例

http://blog.amedama.jp/entry/2017/03/19/160121 ↑こちらの写経。

#!/usr/local/bin/python
# -*- coding: utf-8 -*-
import numpy as np
from sklearn.cluster import KMeans

def main():
    # 3科目の点数
    features = np.array([
        [  80,  85, 100 ],[  96, 100, 100 ],[  54,  83,  98 ],[  80,  98,  98 ],
        [  90,  92,  91 ],[  84,  78,  82 ],[  79, 100,  96 ],[  88,  92,  92 ],
        [  98,  73,  72 ],[  75,  84,  85 ],[  92, 100,  96 ],[  96,  92,  90 ],
        [  99,  76,  91 ],[  75,  82,  88 ],[  90,  94,  94 ],[  54,  84,  87 ],
        [  92,  89,  62 ],[  88,  94,  97 ],[  42,  99,  80 ],[  70,  98,  70 ],
        [  94,  78,  83 ],[  52,  73,  87 ],[  94,  88,  72 ],[  70,  73,  80 ],
        [  95,  84,  90 ],[  95,  88,  84 ],[  75,  97,  89 ],[  49,  81,  86 ],
        [  83,  72,  80 ],[  75,  73,  88 ],[  79,  82,  76 ],[ 100,  77,  89 ],
        [  88,  63,  79 ],[ 100,  50,  86 ],[  55,  96,  84 ],[  92,  74,  77 ],
        [  97,  50,  73 ],
        ])

    # 分類実行 (3個のクラスタに)
    kmeans_model = KMeans(n_clusters=3, random_state=None).fit(features)
    # 表示
    for label, feature in zip(kmeans_model.labels_, features):
        print(label, feature, feature.sum())

if __name__ == '__main__':
    main()

↑こう書くと、↓こう表示されます

$ ./kmeans_scikit_learn.py 
(2, array([ 80,  85, 100]), 265)
(2, array([ 96, 100, 100]), 296)
(0, array([54, 83, 98]), 235)
(2, array([80, 98, 98]), 276)
(2, array([90, 92, 91]), 273)
(1, array([84, 78, 82]), 244)
(2, array([ 79, 100,  96]), 275)
(2, array([88, 92, 92]), 272)
(1, array([98, 73, 72]), 243)
(2, array([75, 84, 85]), 244)
(2, array([ 92, 100,  96]), 288)
(2, array([96, 92, 90]), 278)
(1, array([99, 76, 91]), 266)
(2, array([75, 82, 88]), 245)
(2, array([90, 94, 94]), 278)
(0, array([54, 84, 87]), 225)
(1, array([92, 89, 62]), 243)
(2, array([88, 94, 97]), 279)
(0, array([42, 99, 80]), 221)
(0, array([70, 98, 70]), 238)
(1, array([94, 78, 83]), 255)
(0, array([52, 73, 87]), 212)
(1, array([94, 88, 72]), 254)
(0, array([70, 73, 80]), 223)
(2, array([95, 84, 90]), 269)
(2, array([95, 88, 84]), 267)
(2, array([75, 97, 89]), 261)
(0, array([49, 81, 86]), 216)
(1, array([83, 72, 80]), 235)
(1, array([75, 73, 88]), 236)
(1, array([79, 82, 76]), 237)
(1, array([100,  77,  89]), 266)
(1, array([88, 63, 79]), 230)
(1, array([100,  50,  86]), 236)
(0, array([55, 96, 84]), 235)
(1, array([92, 74, 77]), 243)
(1, array([97, 50, 73]), 220)

apache access_log にあるuser agentからブラウザを判定

昔のエントリのオマージュ。

HTTP User Agentによるブラウザ、OS判定(判別?)なら user-agent-string.info - end0tknr's kipple - 新web写経開発

ssl decryptを行っているロードバランサ(ELB)の配下で 動いているapacheに対し、アクセスしてくるclientのSSL/TLSの対応状況を確認したかった為。

https://help.salesforce.com/articleView?id=000220586&language=ja&type=1

UserAgentString.com - unknown version

に対し、次のscriptのように問合せすれば、OKのはず。

#!/usr/local/bin/perl
use strict;
use warnings;
use Encode;
use HTTP::Request::Common;
use LWP::UserAgent;
use Data::Dumper;

my $REGEXP =
    join(' ',
         '^([^ ]*) ([^ ]*) ([^ ]*) \[([^]]*)\] "([^ ]*)(?: *([^ ]*)',
         '*([^ ]*))?" ([^ ]*) ([^ ]*) "(.*?)" "(.*?)"');

main(@ARGV);

sub main {
    my (@log_files) = @_;

    my $user_agents = collect_user_agent(@log_files);

    my $i = 0;
    for my $user_agent (sort{$user_agents->{$b}<=>$user_agents->{$a}}
                        keys %$user_agents ){
        my $us_short_name = short_name_from_user_anget($user_agent);

        my $access_cnt = $user_agents->{$user_agent};
        print join("\t", $access_cnt, $us_short_name, $user_agent),"\n";
    }
}


sub short_name_from_user_anget {
    my ($user_agent) = @_;

    my $ua = LWP::UserAgent->new;
    $ua->agent($user_agent);
    $ua->timeout(10);

    my $response = $ua->get('http://www.useragentstring.com/');
    unless($response->is_success) {
        die $response->status_line;
    }
    my $html_str = $response->content;

    # scraping が面倒なので、regexp match
    if($html_str =~ /<th colspan='2'>.*?([^<>'=\/]+)<\/th>/o ){
        return $1;
    }
    print STDERR "fail short_name_from_user_anget($user_agent)\n";
    return "";
}

sub collect_user_agent {
    my (@log_files) = @_;
    my $summary = {};
    for my $log_file ( @log_files ){
        open(my $fh ,$log_file) or die "can't open $log_file $!";
        
        while (my $line = <$fh>){
            chomp($line);
            #parse log
            my ($host, $ident, $user, $datetime, $method, $resource,
                $proto, $status, $bytes, $referer, $agent) =
                    $line =~ /$REGEXP/o;
            
            $summary->{$agent} += 1;
        }
        close($fh) or die "can't close $log_file $!";
    }
    return $summary;
}