seleniumのheadlessでプロファイルを使う際のエラー解消法

忘却メモ。
chromeでseleniumでヘッドレスモードを指定し、プロファイルを設定しようとしたら以下のエラーが出た。
解消法を探すのになかなか苦労したため、ここに記す。
■エラー発生した際のサンプルコード

options.add_argument('--headless')
options.add_argument('--user-data-dir=user')
options.add_argument('--profile-directory=profile')
driver = webdriver.Chrome(executable_path=chromeDriverPath, options=options)

■発生したエラーコード

Traceback (most recent call last):
  File "~\hogehoge.py", line 40, in __init__
    driver = webdriver.Chrome(executable_path=chromeDriverPath, options=options)
  File "~\webdriver.py", line 81, in __init__
    desired_capabilities=desired_capabilities)
  File "~\webdriver.py", line 157, in __init__
    self.start_session(capabilities, browser_profile)
  File "~\webdriver.py", line 252, in start_session
    response = self.execute(Command.NEW_SESSION, parameters)
  File "~\webdriver.py", line 321, in execute
    self.error_handler.check_response(response)
  File "~\errorhandler.py", line 242, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.WebDriverException: Message: unknown error: DevToolsActivePort file doesn't exist

この問題を解消するには下記のオプションを追加する。
DevToolsとやり取りするにはポートを指定してやる必要がある。

options.add_argument("--remote-debugging-port=9222") 

少しversionが古いが、下記の公式Documentsが参考になった
ヘッドレス Chrome ことはじめ

以上

PythonでChromeWebDriverを自動更新するselenium運用

Selenium/Appium Advent Calendar 2019の19日目の記事です。

seleniumを利用してしばらく立つと、ある問題に直面します。
chromeのメジャーバージョンアップされると、chromeWebDriverが不一致でエラーを吐く・・・
以前は一度更新すれば3versionくらいは利用できたそうですが、
最近ではメジャーバージョンがあがるとアップデートが必要です。

楽をしたくてseleniumを使うのに面倒をみるのは大変なので、自動更新をしてみます。
つまり、seleniumという便利なクローラーを使うために、seleniumを使わずにクローリングします。
(seleniumの記事なのにseleniumを使わない・・・!?)

コードはPythonで記載をしますが、他言語でもかけるように解説をしっかり入れていきます。
以下のインポートをしている前提で話を進めていきます。

import os
import re
import zipfile
import urllib.request
from lxml import html

※lxmlはpipで取得しておく必要があります。

■処理の流れ
1. ローカルのChromeのバージョンを確認します。
2. バージョンが異なる場合、適切なドライバを調べます。
3. ファイルをダウンロード・解凍し、フォルダに配置します。
4. 適切なドライバでseleniumを起動!

■サンプルコード
GitHub-getChromeWebDriver
※上記コードはMIT LICENCEです。
※この記事のコピペや参考はフリーです。

【1】ローカルのChromeのバージョンを確認します。
Chromeはコマンドから直接バージョンを取得する方法はありません。
なので、Applicationが配置されている場所に、バージョン名のついたフォルダがあることを利用し、
現在のバージョンを取得してみます。
また、Chromeはバージョンアップの際、次のバージョンのディレクトリを作成し、
次回起動時に変更+旧ディレクトリの削除を行うため、ディレクトリが複数ある場合があります。
なので、ディレクトリの中でもっとも新しいバージョンを確認します。

# インストールしている場所を変更していなければディレクトリはここに作成されている。
files = os.listdir(r"C:\Program Files (x86)\Google\Chrome\Application")
# ChromeApplicationフォルダ配\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\下にあるフォルダをフルパスで取得
filesDir = [f for f in files if os.path.isdir(os.path.join(r"C:\Program Files (x86)\Google\Chrome\Application", f))]

# 最大値を取るための変数を用意 
version = 0
for folder in filesDir :
     # 正規表現でメジャーバージョンを取得
     filePattern = re.compile(r'([0-9]+(?=\.))')
     result = filePattern.match(folder)
     # 取得できていたら、最大値を保存
     if result:
          version = max(version,int(result.group()))

