投稿者「kooo」のアーカイブ

JavaのSpringSecurityでAESの暗号化/復号化処理を実装する

サクっと作るなら以下が参考になります。

http://terasolunaorg.github.io/guideline/current/ja/Security/Encryption.html

暗号化

    public static String encryptTextByAesWithGcm(String secret, String salt, String plainText) {
        TextEncryptor aesTextEncryptor = Encryptors.delux(secret, salt);

        return aesTextEncryptor.encrypt(plainText);
    }

復号化

    public static String decryptTextByAesWithGcm(String secret, String salt, String cipherText) {
        TextEncryptor aesTextEncryptor = Encryptors.delux(secret, salt);

        return aesTextEncryptor.decrypt(cipherText);
    }

補足

ここからが個人的には重要

1.secretはプレーンテキストでいいのですが、saltはプレーンテキストではダメなので以下みたいになります。

String salt = new String(Hex.encodeHexString("salt".getBytes()));

2.参考サイト

暗号化の結果について
encryptメソッドの返り値 (暗号化の結果) は実行毎に異なる値を返すが、鍵とソルトが同一であれば復号処理の結果は同一になる (正しく復号できる) 。

の記載、初期化ベクトル (iv)の役割よるものです。
ivは、同じ鍵を使用して同じ平文データを複数回暗号化するときに異なる暗号文を生成します。
ivはランダムな値で、セッションごとに生成される。

セッションごと?確かにソースをSpringSecurityのソースを追っていくと、以下になっている。

    /**
     * Creates a standard password-based bytes encryptor using 256 bit AES encryption.
     * Derives the secret key using PKCS #5's PBKDF2 (Password-Based Key Derivation
     * Function #2). Salts the password to prevent dictionary attacks against the key. The
     * provided salt is expected to be hex-encoded; it should be random and at least 8
     * bytes in length. Also applies a random 16-byte initialization vector to ensure each
     * encrypted message will be unique. Requires Java 6. NOTE: This mode is not
     * <a href="https://en.wikipedia.org/wiki/Authenticated_encryption">authenticated</a>
     * and does not provide any guarantees about the authenticity of the data. For a more
     * secure alternative, users should prefer
     * {@link #stronger(CharSequence, CharSequence)}.
     * @param password the password used to generate the encryptor's secret key; should
     * not be shared
     * @param salt a hex-encoded, random, site-global salt value to use to generate the
     * key
     *
     * @see Encryptors#stronger(CharSequence, CharSequence)
     */
    public static BytesEncryptor standard(CharSequence password, CharSequence salt) {
        return new AesBytesEncryptor(password.toString(), salt, KeyGenerators.secureRandom(16));
    }

以下のJavaDocによると、、、

https://spring.pleiades.io/spring-security/site/docs/current/api/org/springframework/security/crypto/encrypt/AesBytesEncryptor.html

new AesBytesEncryptorの第三引数の KeyGenerators.secureRandom(16) がiv

確かに乱数

では、復号化にも同じ値のivがいるのかなと思ったらソース見たら以下のように暗号化後のテキストから取得しているようなので大丈夫そうでした。

    @Override
    public byte[] decrypt(byte[] encryptedBytes) {
        synchronized (this.decryptor) {
            byte[] iv = iv(encryptedBytes);
            CipherUtils.initCipher(this.decryptor, Cipher.DECRYPT_MODE, this.secretKey, this.alg.getParameterSpec(iv));
            return CipherUtils.doFinal(this.decryptor,
                    (this.ivGenerator != NULL_IV_GENERATOR) ? encrypted(encryptedBytes, iv.length) : encryptedBytes);
        }
    }

    private byte[] iv(byte[] encrypted) {
        return (this.ivGenerator != NULL_IV_GENERATOR)
                ? EncodingUtils.subArray(encrypted, 0, this.ivGenerator.getKeyLength())
                : NULL_IV_GENERATOR.generateKey();
    }

まとめ

ということで改めて復習です。

Secret:
Secretは一般的にユーザーが選択する文字列で、秘密情報を保護するために使用されます。
Secretベースのセキュリティでは、ユーザーのSecretから導かれた鍵を生成します。このとき、パスワードを安全に保管し、強力な鍵導出関数(PBKDF2、bcrypt、Scryptなど)を使用することが重要です。

Salt:
SaltはSecretベースのセキュリティで使用され、Secretに追加されるランダムなデータです。
Saltはユーザーごとに異なり、同じSecretを持つユーザーのハッシュ値を異なるものにします。これにより、レインボーテーブル攻撃から保護されます。

初期化ベクトル (iv):
ivは、同じ鍵を使用して同じ平文データを複数回暗号化するときに異なる暗号文を生成します。
ivはランダムな値で、セッションごとに生成される。

参考書籍

Java、Springをしっかり学びたい人向けの本をまとめています。
Java Spring/SpringBoot習得向けおすすめ本

Javaコーディング規約・実装注意事項整理

Javaにおけるコーディング規約・実装注意事項整理

一般的なJavaのコーディング規約はよくGoogleとかにまとめられているのですが、いざ実際現場に適用すると多すぎて担当者が理解しきれないので、最低限を整理してみました。

観点として,全て共通する一般的な観点とクラス設計,エラー処理,マルチスレッド,性能と分けています。

参考書籍

こちらの本が良い本だったので影響を受けて自分でもまとめようと思いまとめました。

良いコード/悪いコードで学ぶ設計入門―保守しやすい 成長し続けるコードの書き方

一般的観点

  • メソッドやクラスの役割や責務を明確にし、シンプルな設計を心掛けること
  • インデントやスペース、命名規則などのコーディング規約に従い、一貫性のあるコードを書くこと
  • マジックナンバー(直接的な数値)を使わず、意味のある定数や列挙型を使用すること
  • メソッドやクラスの長さを適切に制御し、読みやすさを向上させること
  • 分岐処理では条件分岐を漏らさず記載し、必ずElse節を書くこと
  • 固定値はシステム設定の外部ファイルに定義し、リファクタリングや設定変更が容易にできるようにすること
  • データベースアクセスやネットワーク通信などの外部リソースにアクセスする際は、適切なエラーハンドリングを行い、リトライやロールバックなどのリカバリー処理を実装すること

クラス設計観点

  • メソッドの入力引数や戻り値の型には具体的なクラスやインターフェースを使用し、できる限り抽象化すること
  • 外部からのインプット引数/戻り値はnullである可能性を考慮し、適切なnullチェックを行うこと
  • 不変な変数には極力final修飾子を付け、再代入を防ぐこと

エラー処理観点

  • Catchに指定するExceptionは上位クラスのExceptionにまとめないこと
  • Catchした例外は必ずログに出力し、上位にThrowする際はCatchした例外を保持してThrowすること

マルチスレッド観点

  • マルチスレッド環境での競合状態を避けるために、適切な同期やロックを使用すること
  • 変数のスコープを意識して使用すること(サーブレット定義の変数はシングルトンとなる)
  • 使用するJavaライブラリの処理がスレッドセーフかどうか意識して使うこと(SimpleDateFormatなど)
  • マルチスレッド対応のクラス(ConcurrentHashMapやThreadLocalなど)を過信せず正しく使用すること

性能観点

  • リソースの解放漏れを避けるため、try-with-resources文を使用してリソースの自動クローズを行うこと
  • 文字列の連結や変更が頻繁に行われる場合は、StringBuilderやStringBufferを使用して効率的な文字列操作を行うこと
  • 大規模なループ処理の場合には、パフォーマンスを意識して最適化すること(無駄な処理や重複した操作の排除)
  • 再帰呼び出しを行う場合は、スタックオーバーフローのリスクに対処するために適切な終了条件を設定すること

まとめ

これら全てを理解して実装していくのは結構大変ですよね。
丸暗記ではなく,ダメな背景を理解して定期的に見直し,体の一部にしていきたいと思います。

SpringMVCを使ったJavaプログラムにおけるシングルトンとスタティック変数・メソッドの効果を検証してみた

マルチスレッドでのスタティック変数・メソッドの効果を検証してみた

JavaのSpringMVCを使用して、シングルトンパターンとスタティック変数、スタティックメソッドを実装してみました。これらはJavaアプリケーションでよく使用される概念なんですが、ちょっと混乱してきたので、自分なりの整理結果になります。詳細は以下をご覧ください。

検証ソース

  • コントローラソース
@Controller
@RequestMapping("/hello")
public class HelloWorldController {
    private int counter = 0;

    @RequestMapping("/thread")
    public ModelAndView thread(@RequestParam("id") String id) {
        System.out.println("Method: " + new Object(){}.getClass().getEnclosingMethod().getName());
        System.out.println("Parameter:" + id);

        counter = CommonUtil.countup(counter, id);

        CommonUtil com = new CommonUtil();
        counter = com.countdown(counter, id);

        String NewTime1 = CommonUtil.newtime(id);
        String NewTime2 = CommonUtil.newtime(id);

        System.out.println("NewTime1:" + NewTime1);
        System.out.println("NewTime2:" + NewTime2);
        ModelAndView modelAndView = new ModelAndView("thread");
        modelAndView.addObject("id", id);
        modelAndView.addObject("NewTime1", NewTime1);
        modelAndView.addObject("NewTime2", NewTime2);
        return modelAndView;
    }
}
  • 共通処理クラス
public class CommonUtil {

    public static int countup(int var, String id) {
        System.out.println("Method: " + new Object(){}.getClass().getEnclosingMethod().getName());
        var++;
        System.out.println(var);
        return(var);
    }

    public int countdown(int var, String id) {
        System.out.println("Method: " + new Object(){}.getClass().getEnclosingMethod().getName());
        var--;
        System.out.println(var);
        return(var);
    }

    public static String newtime(String id) {
        System.out.println("Method: " + new Object(){}.getClass().getEnclosingMethod().getName());
        // 現在日時を取得
        LocalDateTime now = LocalDateTime.now();
        // フォーマットを指定して文字列に変換
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        String nowStr = now.format(formatter);      
        System.out.println(nowStr);
        return(nowStr);
    }
}
  • 画面表示用HTML
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Thread</title>
</head>
<body>
  <h2>title</h2>
  <p th:text="${id}"></p>
  <p th:text="${NewTime1}"></p>
  <p th:text="${NewTime2}"></p>
</body>
</html>

ソースコードは単純だと思います。
キーはコントローラクラスの変数「private int counter = 0;」
これは SpringのDIコンテナで管理されている Controller のデフォルトのスコープだとシングルトンのためスレッドセーフではありません
では、それを検証していきましょう。

アクセスURL

