Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

SikuliXでRPAをやるために覚えたこと

More than 1 year has passed since last update.

はじめに

SikuliXでいろいろやってるうちに、これはいろいろできると思って、サンプルを作っているうちにわかってきたことの覚書です。

コードをUTF-8に固定しよう

Python使いならやる、コードの先頭に書くあれです

start.py
# -*- coding: utf-8 -*-

最低限のモジュールを読み込んでおこう

基本的にフルに読み込む場合はimportでライブラリ名を指定しておけばOK、デフォルトの設定を変える場合はreloadする必要があった
sikulixはなぜかsysをreloadしておかないと、デフォルトの文字コードやフォントサイズが変更できなかった

import.py
import os
import sys
import time
import datetime
import csv
import subprocess
reload(sys)
sys.setdefaultencoding("utf-8")

グローバル変数をセットしよう

関数内部から呼び出したりする変数として、必要なものを定義します。
ここでは、whileでループを切り替えるための変数を設定しています

grobal_val.py
#ループ制御用変数
global infinitloop
infinitloop = 0; 

コマンドライン実行した場合の停止用ホットキーを定義しよう

IDEから起動する限り、IDEの割り込みキーが効きますが、コマンドラインから実行した場合割り込みが効かなくなるので、ホットキーを別途設定する場合、以下のように設定します

hotkey.py
##ホットキーの登録(コマンドラインから実行した場合の停止受付)
def StopOrContinue(event):
    ans = popAsk(u"スクリプトを停止しますか?",u"確認")
    if ans == True:
        sys.exit() # スクリプト自体を強制停止

##スクリプトを正常終了させるための割り込み
def StopLoop(event):
    global infinitloop
    infinitloop = 1
    return infinitloop # Whileループを脱するようにして終了

#StopOrContinueとStopLoopに対してイベントを起こすホットキーを登録
Env.addHotkey(Key.UP,KeyModifier.CTRL,StopOrContinue) # CLI実行時の停止用(CTRL+↑で、StopOrContinueを呼ぶ)
Env.addHotkey(Key.DOWN,KeyModifier.CTRL,StopLoop)     # IDE実行時の停止用(CTRL+↓で、StopLoopを呼ぶ)
#Env.removeHotkey("o",KeyModifier.CTRL,keepdown_up)   # Env.addHotkeyで追加したホットキーを解除する

システム変数を設定しよう

デフォルト値を変更する場合以下のように設定します

settings.py
#各種移動ディレイ(デフォルト:1)、0にするとマウスオーバーで何か動作する処理とかが動かなくなるので注意
Settings.MoveMouseDelay  = 0.5
Settings.DelayAfterDrag  = 0.5
Settings.DelayBeforeDrop = 0.5

#ログの出力制御
Settings.ActionLogs      = True
Settings.InfoLogs        = True
Settings.DebugLogs       = False

#最小マッチング率、デフォルト 0.70 最小 0.01 最大 0.99 具体的には similar(0.01~0.99) のこと
#PatternのSimilarで都度設定しない場合の最小値を変更する
Settings.MinSimilarity   = 0.99

#Region.onChange()を使用したときに、変更イベントをトリガーする内容を変更するピクセル単位の最小サイズ。
Settings.ObserveMinChangedPixels = 50

#1秒辺りの走査回数を定義 ※ただし、CPU性能と描画性能がものをいうので、上げ過ぎても効果なし
Settings.WaitScanRate    = 10
Settings.ObserveScanRate = 10

#動作前にハイライトマーカーを出すかどうか[True|False]
Settings.setShowActions(False)

#DelayBeforeMouseDownは、ソース位置のマウスがダウンするまでの待ち時間を小数値(秒)で指定します。
Settings.DelayBeforeMouseDown = 0.1

#DelayBeforeDragは、ソース位置でマウスが押された後の待機時間を小数値(秒)で指定します。
Settings.DelayBeforeDrag = 0.1

#DelayBeforeDropは、目標位置にマウスが上がるまでの待ち時間を数値(秒)で指定します。
Settings.DelayBeforeDrop = 0.1

#マウスの上下の間隔を秒単位で指定するには、0.nnnと指定します。 
Settings.ClickDelay = 0.1

#キーを押している間の遅延を秒単位で指定します(0.nnn)。
Settings.TypeDelay = 0.1

#その他初期変数
Settings.InputFontMono   = True
Settings.InputFontSize   = 20

スクリプトの開始・終了時間をログに残すには

timeでログを表示すればok、

echolog.py
#ログ
print u"開始時刻:",datetime.datetime.today()

#ログ
print u"終了時刻:",datetime.datetime.today()

ファイル読み込み/書き込み

単純に読みだして、書き込むだけ

readwrite.py
f = open('text.txt')
data1 = f.read()  # ファイル終端まで全て読んだデータを返す
f.close()

print type(data1) # 文字列データ
lines1 = data1.split('\n') # 改行で区切る(改行文字そのものは戻り値のデータには含まれない)
print type(lines1)
for line in lines1:
    print line
print

f = open('text.txt', 'w') # 書き込みモードで開く
f.write(data1) # 引数の文字列をファイルに書き込む
f.close() # ファイルを閉じる

アプリケーション名(ウインドウ名)で起動の有無を確認

openAppだと起動させるところからやらないといけないが、AppやswitchAppはすでに起動中のアプリのタイトルバーを完全一致/部分一致で探してフォーカスを切り替えることができる
また、App()は返り値にオブジェクトハンドルが戻るので、それを利用して、isRunning()やhasWindow()、getWindow()を使って画面上にそのウインドウが出ているか調べられる
以下は「名前を付けて保存」が出現しているかどうかの判定

