nprogram’s blog

気ままに、プログラミングのトピックについて書いていきます

WPF開発復習

WPF開発復習

WPF開発技術を復習します。(かなり忘れているため・・・)

参考サイト

jupyter Notebookのコードをexe化する方法 [Anaconda3環境]

jupyter Notebookのコードをexe化する方法 [Anaconda3環境]

jupyter Notebookのコードをexe化する方法を記録します。

手順

  1. まず、Jupyter Notebookを開き、以下のイメージのように、メニューのFile⇒Downlaod as ⇒ Python (.py)を選択してください。

  1. 次に、Anaconda Promptから、conda install pyinstallerを実行して、pyinstallerパッケージをインストールします。

  2. pyinstaller 実行したいPythonファイル --clean -F

2つあるオプションは以下の目的で付けています。 --clean : 前回ファイル削除 -F : 実行時に必要なファイルが1つのexeファイルに集約される

[例] pyinstaller Google_Search_Using_XPath.py --clean -F

作成されたexeファイルは、exeファイルはpyinstallerを実行したカレントディレクトリに生成されるdistフォルダ内に出力されます。

注意点

なお、chromedriver.exeファイルを使用する場合は、作成したexeファイルから参照可能なパスである必要があります。同じ階層に置くとよいでしょう。

参考ページ

あとがき

比較的簡単にexe化を実行することができました。

なお、pyinstallerによって作成されたexeファイルを実行するとコンソールが表示されます。これを消去するのはPythonでは難しそうです。C#なら可能のようです。

python - HeadlessモードでChromeDriverを起動したときにコンソールが表示されないようにする - スタック・オーバーフロー

リンク

How to Package Python Apps With PyInstaller

SeleniumをEXEで動かす - Qiita

Python + Selenium + ChromeでGoogleの検索を自動化する [非headlessモード][XPath]

はじめに

本記事は、Python + Selenium + ChromeGoogleの検索を自動化する手法について記載したものです。

今回は、XPathを使用して、検索を行います。

XPathとは

XPathXML文章中の要素、属性値などを指定するための言語です。 XPathではXML文章をツリーとして捉えることで、要素や属性の位置を指定することができます。 HTMLもXMLの一種とみなすことができるため、XPathを使ってHTML文章中の要素を指定することができます。

(※記事記載時点(2019/10/17)では本記事のコードが動作することを確認していますが、Googleのデザイン等が変われば動作しなくなる可能性があります。ご了承ください)

環境

環境は以下のとおりです。パッケージの管理はAnacondaで実施しています。pythonコードの実行はjupyter Noteboookを使用しております。

  • OS : Windows 10 Pro
  • conda version : 4.7.12
  • conda-build version : 3.18.9
  • python version : 3.7.4.final.0
  • selenium : 3.141.0
  • ChromeDriver : 77.0.3865.40

XPathを用いた検索方法

XPathを用いた検索方法は、以下の記事で分かりやすく説明してあります。

本記事のサンプルコードでは、Full XPath(絶対XPathパス)を用いてWebページの要素を取得しています。

XPathを用いれば、以下のように、要素にID指定やclass名指定がなくても、テストしたいページのタグ構成から要素を掴むことが可能です。

element = driver.find_element_by_xpath("/html/body/form[1]")

XPathを用いて、要素を操作する処理は何度も繰り返すと思いますので、関数化するとよいと思います。

# XPathを用いて、ボタンをクリックする
def click_button_using_XPath(element_XPath : str):
    
    element = WebDriverWait(driver, MAX_WAIT_TIME_SEC).until(
        EC.presence_of_element_located((By.XPATH, element_XPath))
    )
    
    element.click()
    
    return element

サンプルコード

Googleの検索を自動化したコードです。検索文字列を入力してEnterキーを押した後、ニュースボタンを押します。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

MAX_WAIT_TIME_SEC = 100


# ChromeDriverのパスを引数に指定しChromeを起動
CHROME_DRIVER_PATH = "chromedriver.exe"
options = Options()

driver = webdriver.Chrome(executable_path=CHROME_DRIVER_PATH, options=options)
driver.maximize_window() # 画面表示を最大化する

# 指定したURLに遷移する
web_site = 'https://www.google.com/'
driver.get(web_site )


# XPathを用いて、ボタンをクリックする
def click_button_using_XPath(element_XPath : str):
    
    element = WebDriverWait(driver, MAX_WAIT_TIME_SEC).until(
        EC.presence_of_element_located((By.XPATH, element_XPath))
    )
    
    element.click()
    
    return element

    