/hello/thread?id=001
/hello/thread?id=002

マルチスレッドでの検証となるため、URLは二つ用意します。
ログが分かりやすいようにパラメータに id を付与します。
id 自体には処理結果に全く関係ありません。
では、結果です。

結果

順次処理の実行結果

まずは別々にアクセスが来た場合
カウントアップされ直後にカウントダウンされ 1 -> 0、1 -> 0 を繰り返す結果となります。

Method: thread
Parameter:001
Method: countup
1
Method: countdown
0
Method: newtime
2023/04/30 10:04:50
Method: newtime
2023/04/30 10:04:50
NewTime1:2023/04/30 10:04:50
NewTime2:2023/04/30 10:04:50
Method: thread
Parameter:002
Method: countup
1
Method: countdown
0
Method: newtime
2023/04/30 10:04:52
Method: newtime
2023/04/30 10:04:52
NewTime1:2023/04/30 10:04:52
NewTime2:2023/04/30 10:04:52

同時実行の実行結果

では、次に同時実行のケースです。
同時にリクエストが来た場合、以下の結果をなることがあります。
一時的に「counter = 2」になっていることが分かると思います。
要件として「counter」に何をカウントしたいかによりますが、このケースは予期せず起こったケースとなることが多いのではないでしょうか。
もし別々のリクエストでも同じカウントでトータルをカウントしたいケースを想定した場合、「private static int counter = 0;」と明示的にstatic変数にすべきと思います。

Method: thread
Parameter:001
Method: countup
1
Method: thread
Parameter:002
Method: countup
2
Method: countdown
1
Method: newtime
2023/04/30 10:14:05
Method: newtime
2023/04/30 10:14:05
NewTime1:2023/04/30 10:14:05
NewTime2:2023/04/30 10:14:05
Method: countdown
0
Method: newtime
2023/04/30 10:14:13
Method: newtime
2023/04/30 10:14:13
NewTime1:2023/04/30 10:14:13
NewTime2:2023/04/30 10:14:13

ちなみにこの動作確認のやり方としては、1つ目のリクエスト処理中に2つ目のリクエスト処理を先に処理させています。
具体的にはEclipseのデバック機能を使って以下のブレイクポイントを設定しておき、1つ目のリクエスト処理を止めておき、最初に2つ目のリクエスト処理を進めています。

今回は、変数「private int counter = 0;」がキーであるため、
CommonUtilのメソッド「public static int countup(int var, String id)」と「public int countdown(int var, String id)」は
敢えて、countupメソッドの方をstaticメソッドとしていますが、
いずれのケースでも変数「private int counter = 0;」をスタティック変数として使用している結果がわかると思います。
(もし、countdownメソッドの方がスタティック変数を使っていないければ、結果が0にならないはずなので)

補足結果

「public static String newtime(String id)」メソッドでは、変数「private int counter = 0;」を使用していません。
変わりに内部で作成した「String nowStr」を使用しています。
この変数はstaticメソッドのnewtimeメソッド内で作成されていますが、スレッドセーフな変数になります。

public static String newtime(String id) {
        System.out.println("Method: " + new Object(){}.getClass().getEnclosingMethod().getName());
        // 現在日時を取得
        LocalDateTime now = LocalDateTime.now();
        // フォーマットを指定して文字列に変換
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        String nowStr = now.format(formatter);      
        System.out.println(nowStr);
        return(nowStr);
    }

同時実行の実行結果

今回も同時実行して1つ目のリクエスト処理中に2つ目のリクエスト処理を先に処理させています。
ただし、今回は時間がそれぞれ正しく(上書きされずに)取得されて出力されていることがわかると思います。

Method: thread
Parameter:001
Method: countup
1
Method: countdown
0
Method: newtime
Method: thread
Parameter:002
Method: countup
1
Method: countdown
0
Method: newtime
2023/04/30 10:46:22
Method: newtime
2023/04/30 10:46:42
NewTime1:2023/04/30 10:46:22
NewTime2:2023/04/30 10:46:42
2023/04/30 10:46:03
Method: newtime
2023/04/30 10:47:12
NewTime1:2023/04/30 10:46:03
NewTime2:2023/04/30 10:47:12

今回のブレイクポイントは以下に設定しています。

ちなみに画面の表示結果は以下のようになります。

まとめ

最初に書いた通り、キーは変数「private int counter = 0;」です。
これはぱっと見ると、メンバ変数であり、スレッドセーフのように見えますが、SpringのDIコンテナで管理されているインスタントのデフォルトのスコープだと
シングルトン
のためスレッドセーフではありません。
これを常に意識して使いましょう。
補足としてメソッドがstaticかそうではないかは影響しないことが確認できました。

HTMLテーブルの列の表示非表示や行の入れ替えをJavaScriptで実装する方法

JavaScriptでtableを操作するサンプル

HTMLテーブルを操作することは、Web開発において重要なスキルの一つです。この記事では、JavaScriptを使ったHTMLテーブルの列の表示非表示や行の入れ替えの方法を解説します。

ドラッグドロップで列を入れ替える

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Tableの列の入れ替え</title>
  <style>
    table {
      border-collapse: collapse;
    }
    th, td {
      border: 1px solid #ccc;
      padding: 0.5rem;
      text-align: center;
      cursor: move;
    }
  </style>
</head>
<body>
  <table id="myTable">
    <thead>
      <tr>
        <th>名前</th>
        <th>年齢</th>
        <th>性別</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>山田太郎</td>
        <td>20</td>
        <td>男性</td>
      </tr>
      <tr>
        <td>鈴木花子</td>
        <td>25</td>
        <td>女性</td>
      </tr>
      <tr>
        <td>田中一郎</td>
        <td>30</td>
        <td>男性</td>
      </tr>
    </tbody>
  </table>

  <script>
    let draggingColumn;

    // ドラッグ開始時に実行する関数
    function handleDragStart(e) {
      // ドラッグされた列の位置を保存する
      draggingColumn = e.target.cellIndex;
    }

    // ドラッグオーバー時に実行する関数
    function handleDragOver(e) {
      // ドロップ先の列の位置を取得する
      const dropIndex = e.target.cellIndex;

      // ドラッグされた列とドロップ先の列が異なる場合
      if (draggingColumn !== dropIndex) {
        const rows = document.querySelectorAll('#myTable tbody tr');
        rows.forEach(row => {
          // ドラッグされた列とドロップ先の列のセルを取得する
          const draggingCell = row.cells[draggingColumn];
          const dropCell = row.cells[dropIndex];

          // ドラッグされた列をドロップ先の列の前に挿入する
          if (draggingColumn < dropIndex) {
            row.insertBefore(dropCell, draggingCell);
          } else {
            row.insertBefore(draggingCell, dropCell);
          }
        });
      }
    }

    // 各列にイベントリスナーを登録する
    const columns = document.querySelectorAll('#myTable th');
    columns.forEach(column => {
      column.setAttribute('draggable', true);
      column.addEventListener('dragstart', handleDragStart);
      column.addEventListener('dragover', handleDragOver);
    });
  </script>
</body>
</html>

このコードでは、各列にdragstart、dragoverの2つのイベントリスナーを登録しています。dragstartイベントが発生した際には、ドラッグされた列の位置を変数draggingColumnに保存しています。そして、dragoverイベントが発生した際には、ドロップ先の列の位置を変数dropIndexに取得し、ドラッグされた列とドロップ先の列が異なる場合には、querySelectorAllメソッドを用いてテーブルのtbody要素内にあるすべての行を取得し、forEachメソッドを用いて各行に対して以下の処理を実行しています。

1.ドラッグされた列とドロップ先の列のセルを取得する。
2.ドラッグされた列をドロップ先の列の前に挿入する。

こうすることで、列の入れ替えを実現することができます。

ボタン操作で行を入れ替える

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Move Table Row Sample</title>
    <style>
      table {
        border-collapse: collapse;
      }
      th, td {
        border: 1px solid black;
        padding: 0.5em;
      }
      .dragged {
        opacity: 0.5;
      }
    </style>
  </head>
  <body>
  <table>
    <thead>
      <tr>
        <th>ID</th>
        <th>Name</th>
        <th>Age</th>
        <th>Gender</th>
        <th>Action</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>1</td>
        <td>John Doe</td>
        <td>25</td>
        <td>Male</td>
        <td><button onclick="moveUp(this)">Up</button><button onclick="moveDown(this)">Down</button></td>
      </tr>
      <tr>
        <td>2</td>
        <td>Jane Smith</td>
        <td>30</td>
        <td>Female</td>
        <td><button onclick="moveUp(this)">Up</button><button onclick="moveDown(this)">Down</button></td>
      </tr>
      <tr>
        <td>3</td>
        <td>Bob Johnson</td>
        <td>40</td>
        <td>Male</td>
        <td><button onclick="moveUp(this)">Up</button><button onclick="moveDown(this)">Down</button></td>
      </tr>
      <tr>
        <td>4</td>
        <td>Alice Williams</td>
        <td>35</td>
        <td>Female</td>
        <td><button onclick="moveUp(this)">Up</button><button onclick="moveDown(this)">Down</button></td>
      </tr>
    </tbody>
  </table>
  <script>
    function moveUp(button) {
      const row = button.parentNode.parentNode;
      if (row.previousElementSibling) {
        row.parentNode.insertBefore(row, row.previousElementSibling);
      }
    }
    function moveDown(button) {
      const row = button.parentNode.parentNode;
      if (row.nextElementSibling) {
        row.parentNode.insertBefore(row.nextElementSibling, row);
      }
    }
  </script>
  </body>
</html>

このサンプルでは、

要素内の行をドラッグして入れ替えることができます。行をドラッグしたとき、dragstartイベントが発生して、ドラッグ中の行を保持します。ドラッグ中の行が他の行の上に移動すると、dragoverイベントが発生して、ドラッグ中の行を他の行の前または後ろに移動します。行をドロップすると、dragendイベントが発生して、ドラッグ中の行をリセットします。

列の一部を非表示にする

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>テーブルの列の表示非表示を切り替えるサンプル</title>
  <script>
    function hideColumn(columnIndex) {
      var table = document.getElementsByTagName("table")[0];
      var rows = table.rows;
      for (var i = 0; i < rows.length; i++) {
        rows[i].cells[columnIndex].style.display = "none";
      }
    }

    function showColumn(columnIndex) {
      var table = document.getElementsByTagName("table")[0];
      var rows = table.rows;
      for (var i = 0; i < rows.length; i++) {
        rows[i].cells[columnIndex].style.display = "";
      }
    }
  </script>