これで次に起動する際のメジャーバージョンを取得できました。

【2】バージョンが異なる場合、適切なドライバを調べます。
現在のドライバのバージョンを設定ファイルなどで保持しておき、現在のドライバのバージョンと
Chromeのバージョンが不一致の場合、ドライバのダウンロードを行います。

Chromeドライバの最新版は下記からダウンロードできます。
https://chromedriver.chromium.org/downloads

ただし、毎回同じバージョン毎にパスが変わるので、直接ファイルを取得します。
気をつける必要があるのは、ここのDriverはローカルよりも新しいバージョン用のものが置かれていることがあり、
自分にあったドライバを選ぶ必要があります。
※おそらくディベロッパー向けや地域による差だと思います。

ファイルを取得後、Xpathで対象リンクを洗い出します。
※もっとモダンなやり方があればおしえてください。

# ChromeDriver配信ページを取得
url = 'https://chromedriver.chromium.org/downloads'
with urllib.request.urlopen(url) as f:
    htmltext = f.read().decode('utf-8')

# XPATHで対象となる項目を取得
title = html.fromstring(htmltext).xpath("//table[@class='sites-layout-name-one-column sites-layout-hbox']/tbody/tr/td/div/div/ul/li")
chromeDriverVersionList = title[0].xpath("li") 
mi = "0"
for version in chromeDriverVersionList :
    # リンクを取得
    ccc = version.xpath("a")
    # 要素が空の場合、スキップ
    if(len(ccc)==0):
        continue
    chromeDriverStatus = ccc[0].text
    # メジャーバージョンを取得
    m = re.search('(?<=ChromeDriver )[0-9]*', chromeDriverStatus)
    # ローカルのバージョンと一致するか確認、一致したら結果を保存
    if(str(m.group()) == str(version)) :
        versionOfMajor = re.search('(?<=ChromeDriver ).*', chromeDriverStatus)
        Driverversion = str(versionOfMajor.group())

【3】ファイルをダウンロード・解凍し、フォルダに配置します。
Windows用のDriverはZIPで圧縮されているため、ダウンロード後、解凍して配置します。

# version情報からURLを決め打ちでダウンロード
furl = "https://chromedriver.storage.googleapis.com/" + Driverversion + "/chromedriver_win32.zip"
urllib.request.urlretrieve(furl, "temp.zip")
with zipfile.ZipFile('temp.zip') as zipF:
    zipF.extractall("ChromeDriver配置先")

# 任意の設定ファイルなどにDriverのversionを記録(毎回取得しないようにするため)

これをseleniumを利用したアプリケーション起動前に実行することで、バージョン違いの問題はクリアされます。

【4】本命のSeleniumアプリを起動

以下で色々書き加えたソースコード公開しています。
GitHub-getChromeWebDriver

Pythonでwindows通知(トースト・バルーン)を出す方法

Python Advent Calendar 2019の14日目の記事です。

速攻作れるだろ~と思っていたら、意外とめんどくさかった。
なにより日本語の標準モジュールだけで実現する解説記事が多分ない。(多分)

色々調べたので、忘れないうちに未来の自分と同じことをしたい人へ共有

作り方なんていいんだよ!ソースくれって人は下記のソースをぶっこんで使ってどうぞ。
GitHub-easyToast
ちなみにLicenceはMITLicenceです。
あ、スターください。

また、今回勉強しつつ調べてまとめているので、
もし間違っているところがあればツイッターなどで教えて下さい

今回は利用する標準モジュールのみで実装を行う。

■トーストとは?
古すぎないWindowsでなにかApplicationからのお知らせがあるときに
画面右下に出てくる通知のようなもの。
バルーンと呼ばれることもあるが、厳密にはこれはトーストである。
※バルーンは小アイコンからの吹き出し

■トーストを出すまでの流れ
1. Pythonでctypeを利用する
2. APIにあった構造体を用意する
3. WindowsAPIを利用できるように読み込む
4. LoadImageWで画像アイコンを利用できるようにする
5. Shell_NotifyIconWで通知申請を行う