# XPathを用いて、テキストボックスに文字列を入力する
def input_textbox_using_XPath(element_XPath : str, send_code : str):
    
    element = WebDriverWait(driver, MAX_WAIT_TIME_SEC).until(
        EC.presence_of_element_located((By.XPATH, element_XPath))
    )
    
    # 検索テキストボックスをクリアする
    for item in range(0,100) :
        element.send_keys(Keys.BACK_SPACE)
                                 
    element.send_keys(send_code)
    
    return element

    
# テキストボックスに検索ワードを入力して、直後にEnterキーを押す
input_textbox_using_XPath("/html/body/div/div[4]/form/div[2]/div[1]/div[1]/div/div[2]/input", "test").send_keys(Keys.RETURN)


# ニュースボタンを押す
click_button_using_XPath("/html/body/div[7]/div[3]/div[5]/div/div/div[1]/div/div/div[1]/div/div[2]/a")


driver.close() # ウィンドウを閉じることが出来ます。閉じることが出来るのは引数にブラウザのバイナリを指定したインスタンスウインドウのみ

driver.quit() # 「quit」を実行することで全てのウィンドウを閉じることが出来ます。

リンク

Seleniumを安定稼働させるために行うべき3つの設定(Headlessモードにも対応) | たぬハック

Python + Selenium + ChromeでGoogleの検索を自動化する [非headlessモード]

はじめに

本記事は、Python + Selenium + ChromeでGoogleの検索を自動化する手法について記載したものです。

ChromeのWeb操作は、ChromeDriverを用います。

ChromeDriverを導入する方法として、以下の2つの方法がありますが、本記事は前者の方法を使用します。

  • バイナリを直接ダウンロードする
  • pipコマンド(pip install chromedriver-binary)でインストールする

以下のサイトから入手できます。Google Chrome Versionと合わせる必要があるので、ダウンロードするバージョンについては注意してください。

https://chromedriver.chromium.org/

また、ChromeDriverは非headlessモードで動作させます。

(※記事記載時点(2019/10/15)では本記事のコードが動作することを確認していますが、Googleのデザイン等が変われば動作しなくなる可能性があります。ご了承ください)

環境

環境は以下のとおりです。パッケージの管理はAnacondaで実施しています。pythonコードの実行はjupyter Noteboookを使用しました。

  • OS : Windows 10 Pro
  • conda version : 4.7.12
  • conda-build version : 3.18.8
  • python version : 3.7.3.final.0
  • selenium : 3.141.0
  • ChromeDriver : 77.0.3865.40

GoogleのWebサイトを開く

以下のコードでGoogleのWebサイトを開くことができます。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.keys import Keys
from time import sleep

options = Options()
driver = webdriver.Chrome(chrome_options=options, executable_path="F:\\tool\\ChromeDriver_77_0_3865_40\\chromedriver.exe")
web_site = 'https://www.google.com/'
driver.get(web_site )

from selenium.webdriver.common.keys import Keysは、文字を削除、入力、エンターキーの実行を行うためのキー操作を可能にするための宣言です。

from time import sleepはスリープさせるための宣言になります。

特に、webdriver.Chromeのパス指定(executable_path)は注意してください。Windowsの場合は、フォルダー階層はバックスラッシュ2回にする必要があります。

GoogleのWebサイトで検索を実行する

Googleの検索を自動化しようとした場合、以下のような課題があります。

  • Webページが完全に読み込まれる前にコードを実行しようとするとコードの処理が失敗する
  • Webページの要素が存在しないのに、要素を操作しようとするとコードの処理が失敗する

上記の課題を解決するためには、WebDriverWaitを使って任意のHTMLの要素が特定の状態になるまで待つことで解決可能です。

presence_of_element_locatedを実行することで、指定した要素がDOM上に現れるまで待機することが出来ます。

An expectation to locate an element and check if the selection state specified is in that state. locator is a tuple of (by, path) is_selected is a boolean

[例]

WebDriverWait(driver, MAX_WAIT_TIME_SEC).until(EC.presence_of_element_located((By.CLASS_NAME, INPUT_BOX_CLASS_NAME)))

EC.presence_of_element_located((By.CLASS_NAME, INPUT_BOX_CLASS_NAME))で2重の括弧になっていますが、バグではありません。 内側の括弧がタプル(tuple of (by, path))を示します。byは要素の種類、pathは要素の位置を示します。外側の括弧が関数presence_of_element_locatedの括弧です。

Webページの要素を操作する場合は、Webページの要素にアクセス可能か必ず確認してから操作したほうがよいでしょう。

サンプルコード

以下のサンプルコードでは、以下の操作を自動化しています。

  1. GoogleのWebサイトを開く
  2. 入力フィールドをクリアする (Back Spaceボタンを100回押す)
  3. 入力フィールドに、文字列testを入力する
  4. Enterボタンを押す
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