</head>
<body>
  <table>
    <thead>
      <tr>
        <th>名前</th>
        <th>年齢</th>
        <th>メールアドレス</th>
        <th>電話番号</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>山田 太郎</td>
        <td>25</td>
        <td>taro.yamada@example.com</td>
        <td>012-345-6789</td>
      </tr>
      <tr>
        <td>鈴木 次郎</td>
        <td>30</td>
        <td>jiro.suzuki@example.com</td>
        <td>03-1234-5678</td>
      </tr>
    </tbody>
  </table>
  <button onclick="hideColumn(1)">2番目の列を非表示にする</button>
  <button onclick="showColumn(1)">2番目の列を表示する</button>
</body>
</html>

上記のコードでは、hideColumn関数を使用して、2番目の列を非表示にし、showColumn関数を使用して、2番目の列を表示することができます。

行の一部を非表示にする

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hide Table Row Sample</title>
    <style>
      .hidden {
        display: none;
      }
    </style>
  </head>
  <body>
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Age</th>
          <th>Gender</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>John Doe</td>
          <td>30</td>
          <td>Male</td>
        </tr>
        <tr>
          <td>Jane Smith</td>
          <td>25</td>
          <td>Female</td>
        </tr>
        <tr>
          <td>Bob Johnson</td>
          <td>40</td>
          <td>Male</td>
        </tr>
      </tbody>
    </table>

    <button onclick="toggleGender('Male')">Toggle Male Rows</button>

    <script>
      function toggleGender(gender) {
        const rows = document.querySelectorAll('table tbody tr');
        rows.forEach((row) => {
          const genderCell = row.querySelector('td:nth-child(3)');
          if (genderCell.innerText === gender) {
            row.classList.toggle('hidden');
          }
        });
      }
    </script>
  </body>
</html>

このサンプルでは、各行の3列目(Gender列)の値がMaleである行を非表示にするために、行全体にhiddenクラスを追加します。toggleGender()関数は、ボタンがクリックされたときに、各行のGender列の値が引数で指定された値(この場合はMale)と一致する場合に、その行を表示または非表示に切り替えるために、行全体に対してhiddenクラスをトグルします。

参考書籍

2010年の刊行から約100,000部の実績を誇るロングセラーの1冊。
モダンな書き方に寄せて、実践的なコード例で学べる入門〜中級向けの一冊。実際のコードをきれいに保つ設計や、イベントハンドラの整理に役立ちます。

改訂3版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで

やる気が溢れる 元気が出る Mr.Children エネルギーソング

仕事に前向きになれるエンジニア的 Mr.Children エネルギーソング

私は普段エンジニアとして生活しているのですが、Mr.Childrenは毎日と言っていいほど助けられているので、そんな日々を定期的に振り返り元気をもらうための場としてこの記事を投稿しました。

エンジニアの仕事に関わらずかもしれませんが,仕事はいい事ばかりではなく,どちらかというといろんなしがらみがあって大変です。 そんな時に私が心の拠り所にしているミスチルソングをランキング形式で紹介します。

実際に聞くのは断然ライブ映像がオススメです♪
歌からもらえるエネルギーが200%増しなのでオススメのライブも紹介します。

ライブごとのオススメは次の記事でまとめていきます♪

Mr.Children映像化ライブセットリスト集

実は本も発売されてます。ファンの方はぜひ♪

ミスチルおすすめ書籍

NEWアルバム「産声」2026年3月25日発売!!
【Amazon.co.jp限定】産声 (初回生産限定盤) - Mr.Children (メガジャケ付)

※本ページはプロモーションが含まれています。

個人的 TOP10 紹介

10位.星になれたら

長く助走をとった方がより
遠くに飛べるって聞いた

いつも良い事ばかりではないけど
コツコツ頑張っていれば
頑張った分だけ良い事が起こそうで🙂

星になれたら ライブは結構レアだと思います
オススメは SENSE -in the field-

Mr.Children STADIUM TOUR 2011 SENSE -in the field-【Blu-ray】 [ Mr.Children ]

9位.ヨーイドン

数字やデータで未来はつくれない
ぽかりと空いた心の穴埋め問題は一人では解けない

仕事ってよく結果だって言われますよね
でも仕事のやりがいってきっとそれだけじゃない

ヨーイドン はまだライブで歌われていないのですよね
名曲なのに♪

8位.Prelude

前奏曲(プレリュード)が聞こえてくる
さぁ 耳を澄ませてごらん

仕事で新しい事を始める時は不安がいっぱい
でも心の中にあるワクワク感にも目を向けたい

一言
このライブの Prelude やばいです かなりお気に入りです

Mr.Children Dome Tour 2019 “Against ALL GRAVITY”【Blu-ray】 [ Mr.Children ]

7位.幻聴

一歩 また一歩 確実に進む そんなイメージを忘れずに

辛いことには必ず終わりがある
諦めずに1歩、1歩進んで
3ヶ月かかった課題がやっと昨日で解決😊

おすすめは 重力と呼吸
このライブ自体全体的に頑張っていこうという雰囲気が漂っていて好きです♪

Mr.Children Tour 2018-19 重力と呼吸【Blu-ray】 [ Mr.Children ]

6位.ALIVE

迷いや悩みなど一生消えぬものと思えたなら
ボクらはスーパーマン

迷いや悩みのない人なんていない
みんなそれぞれの場所で頑張ってる
人と比べることに意味はない

古い曲ですが、未完ライブで歌われた名曲です♪
Mr.Children Stadium Tour 2015 未完

5位.I\'ll be

いつも心にしてたアイマスクを外してやればいい
不安や迷いと無二の親友になれればいい
旅立とう 明日は無いぞってな具合に
胸に刻みながら一歩ずつ進んで
いつだって夢中だ

時にはトラブル続きで終わらない日々もある
だけど何事にも必ず終わりはある
だからこの曲♪

不安や迷いと無二の親友になれればいいという表現が天才だと思う
名曲だけどライブで歌われたのはかなりレア
CONCERT TOUR POP SAURUS 2001

4位.未来

自分を信じたなら ほら未来が動き出す

何かが終わる一方で始まることもあるタイミングの時はこの曲♪
何事も前向きに捉えて頑張ろう

SENSE -in the field-で歌われている名曲です♪

Mr.Children STADIUM TOUR 2011 SENSE -in the field-【Blu-ray】 [ Mr.Children ]

3位.皮膚呼吸

苦しみに息が詰まったときも
また姿 変えながら
そう今日も 自分を試すとき

仕事なので時には大変な時もある
プレッシャーは期待の裏返し
と 捉え方を変えて頑張るぞ!
という曲♪

2回目の Against ALL GRAVITY ですが
こちらはしっとりと歌い上げます

Mr.Children Dome Tour 2019 “Against ALL GRAVITY”【Blu-ray】 [ Mr.Children ]

2位.彩り

僕のした単純作業が
この世界を回り回って
まだ出合ったこともない人の笑い声を作ってゆく

仕事の曲というとこの曲を一番最初に思い出す気がする♪
年の節目とか定期的に思い出したい曲
彩りをモットーに明日からも頑張ろう

オススメLIVEは彩りといったらこれ
HOME~in the field~

Mr.Children HOME TOUR 2007 -in the field- [ Mr.Children ]

1位.Worlds end

僕らはきっと試されてる
どれくらいの強さで
明日を信じていけるのかを

辛いとき苦しい時
それでも頑張って最後に結果が出るかどうかって
この言葉がすべての真理なんだと思う

Worlds end のオススメライブはやっぱり30周年記念LIVE半世紀へのエントランスです♪

Mr.Children 30th Anniversary Tour 半世紀へのエントランス

まとめ

あくまでも今の自分に響く曲なので、定期的に見直すかもしれません。 何よりミスチルの曲を10曲だけ選ぶなんて普通はできない(^^;) 全部素晴らしい曲なので。

AWS Lambda 入門~おすすめ本5冊の紹介~

概要〜サーバレス基礎からAmplifyを使った本格開発までのおすすめ書籍〜

最近、AWS Lambdaの本増えてきましたよね。ということで今回はLambdaのおすすめ本の紹介です。

基礎編から本格的な専門書、アーキテクト、実際にAmplifyを使った開発と徐々にレベルアップする内容になっているのでLambdaを学びたい人は一つずつ読んでいってみてください。

※本ページはプロモーションが含まれています。

Lambdaおすすめ本 5冊

今回紹介するAWS Lambdaのおすすめ本の一覧です。
忙しい方はここだけ確認してください。

基礎から学ぶ サーバーレス開発

基礎から学ぶ サーバーレス開発

まずは基本編です。Lambdaというかサーバレスとは?という導入編です。サーバレスとはどういうものか丁寧に書かれています。まずはサーバでの開発と比較してサーバレスにはどういったメリットデメリットがあるのかこの本で学びましょう。

目次

  • CHAPTER 01 サーバーレスとは
    • 1 サーバーレスの概要
    • 2 サーバーレスが注目された要因と新たなる課題
    • 3 サーバーレスのメリット・デメリット
  • CHAPTER 02 サーバーレス開発でよく使うサービス
    • 4 AWS Lambda
    • 5 Amazon API Gateway
    • 6 Amazon Aurora Serverless
    • 7 Amazon CloudWatch
    • 8 Amazon Simple Queue Service (Amazon SQS)
    • 9 AWS CodeCommit
    • 10 AWS CodePipeline
    • 11 AWS CodeBuild
    • 12 AWS CodeDeploy
    • 13 Amazon Simple Storage Service (Amazon S3)
    • 14 AWS Step Functions
    • 15 Amazon DynamoDB
    • 16 AWS Cloud9
    • 17 AWS X-Ray
  • CHAPTER 03 サーバーレスアプリケーションの構築
    • 18 フレームワーク
    • 19 CI/CD
    • 20 AWSにおけるCI/CD
    • 21 デプロイ手法
    • 22 トラフィックシフト
    • 23 昇格
  • CHAPTER 04 サーバーレスの運用監視
    • 24 サーバーレスのコスト
    • 25 サーバーレスにおける監視
  • CHAPTER 05 サーバーレス開発におけるセキュリティ
    • 26 サーバーレス開発におけるセキュリティの考え方
    • 27 Lambda@Edgeの利用
  • CHAPTER 06 サーバーレスの構築例
    • 28 完全サーバーレスでのWebページ構築事案
    • 29 完全サーバーレスでのWebページのバッチ部分
    • 30 APIバックエンドにAmazon RDSを用いた事例および2019年のアップデートについて
    • 31 サーバーレスで作る在宅勤務中の勤務時間登録システム
    • 32 AWS LambdaをAlexaのエンドポイントとして使う事例
  • CHAPTER 07 サーバーレスの失敗談と問題解決
    • 33 失敗談①~Amazon RDSを起動させ続けた
    • 34 失敗談②~AWS Lambda でスロットリングが発生してしまった
    • 35 SPA+サーバーレスで再読み込みをするとAccessDeniedになってしまう問題の解決方法

