会社の業務をRPAで効率化した話

#はじめに
ここ数年でRPAが大きく話題になったような気がします。そのRPAとやらを自作して会社で使っちゃおうというお話です。
今回はお話程度なのでライブラリなどについて詳しく解説や使い方の説明はできませんが、順を追って解説を投稿していきたいと思います。

##開発のきっかけ
あるWindowsアプリケーションから項目ごとにプリンターを変えて出力する作業を多い時には150件くらい行います。（およそ2時間座ったまま。）新卒で入社しなれていない自分にとってはかなり苦痛で、おまけに同時進行でモバイルアプリの開発を頼まれ、、このままじゃアプリ開発が進まなくなると思い、RPAを開発しようと決意しました！
##業務の作業環境
- Windows Server 2012 R2
- シンクライアント端末
- Pythonはインストールされていない

##開発環境
- iMac 2019モデル Big sur(Boot Campでwindows10 Proを使用)
- Python 3.8.6
- IDLE 3.8

##機能要件
1. **項目(数字)を読み取り適切なプリンターを選択する**
-  **pythonがない環境でスタンドアロンで動くアプリにする**
-  **印刷時にエラーが発生したらため込み、処理終了後に確認できるようにする**
-  **PCの画面に表示されたものを正確に読み取る**

##開発スタート
RPA初心者なのでまずはPythonのできることについて徹底的に調べ上げました。
結果、機能要件を満足させることが可能なライブラリを見つけました。

| 機能要件 | ライブラリ |
|:-:|:--|
| 機能要件1 |Pyocr(画面のもじ読み取り),PyAutoGUI(キーボード操作等)|
| 機能要件2 |Tkinter(GUI作成),Pyinstaller(exe化)|
| 機能要件3 |csv(csvの処理)|
| 機能要件4 |PyAutoGUI(画面のキャプチャ)|
これで準備は万端です、開発環境に`pip install`でじゃんじゃんインストールしていきます。
###画面作成
とりあえず機能要件２から攻略していきましょうか...簡単そうですし(苦)

```python
import tkinter as tk

def run():
    root = tk.Tk()                  #メイン画面の生成
    root.geometry ('300x180')       #メイン画面のサイズ
    root.title('めざせ業務効率化')  #メイン画面のタイトル

    #ボタンの定義
    button = tk.Button(
        root,                       #配置する画面の名前
        width = 15,                 #ボタンのサイズ
        text = '実行',              #ボタンのテキスト
        command = test              #押したときに実行する関数
        )
    button.pack(anchor='center',expand=1)
    root.mainloop()

def example():
    print("実行ボタンを押したよ！")
    
if __name__ == '__main__':
    run()

```

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186268/8c4a1eea-3cfe-fe0a-206d-21684c53d1b0.png">
たったこれだけでGUIができちゃうのです...素晴らしいですねLoL。これをPyinstallerを使用してexe化しちゃえばスタンドアロンアプリの完成です！当然これだけじゃゴミ箱行き確定です...つづけていきましょう
###数字の読み取り
機能要件1と機能要件4は同じなようで同じではないんですね～～、、
数字の読み取りは調べたところ、Pyocrを使えばできると知り、さっそくPyocrを導入しました。
パラメーターとかいじってみましたが結構、誤認識され使い物にならず...(世の中そんなに甘くないみたいです)
どうしたものかと、しばらく悩みました。ある日Pyocrのことを調べていると、手書き数字の学習で得た認識力を使用しているみたいなことがかいてあったのをみてひらめきました💡自分で数字を読み取る**ニューラルネットワーク**を作っちゃえばいいじゃんって！
デジタル数字の読み取りなのでそこまで難しいものではありませんし(^^)/
####ネットワーク構成
```python
class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1,32,3)
        self.pool = nn.MaxPool2d(2,2)
        self.conv2 = nn.Conv2d(32,64,3)
        self.fc1 = nn.Linear(64*5*5,800)
        self.dropout = nn.Dropout(p=0.5)
        self.fc2 = nn.Linear(800,10)
    
    def forward(self,x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1,64*5*5)
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = F.relu(self.fc2(x))
        return x
```
>x = 28x28の画像(オリジナル画像をリサイズします)


このネットワーク構成が最も結果が良かったです。
他にもいい方法があったかもしれませんが、一番手っ取り早かったので機械学習を用いました👍
項目は４桁の数字になって画面上に出力されているので、その部分を４等分にして１文字ずつ判定するという仕組みにしました
![waku.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186268/f88c391b-339d-c61e-336a-67aaaea1e81f.png)
(↑こんな感じです)
これで完璧かと思いきや...それでも誤認識がでる..デジタル数字なんて変わらないはずなのに....
でも、たまになので対策は簡単です。
判定処理を３回以上ループさせて最も同じ判定結果の数が多かったものを選ぶ感じですね、多数決的な奴です