なにがなんのことかわからないと思うので、順番に説明する。
サンプルコードが何度か登場するが、基本的には以下のimportを行っている前提で話をすすめる。

import os
import ctypes
from ctypes import Structure, windll, sizeof, POINTER, WINFUNCTYPE
from ctypes.wintypes import ( 
    DWORD, HICON, HWND, UINT, WCHAR, WORD, 
    BYTE, LPCWSTR, INT, HINSTANCE, BOOL, HANDLE
    ) 

■Pythonでctypeを利用する
Windowsでトーストを出すにはWindowsAPIからバルーンを出すリクエストを出す必要がある。
WindowsAPIを叩く際にbathなどを経由してもいいが、PythonからWindowsAPIを叩く良いやり方はctypeを使う

ctypeとはPythonでCの関数ライブラリを使うことが出来るものだ。
簡単なコードでとりあえずctypeを試したいのであれば、以下の記事がとても参考になった。
Python から Windows API を呼び出す – MessageBox の例
ctypeの詳細を知りたい方は以下をどうぞ。
ctypes — Pythonのための外部関数ライブラリ

■APIにあった構造体を用意する
ctypeを使って、まずはAPIを叩くための構造体を準備する。
ctypesのStructureを継承したクラスで構造体を定義します。

最初聞いたときなんのこっちゃと思いましたが、
ざっくりいうと、WindowsAPIになんらかの動作を指示する際に渡すデータの集まり(タイトルとか表示方法とか)には
事前に決められた構造体(何番目になんの型のなんて名前のデータが入るなどの定義)を用意する必要があり、
後述するWindowsAPIの仕様書(英語)を片手に準備する必要があります。

今回、トーストを出すために作成する必要がある構造体は以下

class GUID(Structure):
    _fields_ = [
        ("Data1", DWORD),
        ("Data2", WORD),
        ("Data3", WORD),
        ("Data4", BYTE * 8)
    ]

class NOTIFYICONDATAW(Structure):
    _fields_ = [
        ("cbSize", DWORD),
        ("hWnd", HWND),
        ("uID", UINT),
        ("uFlags", UINT),
        ("uCallbackMessage", UINT),
        ("hIcon", HICON),
        ("szTip", WCHAR * 128),
        ("dwState", DWORD),
        ("dwStateMask", DWORD),
        ("szInfo", WCHAR * 256),
        ("uVersion", UINT),
        ("szInfoTitle", WCHAR * 64),
        ("dwInfoFlags", DWORD),
        ("guidItem", GUID),
        ("hBalloonIcon", HICON),
    ]

具体的なやり方などは下記が参考になった。
Python: ctypesパターン集

GUID と NOTIFYICONDATAW ってのを使うんだなーくらいで今は大丈夫。

■WindowsAPIを利用できるように読み込む
WindowsAPIを読み込んで、引数の定義と戻り値の定義を行う
今回利用するAPIはLoadImageとNotifyIconだ。
▼サンプルコード

LoadImageW = windll.User32.LoadImageW
LoadImageW.argtypes = [HINSTANCE, LPCWSTR, UINT, INT, INT, UINT]
LoadImageW.restype = HANDLE
    
Shell_NotifyIconW = windll.Shell32.Shell_NotifyIconW
Shell_NotifyIconW.argtypes = [DWORD, POINTER(NOTIFYICONDATAW)]
Shell_NotifyIconW.restype = BOOL

API名の最後にWがついているが、WindowsAPIは最後にWがつくのと、Aがつくものがあり、
利用している文字コードなどの違いがある。今回はWを利用している。

必要な引数等はMicrosoftのドキュメントが参考になった。
Shell_NotifyIconW function
LoadImageW function
NOTIFYICONDATAW structure

ひとまず、これでWindowsAPIが利用可能になった。

■LoadImageWで画像アイコンを利用できるようにする
バルーンで利用しているアイコンは.icoといった拡張子の画像ファイルだ。
変換サービスやフリー素材などがあるので、適当な画像を用意しよう。
また、アイコンなしでトーストを出す方法も記載するため、この項目は飛ばしても構わない。