AWSではじめる クラウド開発入門

AWSではじめる クラウド開発入門

東大の人気授業が書籍化された内容です。Lambdaに限らずクラウド開発入門という位置付けなのでいろんな開発内容が書かれていて、クラウドでこうやって開発するんだとわかる内容になってます。最後の方は、かなり実践的で勉強になります。

目次

  • Chapter 1 はじめに
    • 1-1 本書の目的内容
    • 1-2 本書のフィロソフィー
    • 1-3 AWS アカウント
    • 1-4 環境構築
    • 1-5 前提知識
    • 1-6 本書で使用する表記について
  • Chapter 2 クラウド概論
    • 2-1 クラウドとは?
    • 2-2 なぜクラウドを使うのか?
  • Chapter 3 AWS入門
    • 3-1 AWSとは?
    • 3-2 AWSの機能・サービス
    • 3-3 Region と Availability Zone
    • 3-4 AWSでのクラウド開発
    • 3-5 CloudFormationとAWS CDK
  • Chapter 4 Hands-on #1:初めてのEC2インスタンスを起動する
    • 4-1 準備
    • 4-2 SSH
    • 4-3 アプリケーションの説明
    • 4-4 プログラムを実行する
    • 4-5 小括
  • Chapter 5 Hands-on #2: AWS でディープラーニングを実践
    • 5-1 なぜ機械学習をクラウドで行うのか?
    • 5-2 GPU による深層学習の高速化
    • 5-3 準備
    • 5-4 アプリケーションの説明
    • 5-5 スタックのデプロイ
    • 5-6 ログイン
    • 5-7 Jupyter Notebook の起動
    • 5-8 PyTorch はじめの一歩
    • 5-9 実践ディープラーニング! MNIST手書き数字認識タスク
    • 5-10 スタックの削除
  • Chapter 6 Docker 入門
    • 6-1 クラウドシステムの構築に向けて
    • 6-2 機械学習の大規模化
    • 6-3 Docker とは
    • 6-4 Docker チュートリアル
    • 6-5 Elastic Container Service (ECS)
  • Chapter 7 Hands-on #3 : AWS で自動質問回答ボットを走らせる
    • 7-1 Fargate
    • 7-2 準備
    • 7-3 Transformer を用いた question-answering プログラム
    • 7-4 アプリケーションの説明
    • 7-5 スタックのデプロイ
    • 7-6 タスクの実行
    • 7-7 タスクの同時実行
    • 7-8 スタックの削除
  • Chapter 8 Hands-on #4: AWS Batch を使って機械学習のハイパーパラメータサーチを並列化する
    • 8-1 クラウドを用いた機械学習モデルの最適化
    • 8-2 AWS Batch
    • 8-3 準備
    • 8-4 MNIST 手書き文字認識(再訪)
    • 8-5 アプリケーションの説明
    • 8-6 スタックのデプロイ
    • 8-7 Docker image を ECR に配置する
    • 8-8 単一のジョブを実行する
    • 8-9 並列に複数の Job を実行する
    • 8-10 スタックの削除
    • 8-11 クラウドを用いた機械学習アプリケーションの開発とデバッグ
    • 8-12 小括
  • Chapter 9 ウェブサービスの作り方
    • 9-1 個人のためのクラウドからみんなのためのクラウドへ
    • 9-2 ウェブサービスの仕組み Twitter を例に
    • 9-3 REST API
    • 9-4 Twitter API
  • Chapter 10 Serverless architecture
    • 10-1 Serverful クラウド (従来型)
    • 10-2 Serverless クラウドへ
    • 10-3 サーバーレスクラウドを構成するコンポーネント
  • Chapter 11 Hands-on #5: サーバーレス入門
    • 11-1 Lambda ハンズオン
    • 11-2 DynamoDB ハンズオン
    • 11-3 S3 ハンズオン
  • Chapter 12 Hands-on #6: Bashoutter
    • 12-1 準備
    • 12-2 アプリケーションの説明
    • 12-3 アプリケーションのデプロイ
    • 12-4 API リクエストを送信する
    • 12-5 大量のAPIリクエストをシミュレートする
    • 12-6 Bashoutter GUI を動かしてみる
    • 12-7 アプリケーションの削除
    • 12-8 小括
  • Chapter 13 Hands-on #7: boto3 道場
    • 13-1 boto3 の基本
    • 13-2 S3 道場
    • 13-3 DynamoDB 道場
    • 13-4 小括
  • Chapter 14 Hands-on #8: シン・Bashoutter
    • 14-1 シン・Bashoutter プロジェクトの概要
    • 14-2 STEP1:ドメインの設定とCloudFront の配置
    • 14-3 STEP2:Cognito によるユーザー認証の追加
  • Chapter 15 Hands-on #9: 深層学習を用いたアート自動生成アプリケーション
    • 15-1 Neural Art Canvas プロジェクト
    • 15-2 Neural style transfer
    • 15-3 ローカルで Neural style transfer を実行
    • 15-4 Step Functions
    • 15-5 Lambda layers
    • 15-6 アプリケーションの説明
    • 15-7 アプリケーションのデプロイ
    • 15-8 画像の生成 (コマンドラインから)
    • 15-9 画像の生成 (GUIから)
    • 15-10 アプリケーションの削除
  • Appendix 環境構築
    • A-1 本書で必要な計算機環境
    • A-2 AWS アカウントの取得
    • A-3 AWS のシークレットキーの作成
    • A-4 AWS CLI のインストール
    • A-5 AWS CDKのインストール
    • A-6 WSL のインストール
    • A-7 Docker のインストール
    • A-8 Python venv クイックガイド
    • A-9 ハンズオン実行用の Docker image の使い方

AWS Lambda実践ガイド 第2版 impress top gearシリーズ

AWS Lambda実践ガイド 第2版

ここからがLambdaの本格化書籍です。まずはLambdaの専門書といったらこれという一冊です。人気本で第二版も発売されてます。Lambdaの基礎からSAMを使った本格化開発まで記載されている良書です。Lambdaの専門書としてまずはこの本をおさえた方が良い内容になってます。

基本を抑えつつ現場で役に立つバージョン管理やエイリアスといった運用面の記載や、コールドスタートやコネクションなど現場で知っておかなければならない制限事項についても書かれており、本当にLambdaと言ったらこれという良書だと思います。

目次

  • 第1章 Lambda で実現するサーバーレスシステム
  • 第2章 Lambda 事始め
  • 第3章 Lambda の実行環境とイベント
  • 第4章 Lambda の開発環境と SAM
  • 第5章 S3 のイベント処理
  • 第6章 API Gateway、DynamoDB、SES との連携
  • 第7章 SQS やSNS を使った連携

AWSによるサーバーレスアーキテクチャ

AWSによるサーバーレスアーキテクチャ

アーキテクト部分からの一冊です。サーバレスアーキテクチャの本ということですが、Lambdaの解説書になっているのでLambdaの専門書という一面もあります。基礎からアーキテクトまで書かれていて参考になります。

目次

  • 第1部 導入
    • 第1章 サーバーレスの世界へ
      • 1.1 ここに至るまでの流れ
      • 1.2 サーバーレスアーキテクチャの原則
      • 1.3 サーバーからサーバーレスへの乗り換え
      • 1.4 サーバーレスの長所と短所
      • 1.5 まとめ
    • 第2章 アーキテクチャとパターン
      • 2.1 ユースケース
      • 2.2 アーキテクチャ
      • 2.3 パターン
      • 2.4 まとめ
    • 第3章 サーバーレスアプリケーションの構築
      • 3.1 24-Hour Video
      • 3.2 Amazon SNS の設定
      • 3.3 動画ファイルのアクセス権限の設定
      • 3.4 メタデータの生成
      • 3.5 仕上げ
      • 3.6 演習問題
      • 3.7 まとめ
    • 第4章 クラウドの設定
      • 4.1 セキュリティモデルとID管理
      • 4.2 ログとアラート
      • 4.3 料金
      • 4.4 演習問題
      • 4.5 まとめ
  • 第2部 コア機能
    • 第5章 認証と認可
      • 5.1 サーバーレス環境における認証
      • 5.2 24-Hour Videoへの認証の追加
      • 5.3 AWS との統合
      • 5.4 委任 トークン
      • 5.5 演習問題
      • 5.6 まとめ
    • 第6章 オーケストレーターとしての AWS Lambda
      • 6.1 AWS Lambdaの内部
      • 6.2 プログラミングモデル
      • 6.3 バージョニング、エイリアス、環境変数
      • 6.4 CLI の使い方
      • 6.5 AWS Lambdaのパターン
      • 6.6 Lambda関数のテスト
      • 6.7 演習問題
      • 6.8 まとめ
    • 第7章 Amazon API Gateway
      • 7.1 インターフェイスとしてのAmazon API Gateway
      • 7.2 Amazon API Gateway の操作
      • 7.3 ゲートウェイの最適化
      • 7.4 ステージとバージョン
      • 7.5 演習問題
      • 7.6 まとめ
  • 第3部 アーキテクチャの拡張
    • 第8章 ストレージ
      • 8.1 賢いストレージ
      • 8.2 セキュアなアップロード
      • 8.3 ファイルへのアクセス制限
      • 8.4 演習問題
      • 8.5 まとめ
    • 第9章 データベース
      • 9.1 Firebase 入門
      • 9.2 24-Hour VideoへのFirebase の追加
      • 9.3 ファイルへのアクセスの保護
      • 9.4 演習問題
      • 9.5 まとめ
    • 第10章 仕上げの学習
      • 10.1 デプロイとフレームワーク
      • 10.2 よりよいマイクロサービスのために
      • 10.3 AWS Step Functions
      • 10.4 AWS Marketplaceが開くビジネスチャンス
      • 10.5 これからの展開のために
  • 付録A サーバーレスアーキテクチャのためのサービス
    • A.1 Amazon API Gateway
    • A.2 Amazon SNS (Simple Notification Service)
    • A.3 Amazon 53 (Simple Storage Service)
    • A.4 Amazon SQS (Simple Queue Service)
    • A.5 Amazon SES (Simple Email Service)
    • A.6 Amazon RDS (Relational Database Service) & Amazon DynamoDB
    • A.7 Amazon CloudSearch
    • A.8 Amazon Elastic Transcoder
    • A.9 Amazon Kinesis Data Streams
    • A.10 Amazon Cognito
    • A.11 Auth0
    • A.12 Firebase
    • A.13 その他のサービス
  • 付録B インストールとセットアップ
    • B.1 システムの準備
    • B.2 IAM ユーザーとCLI のセットアップ
    • B.3 ユーザーアクセス権限の設定
    • B.4 新しいS3バケットの作成
    • B.5 IAMロールの作成
    • B.6 Lambda関数のための準備
    • B.7 Amazon Elastic Transcoder の設定
    • B.8 npm のセットアップ
  • 付録C 認証と認可について
    • C.1 認証と認可の基本
    • C.2 JWT
  • 付録D AWS Lambdaの内部
    • D.1 実行環境
    • D.2 制限
    • D.3 古いランタイムの扱い方
  • 付録 E モデルとマッピング
    • E.1 動画リストの取得
  • 付録 F Amazon S3のイベントメッセージ構造
    • F.1 S3 イベントメッセージの構造
    • F.2 覚えておくべきこと
  • 付録 G Serverless Framework & AWS SAM
    • G.1 Serverless Framework
    • G.2 AWS SAM
    • G.3 まとめ