# ChromeDriverのパスを引数に指定しChromeを起動
CHROME_DRIVER_PATH = "F:\\tool\\ChromeDriver_77_0_3865_40\\chromedriver.exe"
options = Options()
driver = webdriver.Chrome(executable_path=CHROME_DRIVER_PATH, options=options)

# 指定したURLに遷移する
web_site = 'https://www.google.com/'
driver.get(web_site )

# 指定された要素(検索テキストボックス)がDOM上に現れるまで待機する (最大でMAX_WAIT_TIME_SEC秒待つ)
MAX_WAIT_TIME_SEC = 100
INPUT_BOX_CLASS_NAME = "gLFyf"
element = WebDriverWait(driver, MAX_WAIT_TIME_SEC).until(EC.presence_of_element_located((By.CLASS_NAME, INPUT_BOX_CLASS_NAME)))

# 検索テキストボックスをクリアする
for item in range(0,100) :
    element.send_keys(Keys.BACK_SPACE)

# 検索テキストボックスに文字列を入力する
code = "test"
element.send_keys(code)

# Enterボタンを押す
element.send_keys(Keys.RETURN)

参考ページ

あとがき

Webページの要素の指定をさらに簡単にしたいので、「XPath」で指定できるようにしたいと思います。また、GoogleのUIを使用しないHeadless Chromeでの自動化ができるようにしたいと思います。

Python データ構造学習

リストを使って記載してみる

def names(name_and_grouplist, group_name):
    _names = []

    for name_and_group in name_and_group_list:
        if name_and_group[0] == group_name:
            name = name_and_group[1]
            if name not in _names:
                _names.append(name)

    return _names

name_and_group_list = [
    ["Aグループ", "Tom"],
    ["Aグループ", "Jerry"],
    ["Bグループ", "Mike"],
    ["Bグループ", "Ichiro"],
    ["Bグループ", "Mike"],
]

result_list = names(name_and_group_list, "Bグループ")

print(result_list)
['Mike', 'Ichiro']

辞書を使って記載してみる

def get_name_and_group(name_and_group_list):

    # 辞書宣言
    _names_by_group = {}

    print(type(_names_by_group))

    for name_and_group in name_and_group_list:
        
        group = name_and_group[0]
        name = name_and_group[1]

        print(type(group))
        print(type(name))

        if group not in _names_by_group:
             _names_by_group[group] = []

        if name not in _names_by_group:
            _names_by_group[group].append(name)

    return _names_by_group


name_and_group_list = [
    ["A", "Tom"],
    ["A", "Jerry"],
    ["B", "Mike"],
    ["B", "Ichiro"],
    ["B", "Mike"],
]

print(type(name_and_group_list))

result_list = get_name_and_group(name_and_group_list)

print(result_list["B"])
['Mike', 'Ichiro', 'Mike']

defaultdictを使用して、辞書を改善 (listで初期化)

from collections import defaultdict

def get_names_by_group(someList):
    _names_by_group = defaultdict(list)
    
    for item in someList:
        group = item[0]
        name = item[1]
        
        if name not in _names_by_group[group]:
            _names_by_group[group].append(name)
    
    return _names_by_group



name_and_group_list = [
    ["A", "Tom"],
    ["A", "Jerry"],
    ["B", "Mike"],
    ["B", "Ichiro"],
    ["B", "Mike"],
]


result_list = get_names_by_group(name_and_group_list)

print(result_list["B"])
['Mike', 'Ichiro']

defaultdictを使用してみる

defaultdictは標準ライブラリのcollectionsモジュールに含まれるデータ型です。 辞書に存在しないキーにアクセスしたとき、デフォルト値が設定されたキーを自動で作ってくれるのがメリットです。 defaultdict の引数には、「初期化時に実行する関数」を記述します。

defaultdictを使用して、辞書を改善 (setで初期化)

from collections import defaultdict

def get_names_by_group(someList):
    
    _names_by_group = defaultdict(set)
    
    for group, name in someList:
        _names_by_group[group].add(name)
    
    return _names_by_group



name_and_group_list = [
    ["A", "Tom"],
    ["A", "Jerry"],
    ["B", "Mike"],
    ["B", "Ichiro"],
    ["B", "Mike"],
]


result_list = get_names_by_group(name_and_group_list)

print(result_list["B"])
{'Mike', 'Ichiro'}

リンク

C++のコードレビューまとめ

C++のコードレビューのまとめを行います

派生される可能性があるクラスの場合、デストラクタにvirtualをつけよう

ポリモーフィズムを利用すべく作った基底クラスのデストラクタはvirtualが必要であるためです。 基底クラスのデストラクタにvirtualを付けない場合、派生クラスのデストラクタが呼ばれずメモリリークが起きる