chktitle.py
chkApp = App(u"名前を付けて保存")
if chkApp.isRunning():
    switchApp(u"名前を付けて保存")
    wait(1)
    click(Pattern("filename.png").targetOffset(74,-2))
    type("l", KEY_SHIFT)
    type("s", KEY_SHIFT)
    type(Key.TAB)
else:
    popup(u"名前を付けて保存がなかった",title = u"警告")

画像認識範囲と画像倍率

画像認識の範囲が広ければ広いほど、時間もスペックも必要になるのでRegionを使って探索範囲を狭めるのもいいと思う、あと、描画後の待機時間もデフォルト3秒だと長すぎて遅かったりするので変更推奨、
また、用意した判定用の画像の縮尺、対象の表示が自動で変わるような環境で(Androidシミュレーターなど)、再度サンプル画像を用意するのが面倒な場合は、AlwaysResizeを使えば判定用の画像をリサイズしてくれる

region_resize.py
#認識範囲
area = Region(0,0,1920,1080)

#描画待機時間の指定、早くし過ぎると反応しなくなるので注意[default=3]
area.setAutoWaitTimeout(0.4)

#画像を取得したときよりサイズが小さい環境で実施した場合、マッチする画像を自動で縮小できる[範囲:1~0.01]
#1920*1080 -> 1280*720 のウインドウにリサイズした場合、0.66~0.67倍
#1920*1080 -> 1024*768 のウインドウにリサイズした場合、0.53~0.54倍
Settings.AlwaysResize = 1

ファイル名の配列化と取り出し

連続で判定する場合や、同じ条件で識別する場合、画像名を配列に入れておけば楽になる

arrey_img.py
    InputBoxImg = "InBox.png"
    fnames = ["0.png","1.png","2.png","3.png","4.png"]
    for ImgName in fnames :
        click(ImgName) # 配列から1つずつ取り出してクリック
        time.sleep(1) # 待ち
        click(InputBoxImg) #フォームクリック
        paste(ImgName) # ファイル名をペースト
        print u"File Name : %s" % Imgname

判定座標を取る場合

画像判定したあと、座標情報が値としてほしい場合には以下のようにする

find_pos.py
    res = find("find.bmp") # 画像探索
    Pos = resPos.getCenter() # getCenter() 中央座標 getTopLeft() 左上 getTopRight() 右上 getBottomLeft() 左下 getBottomRight() 右下
    MousePos = Location(Pos.getX(),Pos.getY()) # 直前で実行したget???()からX,Y座標を取り出す
    print Pos.getW() + "/" + Pos.getH() # 取得範囲の幅と高さ取得

マウスの直接移動

マウスの移動は、Locationを使って配列データにしてからじゃないと使えないので注意。

mouse_use.py
    PX=1
    PY=1
    MousePos = Location(PX,PY) #直接座標指定
    mouseMove(MousePos)    # Locationで変換してから入れないと使えない
    mouseDown(Button.LEFT) # Button.LEFT, Button.MIDDLE, Button.RIGHT
    mouseUp(Button.LEFT)   # Button.LEFT, Button.MIDDLE, Button.RIGHT
    wheel(MousePos, WHEEL_DOWN , 1 ) # WHEEL_DOWN | WHEEL_UP
    wheel(MousePos, WHEEL_UP , 1 ) # WHEEL_DOWN | WHEEL_UP

Drag&Dropをする場合

dragDropは、画像to画像で使うdragDrop("Drag.bmp","Drop.bmp")のような使い方のほかに、Drag座標とDrop座標を指定して実行する方法がある
その場合、座標はLocationで変換してから渡す必要がある。

dad_loc.py
    #ドラック&ドロップはロケーションを指定して使う、アプリのタップ移動もこれ
    StLocation = Location(1,1) #直接座標指定
    EdLocation = Location(5,5) #直接座標指定
    dragDrop(StLocation,EdLocation)

発見したらハイライト表示

画面上のどこを識別したのかデバックするのに便利、Sikulix 1.1.2から利用できるようになった機能、1.1.1はマニュアルにあったけどバグで動かなかった

highlight.py
    #発見物ハイライト
    find(Pattern(targetimage).similar(samefee).targetOffset(xx,yy)).highlight(1)
    click(getLastMatch()) # findの情報をそのまま継承してクリックする場合
    click(Pattern(targetimage).similar(samefee).targetOffset(xx,yy)) #再指定する場合

無限ループで判定を繰り返す

無限ループをやる場合は安全策として、ホットキーなんかで、whileを止めれるようにするか、何かしら割り込みをできるようにしておかないと悲惨な目に合う。

while_find.py
    while infinitloop == 0:
        try:
            if area.exists("ok_btn.png",0.1):
                click(Pattern("ok_btn.png").similar(0.90).targetOffset(82,27))
                wait("check_bar.bmp")
                click(getLastMatch())
                click("push_btn.png")
                time.sleep(2)
        except Exception as e2:
            raise e2
            print 'System Error : ' + e2

さいごに

私がよく使っているSikuliXの命令と使い方を紹介してみました。怪しい書き方をしている箇所が何か所かありますが、見逃してください(苦笑

誰かの役に立つことを祈って、公開しておきます

hirohiro77
インフラ屋さんやってます。構成の設計とか、仮想サーバのお守とか、システムのマイナーアップデートとか、監視など。 [[ また、このサイトにおける掲載内容はあくまで私自身の見解であり、私の所属団体・企業における立場、戦略、意見を代表するものではありません ]]
opt
"INNOVATION AGENCY" を標榜するインターネット広告代理店。エンジニア組織 "Opt Techonologies" を中心にアドテクetc...に取り組んでいます。
https://opt-technologies.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away