AWS Amplify Studioではじめるフロントエンド+バックエンド統合開発

AWS Amplify Studioではじめるフロントエンド+バックエンド統合開発

最後はより実践編という位置付けでAmplifyを使った開発の内容の本にしました。Lambdaの基礎ではないため、これまでの本で基礎をおさえつつ再度のAmplifyでアプリ開発の本格入門という位置付けの本のしています。Amplify自体いろんな機能があって覚えることがたくさんなのですが、それら丁寧に解説してくれる内容になってます。

Lambdaの真価が問われるのはバックエンドとしてフロントエンドから連携されたアプリだと思っており、それを簡単に実現してくれるAmplifyには個人的には非常に期待しています。本の内容としては、Amplifyの一機能としてLambdaの使用方法を説明した内容になるのでLambdaに特化した内容ではないのですが、実業務でのLambdaで完結することはあまりないと思うで、一番実践に近い本だと思っています。

目次

  • Chapter1 AWS Amplifyを使おう
    • 1.1. AWS Amplifyを準備する
    • 1.2. サンプルアプリを作成する
  • Chapter2 Amplify Studioでバックエンドを設計する
    • 2.1. Reactアプリケーションの作成
    • 2.2. Amplify Studioとユーザー認証
    • 2.3. データモデルの設計
  • Chapter3 FigmaによるUI設計
    • 3.1. Figmaの基本操作
    • 3.2. Figmaでデザインする
    • 3.3. グラフィックの作成
    • 3.4. データモデル用のコンポーネントの利用
  • Chapter4 Reactによるフロントエンド開発
    • 4.1. Reactアプリケーションの基本
    • 4.2. Reactコンポーネントの設計
    • 4.3. ステートフックと副作用フック
    • 4.4. Reactコンポーネントを活用する
  • Chapter5 DataStoreによるデータベースアクセス
    • 5.1. コレクションコンポーネントの利用
    • 5.2. ReactとDataStoreの利用
    • 5.3. モデルデータの操作
  • Chapter6 GraphQLによるデータの利用
    • 6.1. Amplify MockとGraphQL
    • 6.2. コードからGraphQLを利用する
    • 6.3. GraphQLによるデータの書き換え
  • Chapter7 S3ストレージとLambda関数
    • 7.1. Amazon S3の利用
    • 7.2. ファイルの基本操作
    • 7.3. Lambda関数の利用
    • 7.4. LambdaからAWSの機能を使う
  • Chapter8 JavaScriptベースによるフロントエンド開発
    • 8.1. JavaScriptベースのアプリケーション作成
    • 8.2. Amplifyの機能を利用する

LINE Messaging API を使ってAWS Lambdaに連携する

概要

最近、LINE Messaging API x AWS Lambda x Google スプレッドシート を連携してサクッと便利ツールを作っている日々です♪
個人的にサクッと作れる便利ツールとして、UILINE Messaging APIにして、ロジックAWS Lambdaに、DBGoogle スプレッドシートで作るという構成が簡単に出来て気に入っています。

そこで今回はLINE Messaging API x AWS Lambda の連携編になります。
LINE Messaging API と AWS Lambda をWebhookを使ってHTTPSで連携します。
LINE Developersアカウントがあれば誰でも簡単・無料で出来るので、気になった人はやってみてください。

Lambdaの良書が増えてきたので、おすすめ本5冊を以下で紹介しています。Lambdaについて本格的に勉強したい方はぜひ読んでみてください。

AWS Lambdaおすすめ書籍5冊の紹介

前提条件

  • LINE Developersアカウント(LINE Business ID)取得
    • 無料で作成できるのでアカウントがない人は取得しましょう。
  • AWS Lambda関数の HTTP(S) エンドポイント割り当て
    • LINEからLambdaに連携するために、Lambda関数にHTTP(S) エンドポイントが必要になります。Lambdaを作成する際に、関数 URL を有効化する方法が簡単ですが、アクセス制御はしっかり行ってHTTP(S) エンドポイントを公開しましょう。

公式ページ

Messaging APIを始めよう
https://developers.line.biz/ja/docs/messaging-api/getting-started/

LINE Developersにログイン

まずは、LINEにログインします。
LINE Developersアカウント(LINE Business ID)を作成していない人はアカウントを作成から作成します。

https://account.line.biz/login

プロバイダー/チャネル作成 

ログイン後、LINE DevelopersコンソールというLINEの画面でプロバイダーチャネルを作成します。

  • プロバイダー:サービスを提供し、ユーザーの情報を取得する開発者個人、企業、または団体等のことです。個人で運用する場合は自由に登録すれば良いと思います。
  • チャネル:LINEが提供する機能を、サービス提供者が利用するための通信路です。今回の場合は、AWS Lambdaと LINE Messaging API を使って連携するLINEアプリのことです。

参考ページ
https://developers.line.biz/ja/docs/line-developers-console/overview/

プロバイダー作成

それではプロバイダーから作成していきます。
LINE Developersコンソールの画面操作だけで作成できるので簡単です。

LINE Developersコンソールにログイン後、表示される左メニューのプロバイダーを選択しし、プロバイダーの作成を選択します。

作成画面が表示されるため、任意のプロバイダー名を入力し、作成ボタンで作成されます。
簡単ですね。

チャネル作成

続いてチャネルの作成に移ります。
こちらもLINE Developersコンソールの画面操作だけで作成できます。

プロバイダーを選択する(今回は、AWS Lambdaとの連携が目的なのでLambdaProviderというプロバイダーを作成しています)と、チャネルの設定画面が表示されるため、AWS Lambdaと連携する Messaging API をクリックします。

チャネルの種類Messaging API になっていること、プロバイダー作成したプロバイダーになっていることを確認しましょう。あとは、必須項目を自身の情報で入力すれば大丈夫です。
利用規約を確認して、作成ボタンを押しましょう。

チャネル設定

無事にチャネルが作成されたらチャネルの設定に移ります。
この設定作業でAWS Lambdaとの連携情報認証情報LINEアプリの応答メッセージの設定などを行います。

作成したチャネルを選択します。(今回は、MessagingLambdaというチャネル名で作成しています)

Messaging API設定をクリックします。

ページ中央にあるWebhook URLに連携するAWS Lambdaの公開されたURLを編集ボタンを押して設定します。

URLを設定すると、検証ボタンやWebhookの利用Webhookの再送エラーの統計情報の設定ができるようになるので、必要な設定を行います。
Webhookの利用は必ず必要になるので、必ずONにします。

ページ下部にあるチャネルアクセストークンを設定します。
LambdaからこのLINEアプリに連携する際に指定するキーになる情報なので外部に漏れないないように管理しましょう。

応答/あいさつメッセージのカスタマイズ

ページ中央部にある応答メッセージあいさつメッセージ編集ボタンからこのLINEアプリにメッセージを送った時の応答メッセージLINEアプリを友だち追加した時のあいさつメッセージを編集できます。必要であれば編集しましょう。

編集ボタンをクリックすると以下の応答設定用の専用画面が表示されるため、専用画面で設定を変更します。

今回、AWS Lambdaから応答メッセージを自動作成して送信予定のため、応答メッセージオフに設定します。

AWS Lambdaの実装

いよいよここからはAWS Lambda側の実装になります。
前提条件のところに書きましたが、LINEと連携するためHTTP(S) エンドポイントが公開されているLambda関数の使用が前提になります。
今回は、最も基本的なLINEからLambdaにメッセージを送った時にLambdaからLINEへの応答メッセージを送る方法と、LambdaからLINEへプッシュメッセージを送る方法を記載します。

応答メッセージを送る

LINEからLambdaにメッセージを送った時に送られたメッセージをもとにして応答メッセージを送信するAPIは以下になります。

https://developers.line.biz/ja/reference/messaging-api/#send-reply-message

具体的には以下のhttpsリクエストを応答メッセージとして送信する必要があります。

curl -v -X POST https://api.line.me/v2/bot/message/reply \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {channel access token}' \
-d '{
    "replyToken":"{Reply token}",
    "messages":[
        {
            "type":"text",
            "text":"Hello, user"
        },
        {
            "type":"text",
            "text":"May I help you?"
        }
    ]
}'

  • channel access token:LINE Developersコンソールで発行したチャネルアクセストークンを設定します。
  • Reply token:メッセージのやり時の度にLINEからLambdaに送られたリクエストメッセージの中からReply tokenを取得して設定します。

LINEから送られてくるリクエストメッセージのReply tokenの具体例は以下になります。replyToken部から取得しましょう。
text部にLINEから送られてくるメッセージが格納されており、userId部がLINEの送信者を特定するIDになります。(userIdは次の「プッシュメッセージを送る」で使用します)

{
  "destination": "xxxxxxxxxx",
  "events": [
    {
      "type": "message",
      "message": {
        "type": "text",
        "id": "14353798921116",
        "text": "Hello, world"
      },
      "timestamp": 1625665242211,
      "source": {
        "type": "user",
        "userId": "U80696558e1aa831..."
      },
      "replyToken": "{Reply token}",
      "mode": "active",
      "webhookEventId": "01FZ74A0TDDPYRVKNK77XKC3ZR",
      "deliveryContext": {
        "isRedelivery": false
      }
    }
  ]
}

実際にLambdaで replyToken を取得して、応答メッセージを送信するサンプルは以下になります。

import json
import requests
from pprint import pprint