hicon = LoadImageW( None, icon, 1, 0, 0, 0x00000050)

これはLoadImageWを呼び出すコード。
引数の詳細は上記Microsoftのドキュメントに記載があるが、ざっくりと解説。

・第一引数
画像を使う場合はNoneを指定する(nullを指定したことになる)
・第二引数
今回は画像のファイルパスを指定する。
・第三引数
読み込む画像の形式を指定する。
アイコンは1を指定。試していないけど0だとビットマップが読み込める?
・第四引数
アイコンの幅を指定する。0なら自動
・第五引数
アイコンの高さを指定する。0なら自動
・第六引数
オプションフラグを設定する。

16進数で記載されていて最初はよくわからないかもしれないが、
WindowsAPIでは有効にする設定の数値を合計したものを指定する。
例えば、0x00000001の設定と0x00000002の設定を有効にするのなら、0x00000003を指定する。(16進数なので繰り上がりに注意)
足し算でなく、ビット演算で合算してもよい。 0x00000001 | 0x00000002
どのような設定値があるかは上記マニュアルを確認する。

今回はLR_DEFAULTSIZE(0x00000040)とLR_LOADFROMFILE(0x00000010)を指定している。
成功すればイメージのハンドルが返却される。

5. Shell_NotifyIconWで通知申請を行う
まずはWindowsAPIに渡すデータを作成する。
▼サンプルコード(画像表示あり)

flags = 0x00000016
infoflag = 0x00000034
mem_size = sizeof(NOTIFYICONDATAW)
notify_data = NOTIFYICONDATAW(
        mem_size, 0, os.getpid(), flags, 0, hicon, "tips", 
        0, 0, "message", 4, "title", infoflag, GUID(), hicon
)

▼サンプルコード(画像表示なし)

hicon = None
flags = 0x00000014
infoflag = 0x00000000
mem_size = sizeof(NOTIFYICONDATAW)
notify_data = NOTIFYICONDATAW(
        mem_size, 0, os.getpid(), flags, 0, hicon, "tips", 
        0, 0, "message", 4, "title", infoflag, GUID(), hicon
)

さて、とうとう通知を出す。
それぞれ何をしているかざっくり解説する。
※詳細は上記Microsoftのドキュメントを確認されたし

第一引数
作成した構造体のメモリサイズを指定する。
ctypeのsizeofで取得が可能
第二引数
今回は13引数を指定するため、読み飛ばされる
第三引数
実行しているPIDを取得する。上記サンプルでは自身のPIDを取得している
第四引数
どんな情報があるかやどんな表示をするかなどを指定する。
今回は NIF_ICON,NIF_TIP,NIF_INFOを有効にした。
画像を利用しない場合はNIF_TIP,NIF_INFOを有効にする。
※詳細は上記Microsoftのドキュメントを確認されたし
第五引数
LoadImageWで生成したハンドルを指定する
画像表示を行わない場合、NONEを指定
第六引数
アプリ名や詳細の記載を行う。
トーストのアプリ名が以前はここの指定内容になっているような記載をみたが、特に反映されなかった。
第七引数
画像の情報の取り扱い?
利用しないので0を指定する
第八引数
0を指定する
第九引数
通知に表示するメッセージを指定
第十引数
シェルのversionを指定する。
第十一引数
通知のタイトルを指定する。
第十二引数
利用するアイコンの設定を行う。
0x00000001や0x00000002を加算するとシステムに用意されたアイコンを利用できる
他にもいくつかあるので、上記のマニュアルを参照
今回はNIIF_USER,NIIF_NOSOUND,NIIF_LARGE_ICONが有効になっている。
通知時に音を出したいなら0x00000010を引く。
第十三引数
GUIDを与える。なぜ設定するかはドキュメント読むだけだとわからなかった。
第十四引数
LoadImageWで生成したハンドルを指定する
画像表示を行わない場合、NONEを指定
第五との違いはタスクバーのアイコンか、トーストのアイコンかの違い。
今回はどちらも同じものを設定する。

データを渡す。
▼サンプルコード