```python
results = ['1000','1000','1005','1005','1000']        #５回分の判定結果
def majority(results):
    majo_list = []
    for i in range(len(results)):
        majo_list.append(results.count(results[i]))   #要素が自分の中にどれだけ存在するか探索
    if majo_list.count(1) == len(majo_list):          #majo_list内に１が要素分あったらすべてミスということで0000を返す
        return '0000'
    else:
        ind = majo_list.index(max(majo_list))         #majo_list内でもっとも大きい数のindexをresultsのindexとして扱う
        return results[ind]
```
>この場合1000がリターンされる

この仕組みをかますことで誤認識による操作ミスは０になりました。（運用開始から２か月ほど経過）
あとは適当にCSVか何かに項目に対してのプリンターと、キーボードの押す回数を定義してあげれば動くようになります。
これで機能要件１はクリアです♪
(いろいろ試行錯誤しながらやったせいでだいぶん時間がかかってしまいましたが....)
###画面の読み取り
Windowsアプリケーションからポップアップが３，４種類ほど出てくるのでそれをPyautoGUIを使用して検知していきます

```python
import pyautogui as pg

pg.locateCenterOnScreen('検索したい画像のPATH',grayscale=(True or False),confidence=(0~1),region=(x,y,width,height))
```

| 引数 | 説明 |
|:-:|:--|
|PATH(文字列)|イメージ画像のある場所を相対PATH又は、絶対PATHで指定します。| 
|grayscale  |画像と画面を白黒で判断します。精密ではなくなるので少し誤判定があります。| 
|confidence |あいまいさといわれるものです。0.8にすれば８割マッチングで存在すると判断します| 
|region|探索する画面の範囲です。座標(x,y)から幅(width),高さ(height)分を範囲とします|

>引数はPATH以外は省略可能です
>confidenceは別途**cv2**をimportしなければ使えません。

この機能を使います。指定した画像が画面上にあるかどうかを勝手に探索し、存在する場合は対象物の中心座標をRETURNしてくれます。さらに`pyautogui.click()`を併用することでPCの自動操作が実現できるのです!

```python
import pyautogui as pg

path = 画像へのPATH
location = pg.locateCenterOnScreen(path)
pg.click(location)
```
↑のコードは対象を探し出し、クリックするという動きになります。
Windowsアプリケーションからアラートが出てるか監視し、アラートがでていたらその範囲でまた「はい」、「いいえ」を探索し、「はい」を押させるといった流れですね！ほかにもキーボード操作や、スクリーンショットの撮影など多様な機能が用意されています。
これらの機能を駆使すれば機能要件４はクリアできますね！
###エラー保存
エラーがあった場合にCSVで出力され都度EXCELやメモ帳などで開かれる仕様でした。これがでていると処理の邪魔になり自動処理の妨げになるので無視することはできません...結構苦労しましたが、CSVが出力されるとフォーカスがEXCEL等になるという特徴を生かして、現在のフォーカスを監視し、EXCELなどにフォーカスがあたっていたら`pyautogui.hotkey('alt','f4')`を実行して閉じる仕組みを作りました。その閉じたタイミングでCSVファイルを別のCSVファイルに取り込むという流れです。その時に使用したのが`csv`です！

```python
import csv

csv_path = 'C:hogehoge'
with open(csv_path) as f:
    data = list(csv.reader(f))
```
このような感じでcsvを取り込み、別のCSVに書き込みエラーの保存をします。
これで、機能要件３をクリアすることができますね！

##結果
RPAを開発して業務効率は格段にあがりました。今までは付きっきりで処理をしなければなりませんでしたが、ボタンを押すだけで終了まで自動で処理し続けてくれるので２時間かかっていた処理が実質０になり、アプリ開発にあてれる時間が増えかなりのスピードで開発することができています！

##課題
- pyinstallerでexe化した後のファイルのサイズがかなり大きい
- 業務環境で実行するといったんexeファイルをtmpフォルダに展開するのでかなり起動が遅い

> 試行錯誤しながら改善出来たらなぁ🤔

##最後に
今回RPAを自作してみてさらにPythonのありがたみがわかりました！RPAを作成するにあたってディープラーニングからWindowsの操作、CSVデータの取り扱いなど、様々な技術に触れることができていい経験になったと思います！これから会社でRPAを自作する方はぜひ参考にしていただけたらと思います。最後までありがとうございました！