def lambda_handler(event, context):
    #=========================
    # LINEからのリクエスト解析
    #=========================
    bodyjson = json.loads(event['body'])
    messagetext = bodyjson['events'][0]['message']['text']
    print("messagetext: " + messagetext)
    replyToken = bodyjson['events'][0]['replyToken']
    print("replyToken: " + replyToken)

    #=========================
    # LINEへのレスポンス作成
    #=========================
    resmessage = [
        {'type':'text','text':messagetext}
    ]
    payload = {'replyToken': replyToken, 'messages': resmessage}
    # カスタムヘッダーの生成(dict形式)
    headers = {'content-type': 'application/json', 'Authorization': 'Bearer XXX'}
    # headersにカスタムヘッダーを指定
    r = requests.post("https://api.line.me/v2/bot/message/reply", headers=headers, data=json.dumps(payload))
    print("LINEレスポンス:" + r.text)
    return

LINEから送られてきたメッセージをそのまま送り返すサンプルになっています。
LINEからのメッセージと replyToken を取得して、応答メッセージ用のhttpsリクエストに設定して送信すればOKです。簡単ですね。

プッシュメッセージを送る

次はLambdaからLINEにプッシュメッセージを送るAPIは以下になります。

https://developers.line.biz/ja/reference/messaging-api/#send-push-message

具体的には以下のhttpsリクエストを送信することでプッシュメッセージを送ることができます。

curl -v -X POST https://api.line.me/v2/bot/message/push \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {channel access token}' \
-d '{
    "to": "{user id}",
    "messages":[
        {
            "type":"text",
            "text":"Hello, world1"
        },
        {
            "type":"text",
            "text":"Hello, world2"
        }
    ]
}'

  • channel access token:LINE Developersコンソールで発行したチャネルアクセストークンを設定します。
  • user id:プッシュメッセージを送りたい相手の userId を設定します。(userId は「応答メッセージを送る」で確認したLINEからメッセージが送られてきた時のリクエスト内容から確認できます)

実際にLambdaでプッシュメッセージを送信するサンプルは以下になります。

import json
import requests
from pprint import pprint

def lambda_handler(event, context):
    #===========================
    # LINEへのPUSHメッセージ作成
    #===========================
    resmessage = [
        {'type':'text','text':'メッセージ1'},
        {'type':'text','text':'メッセージ2'}
    ]
    payload = {'to': 'XXX', 'messages': resmessage}
    # カスタムヘッダーの生成(dict形式)
    headers = {'content-type': 'application/json', 'Authorization': 'Bearer XXX'}
    # headersにカスタムヘッダーを指定
    r = requests.post("https://api.line.me/v2/bot/message/push", headers=headers, data=json.dumps(payload))
    print("LINEレスポンス:" + r.text)
    return
  • ['to': 'XXX']:プッシュメッセージを送りたい相手の userId を設定します。
  • [Bearer XXX]:LINE Developersコンソールで発行したチャネルアクセストークンを設定します。

送信先のuserIdとチャネルアクセストークンを設定するだけになります。こちらも簡単ですね。

まとめ

今回は、LINE Messaging API x AWS Lambda の連携編を説明しました。
基本的にはLINE Developersコンソール上の設定作業がメインで、Lambda上の実装はMessaging API のリファレンス通りやるだけなので比較的簡単にできたのではないでしょうか。
これで何かあったらLINEにメッセージを送るとツールを動かせたり、定期的にLambdaを起動してLINEにメッセージを送ったりできるので簡単なツールだったら自分でサクッと作ってしまおうかなと思って個人的には気に入っている方法になります。
興味がある方がいたらぜひやってみてください。

AWS Lambda で Python プログラミング~Boto3 サンプル集S3編~

概要~個人的によく使うLambda(Python Boto3)のサンプル集~

Lambdaプログラミングしていて良く使うコードサンプルをまとめていきます。
随時更新予定です(^^)

公式APIリファレンス

AWS公式のAPIリファレンスは以下のURLで何かあったら参照して正しく使いましょう。

https://boto3.amazonaws.com/v1/documentation/api/latest/index.html

S3サンプル

S3ファイルダウンロード

ダウンロードするS3のバケット名、オブジェクト名、Lambdaのローカル環境に保存するファイル名を指定します。
ローカル環境上に同名のファイルがあった場合は上書きされます。

import boto3
s3 = boto3.client('s3')

# バケット名、ダウンロードオブジェクト名、ダウンロードファイル名
S3_BUCKET_NAME = 'sample-bucket'
S3_OBJECT_DOWNLOAD_FILE_NAME = 'sample.jpg'
LOCAL_SAVE_FILE_NAME = '/tmp/sample.jpg'

def lambda_handler(event, context):

    # S3ダウンロード
    s3.download_file(S3_BUCKET_NAME, S3_OBJECT_DOWNLOAD_FILE_NAME, LOCAL_SAVE_FILE_NAME)

    return

S3ファイルアップロード

アップロード先のS3のバケット名、オブジェクト名、Lambdaのローカル環境にあるアップロード対象のファイル名を指定します。
Lambdaのローカル環境にアップロードファイルがある前提のため、事前にアップロードファイルは作成しておく必要があります。
S3上に同名のオブジェクトがあった場合は上書きされます。

import boto3
s3 = boto3.client('s3')

# バケット名、オブジェクト名、アップロード対象ファイル名
S3_BUCKET_NAME = 'sample-bucket'
S3_OBJECT_UPLOAD_FILE_NAME = 'S3sample.jpg'
LOCAL_UPLOAD_FILE_NAME = '/tmp/sample.jpg'

def lambda_handler(event, context):

    # TODOアップロード対象ファイル作成処理

    # S3アップロード
    s3.upload_file(LOCAL_UPLOAD_FILE_NAME, S3_BUCKET_NAME, S3_OBJECT_UPLOAD_FILE_NAME)

    return

S3ファイルダウンロード(メモリ上で扱う場合)

ローカル環境に保存せず、そのままメモリ上でバイトデータとして扱う場合は以下になります。

import boto3

s3 = boto3.resource('s3')

# バケット名、ダウンロードオブジェクト名
S3_BUCKET_NAME = 'sample-bucket'
S3_OBJECT_DOWNLOAD_FILE_NAME = 'sample.jpg'

def lambda_handler(event, context):
    # S3ダウンロード
    bucket = s3.Bucket(S3_BUCKET_NAME)
    obj = bucket.Object(S3_OBJECT_DOWNLOAD_FILE_NAME)
    response = obj.get()
    body = response['Body'].read()

    # 別のBotoクライアントにバイトのまま渡す場合
    # response = client.xxxxxx(Image={'Bytes': body})
    # ダウンロードファイルが画像ではなくJSONファイルの場合
    # print(json.loads(body.decode('utf-8')))

S3ファイルのリスト表示

ファイルのリスト表示するS3のバケット名を指定します。

import boto3

s3 = boto3.client('s3')

# バケット名
S3_BUCKET_NAME = 'sample-bucket'

def lambda_handler(event, context):

    # オブジェクトのリスト表示
    response = s3.list_objects_v2(Bucket=S3_BUCKET_NAME)

    for obj in response['Contents']:
        print(obj['Key'])

    return

S3事前署名付きURL発行

S3に保存してあるファイルを一時的に公開するときに使用する事前署名付きURLを発行するサンプルです。
事前署名付きURLを発行するS3のバケット名、オブジェクト名、URLの有効期限(秒)を指定します。

import boto3
s3 = boto3.client('s3')

# バケット名、事前署名URL発行オブジェクト名、有効期限(秒)
S3_BUCKET_NAME = 'ai-sample-bucket'
S3_OBJECT_PRESIGNES_FILE_NAME = 'sample.jpg'
EXPIRATION = 3600

def lambda_handler(event, context):

    # 事前署名URL発行
    response = s3.generate_presigned_url('get_object',
        Params={'Bucket': S3_BUCKET_NAME,
        'Key': S3_OBJECT_PRESIGNES_FILE_NAME},
        ExpiresIn=EXPIRATION)

    print(response)

    return

参考

Boto3ライブラリは、boto3.clientとboto3.resourceの2つの主要なクラスがあります。

boto3.clientは、AWSサービスのAPIアクションを呼び出すためのクライアントを提供します。例えば、s3クライアントを作成して、S3バケットを作成したり、オブジェクトを取得したり、削除したりすることができます。これは低レベルのAPIであり、AWSリソースとのやり取りがJSONデータで行われます。

一方、boto3.resourceは、AWSリソースを表すPythonオブジェクトを提供します。これは、より高レベルのAPIであり、AWSリソースとPythonオブジェクトのやり取りが可能です。例えば、s3リソースを使用して、バケットを作成したり、オブジェクトを取得したり、削除したりすることができます。また、オブジェクトにはファイルの内容にアクセスするためのread()メソッドがあり、オブジェクトに対してPythonオブジェクトと同じように操作できます。

それぞれで出来ること、やりたいことを理解して使い分けましょう。

EC2(WindowsServer)にSSM Fleet Managerを使ってRDP接続してみる

概要~AWSのEC2(WindowsServer)にAWS Systems Managerのフリートマネージャーを使ってRDP接続してみる~

皆さんはWindowsサーバのEC2を立てた後、どのようにRDP接続していますか?
私はしばらく踏み台サーバを構築して踏み台サーバを経由してアクセスしていたのですが、最近Fleet Managerの存在を知ったのですぐに乗り換えました(^^)便利です。
ただ、乗り換えにあたりいくつか注意点があったのでその内容を共有します。

Fleet Manager RDP接続切り替え手順

今回の記事内容は既存のEC2インスタンスにFleet Managerで接続する接続方法の切り替え手順になります。EC2インスタンスを新しく作成してFleet Managerから接続するのは以下の記事のように比較的簡単に出来るのでそれらの記事を参照にしてください。

【RDP切り替え手順】

  1. RDP接続先のEC2のIAMロールにAmazonSSMManagedInstanceCoreを追加する
  2. SSM Agentを最新化する
  3. RDP接続ポートを3389にする

それでは手順の詳細を説明していきます。

手順1:EC2のIAMロールにAmazonSSMManagedInstanceCoreを追加

Fleet Managerを使ったRDP接続には専用のIAMポリシーが追加になります。AmazonSSMManagedInstanceCoreというポリシーをIAMロールに追加しましょう。
AmazonSSMFullAccessというポリシーもありますが、このポリシーではダメなので注意しましょう。

このIAMポリシーを持つIAMロールをEC2に付与することで、フリートマネージャーが該当のEC2をマネージドノードとして認識でき、フリートマネージャーのマネージドノードの一覧に表示されるようになります。(反映には時間がかかるようで私の場合、IAMロール設定後10分程度一覧に表示されるまで時間がかかりました)