Shell_NotifyIconW(0x00000000, notify_data)
Shell_NotifyIconW(0x00000002, notify_data)

第一引数の0は表示、2は通知を汚さないように削除している。

以上でトーストをPythonから利用できる。
きっと他のWindowsAPIにも応用できるはずだ。

最後にトーストを出す便利ソースを紹介する。
GitHub-easyToast
ぜひスターを(ry

find grepコマンドするとき文字化けファイルを取り除く方法

サーバー上でfindコマンドを叩き、grepしてxargsで渡す・・・
そんなコマンドを叩くときにファイル名が壊れたファイルが邪魔をする。
そんなファイルを回避するのに手こずり、応急処置を思いついてからは作業がさくサクサク進んだのでメモ。

応急処置なので、場合によっては弾きたいファイルが弾ききれないかもしれないし、除くたくないファイルが除かれるかもしれない。

※もっといいやり方あったら教えてください。

find . | grep .ini | xargs cat | grep root

こんなコマンドを叩くとき、文字化けしたり、文字コードの異なるファイルがいると途中で止まってしまう。
なので以下を書き足す。

find . | grep .ini | grep -v ‘[亜-熙]’ | xargs cat | grep root

ファイル名が想定では漢字が含まれないときにしか使えないし、化け方次第では漏れる。
しかし、私の環境ではなんとかなった。

ちなみに ‘[亜-熙]’は
第1水準と第2水準の漢字を指定している。文字化ければ大体漢字が入るだろうという雑な対応。
ちなみに文字コードがCP932なら、纊-黑を追加するといいらしい。

c++で文字と数字の変換(char-string-int)

競プロのb問題とかといていて、よく忘れるため、自分用のメモ

■数字→文字(int→string)

string s = to_string(int n);

ちなみにto_stringではMinGW系(clangdで利用する場合も含む)32bitのものだとバグがあり使えない。
対応することも可能だけれど、このご時世64bitを利用しておきましょう。

■文字→数字(string→int)

int n = stoi("2223");

ベストプラクティスを知らないが、大体必要な時はこれで対処している。

■文字→数字(cahr→int)

char a = '1';
int n = a - '0';

非常によく使う。string型を配列のように使って1文字取り出す場合も、
char型として扱われるため、頻出。ググるとatoiでやれと言われるが環境によっては使えなかったりしていつも苦労してた。
charは文字のまま使うとa-zなども含めて何番目にある文字か?といったintとしてつかわれるため、0-9に並んでいるので頭の0の番号を引くと、0-9に変換できる。

ubuntuでeuc環境(Solaris)にsshするときの文字化け対策

Windows10でWSLでかんたんにネイティブLinux環境を利用できるようになった。
TeraTermからログインしていた環境にはいるのを代替をしようとしていたが、一つの問題にぶつかりしばらくsshでは利用していなかった。

その問題とは、EUC環境に置かれているsjisのファイルを開いたときに、上手く表示できないやつ。

いや、当然といえば当然なんですけど、TeraTermから開いていたときは文字化けしつつも表示してくれて、iconvなり、ターミナルの設定なりで開きなおしていたんですよ。
それがubuntuからだと表示が死んで以降操作できなくなったんですね。
うっかりsjisファイル参照すると死亡するのはいかんせん使いにくかったんですけど、ubuntu側にコマンド一ついれるだけでおおよそのぞみ通りの動作をするようになりました。

cocotコマンドっていうやつです。

それで、リモート先をEUC-JP、ホスト側をutf-8にして繋げば見事に動作しました。
※導入方法や設定方法はいくらでもネットにあるのでググっていただいたほうが確実です。

ただ、cocot利用で接続を行うと今までLinux環境にはいるときに使っていたsshpassが上手く動かなくなるんで、そこだけはまだ未解決・・・

これでvimターミナル生活 to EUC環境が快適にできるぞー!

競プロテンプレートファイルメモ

ヘッダーにいろいろ書いて楽々コーディング
必要に応じて増やすけど、現在はこんな感じです。

#include<iostream>
#include<algorithm>
#include<climits>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<string>
#include<cstring>
#include<vector>
#include<stack>
#include<queue>
#include<set>
#include<bitset>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

#define mod 1000000007LL
#define eps 0.0001
#define pi acos(-1)
#define INF 10000000000000000

#define rep(i, n) for(i = 0;i < n;++i)
#define rep1(i, n) for(i = 1;i < n;++i)
#define pri(x) cout << (x) << "\n"
#define pri2(x, y) cout << (x) << " " << (y) << "\n"
#define pri3(x, y, z) cout << (x) << " " << (y) << " " << (z) << "\n"

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    // ---------------------
    // コーディング開始
    // ---------------------
    string n;
    cin >> n;

    return 0;
}

