MacでのPyAutoGUIの注意点

# MacでPyAutoGUIを利用するにあたって注意すべき点

## 環境
macOS Big Sur 11.2.2
Python 3.8.6
PyautoGUI 0.9.52
デフォルトシェル　zsh

## 撮影が上手く出来ない
* スクリーンショットが上手く撮れない
* confidenceを下げてもlocateCenterOnScreenが反応しなかったり、NullやNotImageExceptionが起きる。（画像を使った機能は内部でスクリーンショットを利用している) 

```python:shot.py
#例えばJupyterLabやブラウザを立ち上げた状態でスクリーンショットを撮って保存する
import pyautogui
sc = pyautogui.screenshot("fullscreen.png")
```
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/661162/32cd1902-ba12-085c-fb2d-7b75926406c1.png "真っ新なデスクトップが表示されている")
真っ新なデスクトップが表示されており、思ったようなスクリーンショットが出来ていない。

### 解決策
1. システムと環境設定を開く
2. セキュリティとプライバシーをクリック
2. プライバシータブを選択
3. 画面収録を選択
4. 鍵を外してターミナルにチェック、ターミナルの再起動

## moveToなどのマウス操作が機能しない
``` python:mouse.py
import pyautogui
pyautogui.moveTo(1,1)
print(pyautogui.position())

#実行すると、
#Point(x=230, y=326)
#といった具合に全く動いていない
```

### 解決策
1. システムと環境設定を開く
2. セキュリティとプライバシーをクリック
2. プライバシータブを選択
3. アクセシビリティを選択
4. 鍵を外してターミナルとAEServer（OSのバージョンによって名称が異なるかも。）にチェックを入れて、再起動

## 画像認識の場所がどうもおかしい
``` python:detec.py
import pyautogui
pos = pyautogui.locateCenterOnScreen('youtube.png', grayscale=False,confidence=0.8)
print(pos)
pyautogui.moveTo(pos.x,pos.y)
print(pyautogui.position())

#座標だけ見ると正しい位置に移動した様に見える
#Point(x=394, y=234)
#Point(x=394, y=234)

#しかし明らかにカーソルはターゲットとズレている。
#一致した箇所のスクリーンを撮影すると、認識した場所とカーソルがズレていることがわかりやすい。
sc = pyautogui.screenshot(region(pos.left,pos.top,pos.width,pos.height))
sc.save('deteceObj.png')
```

### 解決策
x,yともに2で割ると上手くいく

``` python:detec.py
import pyautogui
pos = pyautogui.locateCenterOnScreen('target.png', grayscale=False,confidence=0.8)
print(pos)
pyautogui.moveTo(pos.x/2,pos.y/2)
print(pyautogui.position())

#座標だけ見るとズレている様だが、実際には認識した場所にカーソルがある。
#Point(x=337, y=230)
#Point(x=168, y=115)
```

## locateCenterOnScreeなど認識が上手く出来ない
### 解決策
オプションの変更や、RGB値でのマッチにする

``` chageoption.py
# 識別精度を調整する　opencv-pythongが必要
pos1 = pyautogui.locateCenterOnScreen('target.png',confidence=0.8)

#グレースケールでの検索（高速になるらしいが、個人的には違いがわからない。。）
pos2 = pyautogui.locateOnScreen('traget.png', grayscale=True)

#探索範囲を絞る この場合、(1,1)から幅400px、高さ200pxのエリアで検索する
pos3 = pyautogui.locateOnScreen('taget.png', region=(1, 1, 400, 200))

# RGBマッチにしてみる この場合、(100,100)が赤色(255, 0, 0)かどうかのtrue/falseを返す
# オプションでマッチする範囲を広げられる
ismatch = pyautogui.pixelMatchesColor(100, 100, (255, 0, 0))
ismatch = pyautogui.pixelMatchesColor(100, 100, (255, 0, 0),tolerance=30)

```
## cronを利用するとFileNotFoundErrorになり実行できない

なお、そもそもcronが利用できない場合は、```システム環境設定→セキュリティとプライバシー→フルディスクアクセス```にcronがあるか確認する。cronがない場合は、鍵を外して+マークから/usr/sbin/cronを追加し、チェックマークを入れて鍵を掛けておく。

