gologiusの巣

プログラミングなどの技術メモです。誰かの役に立てるとうれしいです。

メール監視システムを作る その1(サーバー用意編)

要件としては以下の二つです

  • メールを監視したい。 GMailのルールだと限界感があるので、プログラムでゴニョゴニョやりたい。
  • 特定条件ならエラーメールを送信したい。

メールはGmailのメールアドレス(一つ)が対象なので、 GmailAPIを叩くPythonスクリプトを作成します。

作ったスクリプトはサーバー上で一定時間ごとに動かします。

サーバーを用意する

これを買いました。

https://www.amazon.co.jp/gp/product/B07CSN3CLY/ref=oh_aui_detailpage_o03_s00?ie=UTF8&psc=1www.amazon.co.jp

Win10が入ってました(OSなし版もあるみたいです) が容赦なくUbuntu 18.04.1 LTSを上書きインストールします。 デュアルブートにしてもいいんですけど、Win10はメインPCとノートPCに既にあるので今回はなしで。

なぜOSなし版を買わなかった・・・(3000円くらい安い、みたいな意見をどこかで見ました)

環境構築

Ubuntuのインストール方法や、SSHの構築方法は省略します。 いろんな記事があるので適用に参考にすればいいと思います。

  • GmailAPIを叩くスクリプトPythonで作成(次回紹介)
  • 上記Python環境を叩くためのPython環境(今回紹介
  • 一定時間ごとにバッチを叩く→cronを使用(今回説明

Python環境構築

Ubuntu 18.04.1 LTSPythonが既にインストールされています。 素晴らしいですね。

ただし、Python2系とPython3系が両方入っていますpythonコマンドで実行されるのは、デフォルトは2系です(python --versionで調べられます)

パッケージもPython2系とPython3系で管理が分かれています。 ですので、パッケージインストール時はpip install hogepip3 install hogeか、 どちらのバージョンに入れるのかを、明示的に指定する必要があります。

実行時も同様にpython hoge.pypython3 hoge.pyかどちらで実行するか、 明示的に指定する必要があります。

cron環境+動作設定

cronはスケジュール実行する仕組みです(Windowsでいうタスクスケジューラー) 有名な仕組みなので、色々情報もネット上にありますのでここで詳しく説明はしません。

ただですね、Ubuntu 18.04.1 LTSの情報が全然なく(パス関連が微妙に違う)、 困りました。

cronログ出力設定

デフォルト設定ではcronの実行ログが出力されず、cronが動いているか分からないので、 ログを出力するようにします。

/etc/rsyslog.d/50-default.conf

下記がコメントアウトされているのでコメントイン

cron.*    /var/log/cron.log

スケジュール設定

crontab -e

cron設定用のエディタが開くので、以下のような感じで記述 文法自体は他のサイトを参照してください。

crontabの書き方 | server-memo.net

#10分ごとにメールチェックをするバッチを叩きにいく。絶対パスにすること
*/10 * * * * /home/username/python/mail_checker/start.sh

start.shの中身はこんな感じ。実行日ごとにログファイルを作成し、追記しています。

#!/bin/sh
cd  `dirname ${0}`
date
datestr=`date '+%Y%m%d'`

echo begin mail checker
python3 main.py >> log/${datestr}.log 2>&1
echo end mail checker

GMailAPI を叩くPythonスクリプトです · GitHub

設定後、cronをreloadrestartすると、設定スケジュールどおりに実行される。 ※どちらが良いかは不明

/etc/init.d/cron restart
もしくは
/etc/init.d/cron reload

実行されているかは

/etc/init.d/cron status

で確認可能です。

まとめ

サーバー上で一定時間ごとにPythonスクリプトを起動させる準備が整いました。 次回はPythonでGmailAPIを叩き、受信ボックス内のメールを見ていきます

【Python】datetime.strptime で エラー 「unconverted data remains:」 が発生した場合

背景

Gmailの取得をPythonで開発していました。

取得できる受信日は以下のようなフォーマットで取得できます

Fri, 9 Nov 2018 20:37:10 

String型なのでDate型で色々したいわけですよ。

Pythonでは以下のような方法で「文字列→Date型(厳密には違いますが)」ができます。

lastTimeStr = 'Fri 9 Nov 2018 20:37:10 '
lastitime = datetime.datetime.strptime(lastTimeStr, "%a %d %b %Y %H:%M:%S")

※dateutilなど別の方法でもできます。

問題

以下のエラーが発生しました

ValueError: unconverted data remains:  

なぜでしょうか

原因と解決方法

  • 末尾にスペースが入っていたから
  • カンマが入っていたから

なので以下のように文字を消してあげると正常に動きます。

lastTimeStr = 'Fri, 9 Nov 2018 20:37:10 '
lastTimeStr = lastTimeStr.replace(",", "") #カンマ削除
if lastTimeStr[-1] == " ":
    lastTimeStr = lastTimeStr[:-1] #末尾にスペースが含まれていれば無視する

lastitime = datetime.datetime.strptime(lastTimeStr, "%a %d %b %Y %H:%M:%S")

ちなみに上記のエラー、remains:の後に問題になっている文字が表示されるようですが、 スペースなので、表示されていないように見えていたわけですね。

【JavaScript】JSON.parse() がエラーになる

事象

下記のようなエラーが発生する

JSON.parse: unexpected character at line 1 column 1 of the JSON data

文字列(JSON形式)をJSON.parse()でJSのオブジェクト型に変換する際に、 JSON構文エラーになっている模様。 構文チェッカーでチェックしても普通に問題はない。

JSON Pretty Linter - JSONの整形と構文チェック

環境

  • サーバー側(PHP)とフロントエンド側(JavaScriptJQuery)でAjax的なことをする
  • サーバー側にPOSTでアクセスすると、結果がJSONで返却される

以下JavaScript側サンプルコード

//サーバー上のファイルを削除する
function cancel(){
    
    if(!confirm('サーバー上のファイルを削除します。よろしいですか?')){
        return false;
    }

    //サーバーに受け渡し
    $.ajax({
        type: 'POST',
        url: 'php/cancel.php' <<<<< ★
    }).done((result)=>{
        showResultMsg(result);  
    });
}

function showResultMsg(json){
    
    console.log(json);
    var result = JSON.parse(json); // <<<<<<こいつがエラーを吐く
        
    if (result.result != 'SUCCESS'){
        //エラー時
        $('#result').attr('class','warning');                    
        console.log(result);
    }
    else {
        //成功時
        $('#result').attr('class','');                   
    }
    $('#result').text(result); 
}

原因

サーバー側のcancel.phpが原因。 他ファイルは全てUTF-8で統一されていたが、こいつだけUTF-8-BOMになっていた。 なので、'echo`して返却したJSONがおかしいことに なっていたっぽい

BOMとは

uxmilk.jp

感想

クソハマったのでつらかった。

【コマンドプロンプト(cmd)】AAを表示する

以下のようなスクリプトを作りましたが、その作り方

コマンドプロンプト(cmd.exe)で大きく「本番」と「検証」の文字を表示します · GitHub

f:id:gologius:20180616094423p:plain

画像変換ツールをDL

Unix系だとFIGlet というツールがapt-getで使えるようです。 Win版も配布されていましたが、Win10では使用できませんでした。

なので以下のツールを使用しました 文字絵エディターの詳細情報 : Vector ソフトを探す!

使い方は簡単なので割愛

編集

正直あとは適当に編集してやればできるのですが、私の方法を記載しておきます。

  • notepad++を使用します(そこそこのテキストエディタならなんでもよいですが)
  • Alt+Shitで行、列ごとの選択ができます
  • 先頭行にechoを追加します

まとめ

文字数少ないね ( ^ω^)・・・

【Python】 loggerのログが重複する

ログが再起動のたびに増えていくバグと、運命の出会いを果たしたのでメモします。

Pythonのログの取り方をお勉強しました

以下の記事で、ログの取り方について学びました。

ログ出力のための print と import logging はやめてほしい

超絶的に雑な解釈ですが、以下のようなものだと理解しました

  • print → エラーなのか警告なのかログなのか分からない。ログとる際に使うべきでない
  • logging → グローバル変数的な立ち位置。
  • logger → ローカル変数的な立ち位置。

※詳しくは元記事を参考にしてください・・・。

お、同じログがいっぱいでるぞ~

以下の記事で解決

uyamazak.hatenablog.com

再実行するたびにログが増えていく

上記の記事のようなログ用モジュールを作成して、実行する。

超具体的には、Anacondaで環境構築して、SpyderをIDEとして利用しており、 実行時にF5を使用すると、実行のたびにどんどん増える

# 一回目
[log info] ログテスト
# 二回目
[log info] ログテスト
[log info] ログテスト
#三回目
[log info] ログテスト
[log info] ログテスト
[log info] ログテスト

f:id:gologius:20180606214449p:plain

解決策

終了時に、killLoggers()のようにハンドラを全削除してあげます

from logging import getLogger, Formatter, FileHandler,StreamHandler, DEBUG, shutdown
from logging.handlers import RotatingFileHandler

loggers = {}

def getModuleLogger(moduleName):
    if moduleName is None:
        moduleName = __name__

    if loggers.get(moduleName):
        return loggers.get(moduleName)

    formatter = Formatter('[%(asctime)s | '
                          '%(name)s | '
                          '%(levelname)s] '
                          '%(message)s')

    streamHandler = StreamHandler()
    streamHandler.setFormatter(formatter)
    streamHandler.setLevel(DEBUG)

    fileHandler = RotatingFileHandler("download.log", maxBytes=5000, backupCount=3)    
    fileHandler.setFormatter(formatter)
    fileHandler.setLevel(DEBUG)

    logger = getLogger(moduleName)
    logger.setLevel(DEBUG)
    logger.addHandler(streamHandler)
    logger.addHandler(fileHandler)

    logger.propagate = False
    loggers[moduleName] = logger

    return logger

def killLoggers():

    for l in loggers:
        logger = loggers.get(l)
        for h in logger.handlers:
            logger.removeHandler(h)

    shutdown()

    return

これで重複出力はなくなります。

以上。

Selenium+Pythonにて、アラートが出る新規ウインドウに遷移したい

※Teratailで私が質問した問題を、結局自分で解決した際のメモです

Python - Selenium+Pythonにて、アラートが出る新規ウインドウに遷移したい(125969)|teratail

※2018/06/03追記

どうやらヘッドレスモード(GUI、ウインドウを表示しないモード)にすると、いちいち前のウインドウに遷移する必要がなくなるようです。 記事の最期の方に記載しておきます。

※2018/06/27追記 ↑とか書いちゃったのですが、実際には

  • 新規ウインドウが出るようなボタンをクリック
  • (新規ウインドウでアラートが出るまで待つ。)
  • switch_to_window()で新規ウインドウをアクティブにする

の流れで処理すると動くようです

やりたいこと

  • リンクをクリックする→新規ウインドウが開く(※ただしウインドウが開くと即アラートが出る)

という動きをSeleniumPythonで実現したいと考えています。

実際にはその先のボタンを押したいのですけどね。

下記のリンクで例を見ることが出来ます。

テストページ | gologiusのページ

問題点

調べているとどうも

  • アラートを消すためには、アラートが出ているウインドウに遷移しなければならない
  • ウインドウに遷移するためには、アラートを消さなければならない

と、デッドロックみたいな状況になっているようです。

以下ソースの★1、★2の部分です。

ソース

# -*- coding: utf-8 -*-
"""
Created on Wed Mar  7 22:16:01 2018

@author: 
"""

from selenium import webdriver
from selenium.webdriver.common.alert import Alert
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException

import datetime
import traceback

###########################################################################
def waitOpenWindow(driver, beforeWinNum, maxWaitSec = 10):
    """
    指定された時間まで、ウインドウが開くのを待つ
    """

    beginTime = datetime.datetime.now()
    endTime = beginTime + datetime.timedelta(seconds=maxWaitSec)

    while(datetime.datetime.now() <= endTime):

        afterWinNum = len(driver.window_handles[-1])
        if afterWinNum == beforeWinNum:
            continue
        elif  afterWinNum > beforeWinNum:
            #ウインドウ数が増える=新規ウインドウが開いた
            return True , "指定時間内に新規ウインドウが開きました"
        else:
            return False, "ウインドウが外部から閉じられた可能性があります"

    return False, "指定時間内に新規ウインドウが開きませんでした"

###########################################################################
def waitAlert(driver, maxWaitSec = 10):
    """
    指定された時間まで、アラートが開くのを待つ    
    """

    try:
        wait = WebDriverWait(driver, maxWaitSec)
        wait.until(expected_conditions.alert_is_present())
    except TimeoutException as time_e:
        return 0, "アラート表示待機中にタイムアウトしました"
    except Exception as e:
        print(traceback.print_exc)
        return -1, "アラート表示待機中に予期しないエラーが発生しました"

    return 1, "アラートが表示されています : " + Alert(driver).text

###########################################################################
#関数定義
def access():

    try :
        print("begin process")

        driver = webdriver.Chrome("chromedriver_win32\chromedriver.exe")
        driver.implicitly_wait(10)
        driver.get("https://gologius.github.io/test.html")

        print("画面名", driver.title)
        winNum = len(driver.window_handles)

        #リンクをクリック
        driver.find_element_by_link_text("javascriptによる小ウインドウ表示").click()

        #ウインドウ表示待機     
        print("画面遷移待機中")
        waitResult, msg = waitOpenWindow(driver, winNum)
        if waitResult == False:
            return False, "ウインドウ表示に失敗しました"
        print(msg)

        #新規ウインドウに遷移する ★1
        win = driver.window_handles[-1] #リストの最後=最後に開いたウインドウ
        driver.switch_to.window(win)
        print("画面名", driver.title)
        winNum = len(driver.window_handles)

        #アラート待機 ★2
        print("アラート表示待機中")
        resultCode, msg = waitAlert(driver)
        if resultCode == 1:
            Alert(driver).accept()
        print(msg)

        print("end process")

    except:
        print("予期しないエラーが発生しました")
        print(traceback.format_exc())
        #ログ出力
        driver.quit()

    return True, "アクセス成功"

###########################################################################
#実行

result, msg = access()
print(msg)

解決方法

ボタンクリック後、「遷移前のウインドウ」にdriver.switch_to_window(win)で遷移すると、 その後のアラート処理、ウインドウ遷移が実行できるようです

win1 = driver.window_handles[-1]

driver.find_element_by_link_text("javascriptによる小ウインドウ表示").click()

#遷移前のウインドウに遷移する
driver.switch_to_window(win1)

#アラート処理

#ウインドウ遷移
win2 = driver.window_handles[-1]
driver.switch_to_window(win2)

理由は不明ですが、推測するに、新規ウインドウが表示される際に、

  • 新規ウインドウが開く際に、そのウインドウがアクティブになろうとする
  • Alertのせいでアクティブになりきれない →無限ループの原因
  • 遷移前のウインドウをアクティブに(switch_to_window)する
  • Alert(driver).accept()なども実行可能になる

みたいな気がします。


※2018/06/03追記↓

ヘッドレスモードで起動すると、この話は無視できる・・・?

        chromeOptions = webdriver.ChromeOptions()
        chromeOptions.add_argument('--headless') #ブラウザのGUIが表示されなくなる 
        driver = webdriver.Chrome("hogehoge/chromedriver.exe", chrome_options=chromeOptions)

雑感

上記のような不具合を抜きにしてもSelenium便利。

ちなみに検証用サイトはgithubioで作成しました。 テストページ | gologiusのページ

JavaScript使えるのでこういう際にサクッと作れるので便利。 ただJSの書き方を忘れていたのはナイショ...(/ω\)

【Akeytsu】 Reverse Footがうまく設定できない

Akeytsuを使用していて発生した問題についてメモ

Reverse Footとは

f:id:gologius:20180504170536p:plain

リファレンスの動画見た方がよくわかるのですが、足用のIKです。

問題発生

  • 片足には設定できるがもう片足に設定できない
  • 挙動がおかしい(膝を曲げて頂きたいのですが、曲げてくれません)

f:id:gologius:20180504171111p:plain

解決方法

これ、つま先のボーン(足のボーンの末端)に対して設定しないといけません。

上記二つの問題はこれで解決できます。やったね!

f:id:gologius:20180504171300p:plain

静止してくれない場合

f:id:gologius:20180504173841g:plain

上記のようになる場合、対象のフレームすべてにおいて、右クリックでリングを赤にすると解消されます。

f:id:gologius:20180504173906g:plain

まとめ

つまずいた点をメモりました。 Akeytsuは日本語情報が少ないので、小さなことでもメモっていきたいですね。 というかここ一年でこのブログ以外に日本語情報がないのですが・・・

また、以下の本が簡素で分かりやすい説明になっています(図が異様に雑な気がしますが・・・)。

おしまい。