ルール厳守なもの

[Guilty Code]

class Test
{
    Test(){}
}

[Correct Code]

class Test
{
    Test(){}
    virtual ~Test() = default;
}

std::cinとstd::coutへのアクセスは副作用があるため呼び出す場所は限定すること

std::cinとstd::coutは副作用があるため呼び出す場所は限定すること (main関数内を強く推奨)

不要な#include文は削除しましょう

使用していない#include文は削除しましょう

コードを意味のあるまとめまりにしよう

単体テストを活用しよう

#include <cassert>
#include <string>
#include "Main.h"

bool isIPAddr(const std::string& inputConsoleLine);

class Test
{
public:
    Test()
    {
        ExecuteTest();
    }
private:    
    static void ExecuteTest()
    {
        // ---- 正常パターン単体テスト ---- //
        // 正規表現ロジック上の下限値・上限値確認
        assert(isIPAddr("0.0.0.0"));
        assert(isIPAddr("255.255.255.255"));
        assert(isIPAddr("9.9.9.9"));
        assert(isIPAddr("10.99.10.99"));
        assert(isIPAddr("100.199.100.199"));
        assert(isIPAddr("200.249.200.249"));
        assert(isIPAddr("250.255.250.255"));
        
        // ---- 異常パターン単体テスト ---- //
        // 前置きの0は失敗判定させる
        assert(!isIPAddr("00.00.00.00"));
        assert(!isIPAddr("000.000.000.000"));
        assert(!isIPAddr("0000.0000.0000.0000"));
        
        // 異常値を与える
        assert(!isIPAddr("."));
        assert(!isIPAddr(","));
        assert(!isIPAddr("2147483647"));
        
        // コロンを挿入する位置を変更する
        assert(!isIPAddr("12.3..4"));
        assert(!isIPAddr("..1.2.3.4"));
        assert(!isIPAddr("1.2.3.4.."));
        
        // 誤った桁数を与える
        assert(!isIPAddr("1"));
        assert(!isIPAddr("1.2"));
        assert(!isIPAddr("1.2.3"));
        assert(!isIPAddr("1.2.3.4.5"));
        
        // 範囲外の値を与える
        assert(!isIPAddr("-1.-1.-1.-1"));
        assert(!isIPAddr("256.256.256.256"));
        assert(!isIPAddr("2147483647.2147483647.2147483647.2147483647"));  // __int32の+側最大値
    }
};

const static Test Test;

intよりint32_tを使おう

型エイリアス宣言を用いて、クラス側でクラスで使用する型を決定することで、ソフトウェアの堅牢性を向上させる

型エイリアスは以前に定義された型を参照する名前です (typedef と同様です)

以下のように型を別名で宣言します。 using TestData = std::array<int, 10>;

【修正前】 [Main.cpp]

#include <iostream>
#include "Test.hpp"

int main(void)
{
    std::array<int, 10> testData{1,2,3,4,5,6,7,8,9,10};
    
    Test test{testData};
    
    for (const auto& i : test.GetMultiplyData(5))
    {
        std::cout << i << std::endl;
    }
}

[Test.hpp]

#include <array>
#include <vector>
#include <algorithm>    // std::transform

class Test
{

private:
    std::array<int,10> m_inputData;

public:
    Test(std::array<int,10> inputData) : m_inputData{inputData}
    {}
    
    // 倍数したデータをvectorで取得する
    std::vector<int> GetMultiplyData(int times)
    {
        std::vector<int> resultData;
        
        std::transform(std::begin(m_inputData), std::end(m_inputData), std::back_inserter(resultData), [times](int data){return data * times;});
        
        return resultData;
    }
};

【修正後】 [Main.cpp]

#include <iostream>
#include "Test.hpp"

int main(void)
{
    Test::TestData testData{1,2,3,4,5,6,7,8,9,10};
    
    Test test{testData};
    
    for (const auto& i : test.GetMultiplyData(5))
    {
        std::cout << i << std::endl;
    }
}

[Test.hpp]

#include <array>
#include <vector>
#include <algorithm>    // std::transform

class Test
{
    // クラス側でクラスで使用する型を決定することで、ソフトウェアの堅牢性を向上させる
public:
    using TestData = std::array<int, 10>;
    
private:
    TestData m_inputData;

public:
    Test(TestData inputData) : m_inputData{inputData}
    {}
    
    // 倍数したデータをvectorで取得する
    std::vector<int> GetMultiplyData(int times)
    {
        std::vector<int> resultData;
        
        std::transform(std::begin(m_inputData), std::end(m_inputData), std::back_inserter(resultData), [times](int data){return data * times;});
        
        return resultData;
    }
};

リンク