vim-lspが重かったのを解消した話

競技プログラミングに参加するにあたって、vim-lspを導入したんですけど、
デスクトップでは軽いのにノートPCだとなんか重く、タイピング本気でやると応答がなくなる始末でした。
clangdの動作が重いのかなーとかそんなこと考えて調べてました。
でも一応ノートPCもCPUi5だし、インクルードファイルの内容によってはHDD性能とかが足引っ張る事例も見かけはするものの、SSD・・・

エラーチェックオフにするといいよーと言われ、オフにしても変わらず。

色々切り分けてみると単純なことで解決しまいした。

重かった原因はこいつ↓
let g:lsp_log_file = expand(‘~/vim-lsp.log’)
let g:lsp_log_verbose = 1

lspでなにかするとログを残してくれるデバッグオプション。
そりゃがりがりioしてたら遅いよね、と。

私の環境だと何故か
let g:lsp_log_verbose = 0
にしてもログがはかれていました。
g:lsp_log_file も指定しないとエラーはいてしまうようなので、
let g:lsp_log_file = “”
“let g:lsp_log_verbose = 1
とすることで解決。

ノートPCでも爆速動作するようになりました。

■雑談
エラーチェックで段がずれる問題ですけど、aleだとエラー出す列を出しっぱなしにするオプションがあるらしい。
kaoriya番は2019/8/25の最新バージョンでは対応していないけれど、最新のVimではエラー表示をnumber列にだすようにかえるオプションがあるので、
kaoriya版が更新されたら設定いじってみるつもり

oracleのUNIONでORA-01790が出まくった話

不慣れにもポチポチoracleのSQLを書いていたとき、ある壁が立ちはだかった。
もし、同じ問題に躓いている人の助けになれば幸いである。

あるテーブルとあるテーブルをUNION ALLし、総件数を求めるというもの
※ここでは最小限のSQLを記載している

SELECT count(*) FROM
(
SELECT
USER_NAME
FROM
SCHEMA.T_USER_MASTER
) UNION ALL (
SELECT
USER_NAME
FROM
SCHEMA.T_USER_MASTER_OLD
)

上記を実行すると下記エラーが発生するのだ。
ORA-01790 式には対応する式と同じData型を持つ必要があります

はて、さてはテーブル定義が違うなと調べてみても全く同じ。
USER_NAMEを別の列名に変更すると出来るものとできないものが・・・うむむ

いろいろいじってみて
SELECT count(*) FROM
(
SELECT
USER_NAME
FROM
SCHEMA.T_USER_MASTER
) UNION ALL (
SELECT
USER_NAME
FROM
SCHEMA.T_USER_MASTER
)
に変更しても同様のエラーが起きことが発覚。そんな馬鹿な。
そこで一つ思い付き試すとしっかりと通った。

結論としては以下のようにすると解決する。

SELECT count(*) FROM
((
SELECT
USER_NAME
FROM
SCHEMA.T_USER_MASTER
) UNION ALL (
SELECT
USER_NAME
FROM
SCHEMA.T_USER_MASTER_OLD
))

UNION ALLの上側のSQLがどこまで対象であるか明示されていなかった。
詳しい優先度はもっと詳しい人にお願いするとして、今回のケースでは
SCHEMA2.T_USER_MASTERのUSER_NAME(char型)とcount(数値型)をUNIONしようとしていたために発生していたようだ。