次の例では毎日21時に上述のshoto.pyを実行し、ログを書き出す。
(cronの書き方については[こちらなど](https://qiita.com/hikouki/items/e744b3a4d356d2af12cf)を参考に)
なお、pythonのパスは```which python```とターミナル入力すれば取得できる。

```  
0 21 * * * pythonのフルパス /Users/ユーザーネーム/Desktop/shoto.py >  /Users/ユーザーネーム/Desktop/err.log 2>&1
```

実行するとerr.logに以下の様な文章が書き出された。
（ログに何も書き出されていない場合はターミナルからmailを実行すると何かしらログを確認できるかも）

```
Traceback (most recent call last):
  File "/Users/ユーザーネーム/shoto.py", line 2, in <module>
    sc = pyautogui.screenshot("fullscreen.png")
  File "/Users/ユーザーネーム/.pyenv/versions/3.8.6/lib/python3.8/site-packages/pyscreeze/__init__.py", line 446, in _screenshot_osx
    subprocess.call(['screencapture', '-x', tmpFilename])
  File "/Users/ユーザーネーム/.pyenv/versions/3.8.6/lib/python3.8/subprocess.py", line 340, in call
    with Popen(*popenargs, **kwargs) as p:
  File "/Users/ユーザーネーム/.pyenv/versions/3.8.6/lib/python3.8/subprocess.py", line 854, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/Users/ユーザーネーム/.pyenv/versions/3.8.6/lib/python3.8/subprocess.py", line 1702, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'screencapture'
```

### 解決策1
内部(私の場合は/Users/ユーザーネーム/.pyenv/versions/3.8.6/lib/python3.8/site-packages/pyscreeze/\_\_init__.py)で呼び出されるscreencaptureは/usr/sbinにあるが、cronの環境変数は/usr/binと/binのみの為に実行出来ない模様。

そこで、cronの環境変数に/usr/sbinを追加して実行する。
また、cronが実行されるのはホームディレクトリだが、実際に実行しているshoto.pyはホームディレクトリでは無いので困ることがある。
shoto.pyの場合、fullscreen.pngがshoto.pyと同じフォルダではなく、ホームディレクトリに保存される。絶対pathで保存先を指定すれば回避できるが、他にも困ることがありそうなのでついでに変更する。os.chdir()を使ってPython側で変更しても上手くいくと思う。

```  
PATH=/usr/sbin:/usr/bin:/bin
0 21 * * *  cd /Users/ユーザー名/Desktop && pythonのフルパス shoto.py >  err.txt 2>&1
```
### 解決策2 
cronでPythonを直接実行せず、シェルスクリプトを実行する方法もある。
この場合も解決策1と同様に/usr/sbinを追加する必要がある。

``` shoto.sh
#!/bin/zsh
PATH=/usr/sbin:$PATH
cd /Users/ユーザー名/Desktop/scr
pythonのフルパス shoto.py
```

crontabの記述

```  
0 21 * * * zsh /Users/ユーザー名/Desktop/shoto.sh > /Users/ユーザー名/Desktop/err.txt 2>&1
```
## cronを利用すると撮影が出来ない
cronを利用しないと実行できるが、cronを利用すると上述の「撮影が出来ない」と同じ症状になる。

### 解決策
1. システムと環境設定を開く
2. セキュリティとプライバシーをクリック
3. プライバシーをクリック
4. 画面収録をクリック
5. 鍵を外して+ボタン/usr/sbin/cronを追加し、チェックマークを入れて鍵を閉じる
  （「crontabを利用するとFileNotFoundErrorになり実行できない」解決策2、のようにシェルを利用している場合は代わりに/bin/zshを追加する）

>参考
>[MacBook ProでPyAutoGUIの座標が正しく取れない？](https://qiita.com/yotsuya-design/items/a944a04ffb8e125a7ef3)
>[備忘Lock!! crontabの利用法](http://kapibara3kapibara3.blogspot.com/2018/01/crontab.html)
>[cronの環境変数について](https://qiita.com/positrium/items/a2de9af6c5b4d06b504e)
>[Unable to execute a python script via crontab, but can execute it manually. What gives?](https://www.digitalocean.com/community/questions/unable-to-execute-a-python-script-via-crontab-but-can-execute-it-manually-what-gives)