ちなみにこの画面はAWS Systems Managerのトップ画面の左メニューにあるフリートマネージャーをクリックすることで遷移できます。

手順2:SSM Agentを最新化する

手順1を実行することで基本的にはフリートマネージャーからのRDP接続の準備が完了なのですが、私の場合、これだけではエラーになったため、あと2つの切り替え手順を記載します。ちなみにこの状態でRDP接続をするためには、フリートマネージャーの画面から ノードアクションリモートデスクトップ(RDP)との接続をクリックします。

表示されたリモートデスクトップ接続画面からユーザー認証情報(通常のRDP接続に使っているID/パスワード認証)か、キーペア(EC2作成時に指定したキーペアを使った認証)かを選び、Connectをクリックします。

接続を試みているようですが、、、

最終的にはリモートデスクトップセッションが確立できないとかで、タイムアウトしました。。。

この原因を調査したのですが、直接的な原因がわからず、結論としてはSSM Agentのバージョンアップをしたところ、解決しました。
(私のEC2は2020年頃作成したもので、SSMエージェントは自動インストールされており、当時のバージョンは3.0.XXX.Xくらいだったと思います)

SSM Agentの最新化は以下のフリートマネージャーの画面操作で簡単にできます。
アカウント管理SSMエージェントの自動更新

↓のバージョンが上がったことを確認しましょう。

手順2は以上です。

手順3:RDP接続ポートを3389にする

この手順は敢えてデフォルトのRDP接続ポートを変更していた人のみの手順になります。
私はポート変更していたので、ここで結構ハマリました。
なぜなら3389以外のポートにしていた場合、エラーメッセージが↓の認証エラーでした。。。

リモートデスクトップセッションを確立できません。
有効な認証情報が提供されていること、および指定したユーザがリモートデスクトップ経由でログインできることを確認してください。
Authentication failed

このエラーに従って接続ユーザやキーペアの正当性を繰り返し確認したり、新しいログインユーザを作成してみたり、もう一台EC2を立ち上げてそのEC2からの接続を試みたり等、試しましたが、その場合は正しくRDPできてしまう。。。ただ、フリートマネージャーを使ったRDPは必ずこのエラーになってしまう。。。

既存のEC2からの乗り換えは難しいかと諦めかけたときにそう言えばデフォルトのRDPポートを変えていることに気が付き、もしかしたらと試してみたら無事に接続できました。

リモートデスクトップのデフォルトポートの変更はAWSとは関係のない話しなので変更手順は以下のサイト等参照にします(^^;)

https://learn.microsoft.com/ja-jp/windows-server/remote/remote-desktop-services/clients/change-listening-port

以上です!

まとめ

私の環境ではいくつかのエラーが発生し乗り換え手順が必要になりましたが、基本的にはフリートマネージャーはかなり便利に使えると思います。
いくつか接続後の注意点としては、

  1. ファイル共有が出来ない(ローカル環境からファイルをコピペするが出来ない)ことは、S3をローカル環境とEC2のファイル共有に利用することが運用しています。
  2. 日本語が入力できない

ことが挙げられ、2番目(今のところ出来なそう)はなんとかならないかなぁと思っているところです。

Pythonのseleniumを使ってWeb画面操作を自動化する~サンプルを使ってWebスクライピンングのチャレンジ~

概要~Pythonのseleniumを使って値取得/値入力/クリック/スクリーンショット等、画面操作を自動化する~

少しずつ毎日のWeb生活を快適にするために最近は Python selenium 使ったWeb画面自動化処理を行っています。
今回の投稿は、その自動化処理をする上で繰り返し使える基本コードのまとめから、ちょっとした便利ノウハウをまとめた内容になっています。
これを実行すれば誰でも簡単にWeb画面の自動化処理ができ、日々単調なルーチンワークから脱却できるので、ぜひ試してみてください。

対象読者

  • Pythonを触ったことがある人
  • ブラウザの自動化で楽したい人

出来るようになること

  • Web画面を自動操作する
    • 画面を開く
    • 特定のHTML要素から値を取得する
    • 特定のHTML要素に値を入力する
    • ボタンをクリックする
  • Web画面のスクリーンショットを取得する

事前準備

今回は selenium を使って自動化します。その為に2つの事前準備が必要なのでその準備をします。

事前準備1~ブラウザのドライバをダウンロード~

Web画面の自動化はブラウザの専用ドライバーを使って画面操作を行います。そのためにドライバーをダウンロードします。ドライバーはブラウザ別に用意されているのでお好みのブラウザのドライバーをダウンロードしましょう。(どのドライバーでも自動化のソースコードは同じです)

chromeドライバー

https://chromedriver.chromium.org/downloads

Edge ドライバー

https://developer.microsoft.com/ja-jp/microsoft-edge/tools/webdriver/

事前準備2~seleniumのインストール~

これはpipを使うだけで簡単です。以下のコマンド実行します。

pip install selenium

事前準備は以上です!では、さっそくWeb画面の自動化にチャレンジしていきましょう!!

補足~Google Colaboratoryでのselenium実行~

seleniumu は Google Colaboratory でも実行できるので、サクッと試してみたい時はこちらがおすすめです。
その場合、ドライバー、selenium のインストールは以下のコマンドを実行しておくことになります。

!apt-get update
!apt install chromium-chromedriver
!cp /usr/lib/chromium-browser/chromedriver /usr/bin
!pip install selenium
!pip freeze

Google Colaboratory では、日本語フォントがインストールされていないので文字化けが発生する場合は、以下のコマンドで日本語フォントをダウンロードします。

# 日本語フォントをダウンロードする。
!apt-get -y install fonts-ipafont-gothic

それでは、本編に戻ります!

ページを開く

最初は最も基本的な特定のページを開く操作です。
getメソッドに開きたいページのURLを指定します。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By

# ドライバーの設定
CHROME_DRIVER = "{ドライバーの格納フォルダ}\\chromedriver"

driver = webdriver.Chrome(CHROME_DRIVER,options=Options())

# 指定したURLの画面を開く
driver.get("http://www.google.com")

# ブラウザを閉じる
driver.quit()

【参考ドキュメント】
https://www.selenium.dev/ja/documentation/webdriver/drivers/options/#normal-default

サンプルソースの{ドライバー格納フォルダ}を自身でドライバーを保存したフォルダに変更して実行してみてください。(サンプルはChromeドライバーを使った例になります)

簡単にブラウザの特定のページが開けたと思います。すぐに閉じてしまっているので一瞬だったかもしれませんが、ここから自動化処理のスタートです!

要素を探す

seleniumで実現したいことはWeb画面の自動操作だと思います。
その中で最も基本的なことは操作したい要素を探す(指定する)ことであり、慣れるまでここが最も大変と思うのでこの部分を重点的に説明します。

要素の選択(指定)

要素の選択(指定)方法には以下の8種類があります。
(ロケータと呼びます)

ロケータ詳細コード例
idid属性が一致する要素を探すdriver.find_element(By.ID,"abc")
class nameclass名に値を含む要素を探す(複合クラス名は使えない)driver.find_elements(By.CLASS_NAME,"abc")
css selectorCSSセレクタが一致する要素を探すdriver.find_elements(By.CSS_SELECTOR,"abc")
xpathXPathと一致する要素を探すdriver.find_elements(By.XPATH,"abc")
link texta要素のテキストが一致する要素を探すdriver.find_elements(By.LINK_TEXT,"abc")
partial link texta要素のテキストが部分一致する要素を探すdriver.find_elements(By.PARTIAL_LINK_TEXT,"abc")
namename属性が一致する要素を探すdriver.find_elements(By.NAME,"abc")
tag nameタグ名が一致する要素を探すdriver.find_elements(By.TAG_NAME,"abc")

【参考ドキュメント】
https://www.selenium.dev/ja/documentation/webdriver/elements/locators/#%E8%A6%81%E7%B4%A0%E9%81%B8%E6%8A%9E%E3%81%AE%E6%96%B9%E6%B3%95

この表は私が良く使う順に記載しています。
次から具体的に説明していきますが、コード例のidだけはfind_element最後の(s)がないことに注意してください。
これはidだけはページ内で1つというHTMLのルールがあるため、1つの要素が返却されることを意味しています。
ちなみに他のロケータにもfind_elementがありますが、先頭の1つの要素が取れるという仕様になっており、限られたケースでしか使用しないため、私は基本的にはid以外はfind_elements最後の(s)ありのメソッドを使用しています。

使用優先度1:ID指定による要素選択

ここからは具体例とともにそれぞれの要素選択方法のサンプルを記載していきます。
まずはID指定です。取得したい要素にid指定の属性があった場合、迷わずこれを使えば良いと思います。

サンプルコード

login_form = driver.find_element(By.ID, 'loginForm')

サンプルHTML

<html>
 <body>
  <form id="loginForm">
   <input name="username" type="text" />
   <input name="password" type="password" />
   <input name="continue" type="submit" value="Login" />
  </form>
 </body>
</html>

実行結果サンプル

login_form = driver.find_element(By.ID, 'loginForm')
print(login_form.tag_name)
# form が出力される

【参考ドキュメント】
https://selenium-python.readthedocs.io/locating-elements.html#locating-by-id

これで formタグの要素(element)が取得できます。id属性があれば最も簡単です。

使用優先度2:クラス名指定による要素選択

次はクラス名指定に要素選択方法です。
idがなくてもクラスが指定されているケースはよくあるので良く使用しています。
(普段使うのは find_elements メソッドですが、サンプルをわかりやすくするため find_element (sなし)メソッドを使っています。)

コードサンプル

content = driver.find_element(By.CLASS_NAME, 'content')

HTMLサンプル

<html>
 <body>
  <p class="content">Site content goes here.</p>
</body>
</html>

実行結果サンプル

content = driver.find_element(By.CLASS_NAME, 'content')
print(content.text)
# Site content goes here.

【参考ドキュメント】
https://selenium-python.readthedocs.io/locating-elements.html#locating-elements-by-class-name

クラス名に複数の指定がある場合

クラス名指定は結構便利ですが、1つ注意点があります。
クラス名はよく複数指定されるケースがあります。↓こんな場合、

<div class="content test">TEST</div>

こんな場合は、.(ドット)区切りで指定します。

content = driver.find_element(By.CLASS_NAME, 'content.test')

これで取得できるのですが、たまに取得出来ないケースがあるようです。
私の場合は、AWS Lambdaがこのケースで取得できず、結局次のCSSセレクタを使って取得しました。(ちなみにLambdaは、Python 3.7 Selenium 3.141でした)

使用優先度3:CSSセレクタ指定による要素選択

次にCSSセレクタです。
CSSに詳しい人なら迷わず使えますね。私もクラス名指定の所で書いたクラス名指定ができないケースがあるので、最近はこれを一番多用しています。

コードサンプル

content = driver.find_element(By.CSS_SELECTOR, 'p.content')

HTMLサンプル

<html>
 <body>
  <p class="content">Site content goes here.</p>
</body>
</html>

実行結果サンプル

content = driver.find_element(By.CSS_SELECTOR, 'p.content')
print(content.text)
# Site content goes here.

【参考ドキュメント】
https://selenium-python.readthedocs.io/locating-elements.html#locating-elements-by-css-selectors

クラス名にタブを.(ドット)区切りで指定というクラス名指定と同じようなシンプルな指定で使えるので簡単ですね。

クラス名の所で記載した複数のクラスが指定されていた場合の指定方法は以下になります。

# <div class="a b">text</div> の場合
find_element(By.CLASS_NAME, "a.b")# ←Lambda NG
find_element(By.CSS_SELECTOR, "div.a.b")# ←OK
find_element(By.XPATH, "//div[@class='a b']")# ←XPathのこれもOK

使用優先度4:XPath指定による要素選択

次はXPathによる指定です。
XPathは柔軟な指定ができるので、私はクラス名やCSSセレクタ指定で複数の要素が取得できてしまうケースで、敢えて1つのみの要素を指定したい場合とかに使用しています。

コードサンプル

login_form = driver.find_element(By.XPATH, "/html/body/form[1]")
login_form = driver.find_element(By.XPATH, "//form[1]")
login_form = driver.find_element(By.XPATH, "//form[@id='loginForm']")

HTMLサンプル

<html>
 <body>
  <form id="loginForm">
   <input name="username" type="text" />
   <input name="password" type="password" />
   <input name="continue" type="submit" value="Login" />
   <input name="continue" type="button" value="Clear" />
  </form>
</body>
</html>

実行結果サンプル

login_form = driver.find_element(By.XPATH, "/html/body/form[1]")
login_form = driver.find_element(By.XPATH, "//form[1]")
login_form = driver.find_element(By.XPATH, "//form[@id='loginForm']")
print(login_form.tag_name)
# form が出力される(どの指定方法でも同じ結果です)

【参考ドキュメント】

https://selenium-python.readthedocs.io/locating-elements.html#locating-by-xpath

3種類の指定方法をサンプルにしていますが、取得結果はどれも同じです。

  1. htmlタグからの絶対パスで指定しています。直感的な指定が可能ですが、HTML構造が変わったら取得できなくなるので使用ケースは注意が必要です
  2. formタグの1つ目という指定です。// を使用すること絶対パス指定が不要になります。
  3. formタグ、かつ、id指定という指定の仕方です。2.のケースよりわかりやすく指定できますね。

使用優先度5:a要素テキスト指定による要素選択

次はa(アンカー)タグ内のテキスト指定する方法になります。
完全一致で指定する LINK_TEXT と 部分一致で指定する PARTIAL_LINK_TEXT があります。

コードサンプル

continue_link = driver.find_element(By.LINK_TEXT, 'Continue')
continue_link = driver.find_element(By.PARTIAL_LINK_TEXT, 'Can')

HTMLサンプル

<html>
 <body>
  <p>Are you sure you want to do this?</p>
  <a href="continue.html">Continue</a>
  <a href="cancel.html">Cancel</a>
</body>
</html>

実行結果サンプル

continue_link = driver.find_element(By.LINK_TEXT, 'Continue')
print(continue_link.text)
# Continue
continue_link = driver.find_element(By.PARTIAL_LINK_TEXT, 'Can')
print(continue_link.text)
# Cancel

【参考ドキュメント】
https://selenium-python.readthedocs.io/locating-elements.html#locating-hyperlinks-by-link-text

この指定方法は、Web画面に表示されている文字をそのまま使えるので結構便利だったりします。ただ、最近のWebでアンカータグがどれだけあるのか、画面表示の文字は頻繁に変わらないか、と考えると使用ケースは限られるため、個人的に使用ケースがあまりないかなと思っています。

使用優先度6:name属性指定による要素選択

次はname属性による指定です。
先に紹介したクラス名指定とよく勘違いするのですが、こちらはname属性になります。

コードサンプル

username = driver.find_element(By.NAME, 'username')
password = driver.find_element(By.NAME, 'password')

HTMLサンプル

<html>
 <body>
  <form id="loginForm">
   <input name="username" type="text" />
   <input name="password" type="password" />
   <input name="continue" type="submit" value="Login" />
   <input name="continue" type="button" value="Clear" />
  </form>
</body>
</html>

実行結果サンプル

username = driver.find_element(By.NAME, 'username')
print(username.tag_name)
# input

【参考ドキュメント】
https://selenium-python.readthedocs.io/locating-elements.html#locating-by-name

この指定ケースも直感的でわかりやすいのですが、name属性が指定された要素がそんなにあるかな。。。という印象なので、どちらかというとクラス名指定を良く使っています。

使用優先度7:タグ名指定による要素選択

最後は、タグ名による指定です。
これはシンプルですね。タグ名をそのまま指定します。

コードサンプル

heading1 = driver.find_element(By.TAG_NAME, 'h1')

HTMLサンプル

<html>
 <body>
  <h1>Welcome</h1>
  <p>Site content goes here.</p>
</body>
</html>

実行結果サンプル

heading1 = driver.find_element(By.TAG_NAME, 'h1')
print(heading1.text)
# Welcome

【参考ドキュメント】
https://selenium-python.readthedocs.io/locating-elements.html#locating-by-name

使用方法はシンプルなのですが、タグの直接指定がそれほど使用ケースがない気がしているので最後の紹介になります。

補足~便利?コードサンプル~

ここからは私が個人的に良く使っているコードサンプルの紹介です。

要素取得判定

冒頭で記載した通り、私は find_elements(find_elementではなく)を多用しています。
そのため、要素が意図通り取得できたかを以下のように確認するメソッドを使って判定しています。

コードサンプル

# 要素取得判定関数
def element_check(element, target):
    if len(element) > 0:
        print(target + " 個数:" + str(len(element)))
        return True
    else:
        print(target + ":要素が見つかりません")
        return False

# 要素取得判定関数呼び出し例
elements = driver.find_elements(By.TAG_NAME, 'a')
if element_check(elements, 'a') :
    for element in elements:
        print(element.text)

要素に対する操作コードサンプル

要素を取得した後の操作は基本的には以下の操作と思っています。
これだけは覚えておけば基本的な操作はできるかなというものを記載しておきます。

  • テキスト取得
  • テキスト入力
  • テキストクリア
  • クリック

コードサンプル

element.text
element.send_keys("abc")
element.clear()
element.click()

ページのタイトルを取得する

各処理を始める前にまずタイトルを取得する場合、以下のコードで簡単に取得できます。

driver = webdriver.Chrome(CHROME_DRIVER,options=Options())
print(driver.title)

HTMLを表示する

意図した動作にならない場合など、実際のHTMLを確認したいケースがあると思います。その時は、以下のコードでHTMLを取得します。

driver = webdriver.Chrome(CHROME_DRIVER,options=Options())
print(driver.page_source.encode('UTF-8'))

スクリーンショットを保存する

Web画面の操作結果を保存する、意図した動作になっているか途中の画面遷移を保存する際に利用するのがスクリーンショットです。
seleniumu では簡単にスクリーンショットを取ることができます。

コードサンプル

# スクロールを含めて全画面のスクリーンショットを取得するためヘッドレスモードにする
options = Options()
options.add_argument('--headless')
driver = webdriver.Chrome(CHROME_DRIVER,options=options)

# 全画面のスクリーンショット取得
w = driver.execute_script('return document.body.scrollWidth')
h = driver.execute_script('return document.body.scrollHeight')
driver.set_window_size(w, h)
driver.save_screenshot("screenshot.png")

指定した要素部分のみの一部のスクリーンショットを保存する

上の例だと全画面表示になってしまうため、必要な要素部分だけ取得したい場合(例えば特定のframe内だけ取得したい場合)は事前に要素取得してから、スクリーンショットを保存します。

# スクロールを含めて全画面のスクリーンショットを取得するためヘッドレスモードにする
options = Options()
options.add_argument('--headless')
driver = webdriver.Chrome(CHROME_DRIVER,options=options)

# 一部要素の全画面スクリーンショット取得
w = driver.execute_script('return document.body.scrollWidth')
h = driver.execute_script('return document.body.scrollHeight')
driver.set_window_size(w, h)
# 取得したい要素を指定する(ID指定でなくても良い)
element = driver.find_element(By.ID, 'main-element')
png = element.screenshot_as_png
# 任意のファイル名を指定してpngファイルに保存
with open('ElementScreenshot.png', 'wb') as f:
    f.write(png)

ドライバーの自動更新

ドライバーのバージョンは自分が普段使用するブラウザのバージョンと一致しておく必要があるため、ブラウザのバージョンアップが定期的にあり、その度にドライバーのダウンロードが面倒という方は以下でドライバーのインストール含めて自動化できます。

# Webdriver ManagerでChromeDriverを取得
from webdriver_manager.chrome import ChromeDriverManager
# ChromeDriverManager().install()を指定して自動インストール
driver = webdriver.Chrome(ChromeDriverManager().install(),options=options)

ちなみにブラウザとドライバーのバージョンは不一致の場合、以下のエラーメッセージが出力され、処理が異常終了します。

selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 104
Current browser version is 106.0.5249.119 with binary path C:\Program Files (x86)\Google\Chrome\Application\chrome.exe

逆引き参考ページ

以下はseleniumでやりたいことの目的別に逆引きができるサイトになります。

https://www.seleniumqref.com/api/webdriver_gyaku.html

属性ごとの取得方法まとめ

以下は属性ごとに指定方法がまとめられて非常に参考になるサイトになります。
XPathの使い方がうまい。

https://qiita.com/VA_nakatsu/items/0095755dc48ad7e86e2f

「Chrome の自動操作を一通り」

タイトルの通り、「Chrome の自動操作を一通り」まとめてくれているサイトになります。網羅的に書かれていて非常に参考になります。

https://qiita.com/memakura/items/20a02161fa7e18d8a693

まとめ

今回はPythonのSeleniumでWeb画面操作を自動化する基礎をまとめてみました。
自分なりによく使う辞書的な位置づけでまとめてみたので、同じような人の役に立てれば嬉しいです(^^)