投稿者: buff

  • CSV読み込み

    WindowsでCSVの読み書きは標準ではないので、’shift_jis’ではなくて、’cp932’を使用する

    読み方:シーピーきゅうさんに
    Windowsで扱うファイル(特に日本語)には、cp932を明示的に使うのが安全
    shift_jis とは異なり、機種依存文字を含めてエラーになりにくい

    読み込み

    import csv
    
    # CSVファイルをSHIFT-JISで辞書型で読み込む
    with open('file_path.csv', mode='r', newline='', encoding='cp932') as file:
        reader = csv.DictReader(file)
        
        # 各行を辞書型で表示
        for row in reader:
            print(row)
    

    書き込み

    import csv
    
    # 辞書型データの例
    data = [
        {'名前': '佐藤', '年齢': '30', '職業': 'エンジニア'},
        {'名前': '田中', '年齢': '25', '職業': 'デザイナー'}
    ]
    
    # CSVファイルにSHIFT-JISエンコーディングで書き込む
    with open('output_file.csv', mode='w', newline='', encoding='cp932') as file:
        # フィールド名(ヘッダー)のリスト
        fieldnames = ['名前', '年齢', '職業']
        
        # DictWriterの作成
        writer = csv.DictWriter(file, fieldnames=fieldnames)
        
        # ヘッダーを書き込む
        writer.writeheader()
        
        # 辞書型データを書き込む
        writer.writerows(data)
    
  • ファイルパス確認

    絶対パスか相対パスかどうかを確認する方法

    from pathlib import Path
    
    def check_if_full_path(path):
        p = Path(path)
        if p.is_absolute():
            return "フルパスです"
        else:
            return "ファイル名または相対パスです"
    
    # 使用例
    path = '/home/user/file.txt'
    print(check_if_full_path(path))  # 出力: フルパスです
    
    path = 'file.txt'
    print(check_if_full_path(path))  # 出力: ファイル名または相対パスです
    
  • argparse の利用

    import argparse
    
    def main():
        # 説明
        parser = argparse.ArgumentParser(description="引数のデモ")
        # 必須の場合
        parser.add_argument("name", type=str, help="名前を指定してください")
        # 任意の場合
        parser.add_argument("-a", "--age", type=int, help="年齢を指定できます(オプション)")
        
        args = parser.parse_args()
        
        print(f"名前: {args.name}")
        if args.age:
            print(f"年齢: {args.age}")
    
  • ファイルを開く

    seleniumでファイルを開くのは難しい

    pywinauto を使用して処理を行う

    pip install pywinauto pywin32
    import pywinauto
    
    def fileopen(filepath: str) -> None :
        # pywinautoによる制御
        findWindow = lambda: pywinauto.findwindows.find_windows(title='開く')[0]
    
        dialog = pywinauto.timings.wait_until_passes(5, 1, findWindow)
        pwa_app = pywinauto.Application()
        pwa_app.connect(handle=dialog)
        window = pwa_app['開く']
        window.wait('ready')
    
        #ファイル入力(Alt+N)
        pywinauto.keyboard.send_keys("%N")
        edit = window.Edit4
        edit.set_focus()
        edit.set_text(filepath)
    
        # ダイアログの「開く」ボタンをクリック    
        button = window['開く(&O):']
        button.click()
    

    クリックが反応してくれない場合がある。(フォームが最前面ではないから)ということより、最終的には下記になった

    import pywinauto
    from pywinauto import win32defines
    
    def fileopen(self, filepath: str) -> None :
        # pywinautoによる制御
        findWindow = lambda: pywinauto.findwindows.find_windows(title='開く')[0]
        dialog = pywinauto.timings.wait_until_passes(5, 1, findWindow)
        pwa_app = pywinauto.Application()
        pwa_app.connect(handle=dialog)
        window = pwa_app['開く']
        window.wait('ready')
    
        # ファイル入力(Alt+N)
        pywinauto.keyboard.send_keys("%N")
        edit = window.Edit4
        edit.wait('ready', timeout=3)  # 安定化のため待機
        
        # **前回の入力をクリア**
        edit.type_keys("^a{BACKSPACE}")  # Ctrl + A → Backspace で全削除
        time.sleep(0.2)  # 短い待機(高速化のため0.2秒)
    
        # **新しいパスを入力**
        edit.set_text(filepath)
        time.sleep(0.5)  # 入力が確実に反映されるように待つ
    
        try:
            # 最前面に持ってくるためにウィンドウにメッセージを送る
            window.send_message(win32defines.WM_SYSCOMMAND, win32defines.SC_RESTORE)
            # ダイアログの「開く」ボタンをクリック
            button = window['開く(&O):']
            button.wait('visible', timeout=3)  # ボタンが表示されるまで待つ
            # ボタンにフォーカスを当てる
            button.set_focus()
            # ボタンをクリック
            button.click()
            button.click()
            button.click()
    
            print(f"[INFO] 開くボタンをクリック成功")
    
        except (pywinauto.findbestmatch.MatchError, pywinauto.timings.TimeoutError) as e:
            print(f"[WARN] 開くボタンのクリック失敗(2回目クリック不要だった)")
            time.sleep(0.5)  # 少し待ってから再試行
        except Exception as e:
            print(f"[ERROR] 予期せぬエラー: {e}")
            time.sleep(0.5)
    
    
  • CSV出力

    php出力処理、他にも使えそうなので、サンプルとして残す

    /**
     * 文字列の配列の配列をCSV出力する
     *
     * この関数は、文字列の配列を複数持つ配列(2次元配列)を受け取り、
     * それをCSV形式で出力します。
     *
     * @param string $filename ファイル名
     * @param array $records 文字列を要素とする配列の配列
     */
    function csv_download($filename, $records) {
        // 出力情報の設定
        header("Content-Type: application/octet-stream");
        header("Content-Disposition: attachment; filename={$filename}");
        header("Content-Transfer-Encoding: binary");
    
        // 出力バッファを開始
        ob_start();
        // もし他の部分で余計なデータが出力されていた場合にクリアする
        ob_clean(); // ここで出力バッファをクリアする場合
    
        // ベースとなるデータを作成
        $buffer = "";
        foreach($records as $record) {
            $buffer.= '"' . implode('","', $record) . '"'. "\r\n";
        }
    
        $buffer = mb_convert_encoding($buffer, "SJIS-win", "auto");
    
        echo $buffer;
        // 出力バッファの内容を終了して送信
        ob_end_flush();
        exit;
    
    }
    
  • 画像取得

    要インストール: requests

    画像ダウンロード処理のサンプル
    codeとaddressという項目を持つCSVを読み込んで、当該のaddress(カンマ区切りで複数定義されている)の画像をダウンロードして、codeの名称をベースとしたファイル名で保存する。
    ということをしている。

    """
    ファイル名: main.py
    作成者: UniqueVisionProductions
    
    ダウンロード動作テスト用 メインファイル
    
    このファイルには、以下の関数が含まれています:
    - main: 起点となる関数
    
    変更履歴
    - Ver.1.0.0.1 :
    """
    import csv
    import os
    import requests
    
    def main():
        """
        メイン関数
    
        Parameters:
            None
    
        Returns:
            None
    
        Raises:
            None
        """
        # ユーザーのホームディレクトリを取得
        desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
        # 読み込み元ファイル
        base_filename = "images_01.csv"
    
    
        csvfilepath = os.path.join(desktop_path, base_filename)
        # ダウンロード先
        download_dir = os.path.join(desktop_path, "imagedownload")
        # ディレクトリが存在しない場合に作成
        os.makedirs(download_dir, exist_ok=True)
    
        with open(csvfilepath, newline="", encoding="shift_jis") as f:
            reader = csv.DictReader(f)  # ヘッダー行をキーにして辞書として読み込む
            records = [row for row in reader]  # 各行を辞書としてリストに格納
    
    
        for record in records :
            count = 0
            for url in record['address'].split(","):
                filename = f"{record['code']}_{count}.jpg" if count else f"{record['code']}.jpg"
                filepath = os.path.join(download_dir, filename)
    
                response = requests.get(url)  # 画像を取得
    
                if response.status_code == 200:
                    with open(filepath, "wb") as f:
                        f.write(response.content)  # バイナリデータを書き込み
                    # ファイル項番更新
                    count+=1
                    print(f"{record['code']}: ダウンロード完了")
                else:
                    print(f"{record['code']} ダウンロード失敗:", response.status_code)
    
    
    if __name__ == "__main__":
        try:
            # 引数なし
            main()
        except Exception as e:
            # エラー検出
            print("ERROR : ", e)
        finally :
            # キー入力待ち
            input("Press any key to finish.")
        
       
  • 画像検索

    WordPressでメディアライブラリにある画像の検索に戸惑ったので記す。
    状態の設定 ‘post_status’ を’any’ 指定することが大事

    $searchstring = "検索文字列";
    
    $images = [];
    // メディアライブラリ内でファイルを検索
    $args = array(
        'post_type'      => 'attachment',   // メディアファイルを検索
        'post_mime_type' => 'image',        // 画像のみ
        'posts_per_page' => -1,             // すべての結果を取得
        'post_status'     => 'any', // すべての状態(公開、非公開、ドラフト、等)
        'meta_query' => array(
            array(
                'key'     => '_wp_attached_file',  // メディアファイルのファイル名
                'value'   => $searchstring,        // 検索したいファイル名の部分文字列
                'compare' => 'LIKE'                // 部分一致検索
            )
        )
    );
    
    $query = new WP_Query($args);
    // クエリ出力(デバッグ用)
    echo $query->request;  // 実際に生成されるSQLを確認
    
    if ($query->have_posts()) {
        while ($query->have_posts()) {
            $query->the_post();
            // ファイルパスの取得
            $file_path = wp_get_attachment_url(get_the_ID());
            $images[] = $file_path;  // ファイルパスを配列に追加
        }
        // ファイルパス順に並べ替え(自然順)
        usort($images, function ($a, $b) {
            return strcmp(basename($a), basename($b));  // ファイル名の部分で比較
        });
    } else {
        echo '該当する画像はありません。';
    }
    
    wp_reset_postdata();
    

  • 画像検索

    メディアライブラリの画像を検索する際、完全一致だけでなく、部分一致曖昧検索なども可能です。WP_Queryを使うことで、柔軟に検索条件を変更できます。

    1. 完全一致の検索

    これは画像タイトルやタグなどに対して完全一致で検索する場合です。

    $args = array(
        'post_type' => 'attachment',
        'post_mime_type' => 'image',
        'posts_per_page' => -1,
        's' => '検索キーワード'  // 完全一致でタイトルや説明に一致するものを検索
    );
    $query = new WP_Query($args);

    この's'引数は、画像のタイトル説明post_titlepost_excerpt)に基づいて検索されます。

    2. 部分一致の検索

    WP_Querys引数は部分一致にも対応しています。例えば、検索キーワードが「cat」だとしたら、タイトルや説明に「cat」が含まれている画像をすべて取得できます。

    $args = array(
        'post_type' => 'attachment',
        'post_mime_type' => 'image',
        'posts_per_page' => -1,
        's' => 'cat'  // 部分一致検索
    );
    $query = new WP_Query($args);

    このように、完全一致だけでなく、部分一致でも検索可能です。

    3. カスタムフィールドやタグで検索

    画像にカスタムフィールドタグが設定されている場合、meta_querytax_queryを使って検索を絞り込むことができます。

    例:タグで部分一致検索

    $args = array(<br>    'post_type' => 'attachment',<br>    'post_mime_type' => 'image',<br>    'posts_per_page' => -1,<br>    'tax_query' => array(<br>        array(<br>            'taxonomy' => 'post_tag',<br>            'field'    => 'name',<br>            'terms'    => 'cat', // タグに「cat」を含む画像を検索<br>            'operator' => 'LIKE', // 部分一致<br>        ),<br>    ),<br>);<br>$query = new WP_Query($args);

    例:カスタムフィールドで部分一致検索

    $args = array(
        'post_type' => 'attachment',
        'post_mime_type' => 'image',
        'posts_per_page' => -1,
        'meta_query' => array(
            array(
                'key'     => 'custom_field_key',  // カスタムフィールド名
                'value'   => 'cat',  // カスタムフィールドの値(部分一致)
                'compare' => 'LIKE',  // 部分一致
            ),
        ),
    );
    $query = new WP_Query($args);

    4. 他の検索条件の追加

    さらに、画像のアップロード日カテゴリーを条件に加えて絞り込むこともできます。例えば、特定の期間にアップロードされた画像を検索する場合は、date_queryを使用できます。

    $args = array(
        'post_type' => 'attachment',
        'post_mime_type' => 'image',
        'posts_per_page' => -1,
        'date_query' => array(
            array(
                'after' => '2025-01-01', // 2025年1月1日以降の画像
            ),
        ),
    );
    $query = new WP_Query($args);

    部分一致検索は十分に可能ですし、さらにカスタムフィールドやタグを使った検索を加えることで、非常に柔軟な検索機能を実装できます。WP_Queryの柔軟性を活かして、必要に応じて条件を追加・調整できますので、特定のニーズに合わせた検索機能を実装できます。

  • BURGER MENU

    割とシンプルに書けていると思う

    @charset "UTF-8";
    // 共通設定
    * {
        margin: 0;
        padding: 0;
        border: 0;
        font: inherit;
        font-size: 100%;
        vertical-align: baseline;
        box-sizing: border-box; // padding と border を幅と高さに含める
    }
    
    // スクロールは緩やかに
    html {
        scroll-behavior: smooth;
    }
       
    // 基本フォント設定
    body {
        font-family: "Hiragino Kaku Gothic ProN", "ヒラギノ角ゴ ProN W3", "游ゴシック体", YuGothic, "游ゴシック", "Yu Gothic", "メイリオ", sans-serif;
    }
    
    // 箇条書き設定
    ul {
        /// 横並び
        &.sidebyside li { display: inline-block; }
    }
    
    
    header {
        width: 100%;
        background-color:  lightblue;
        position: fixed;
    
        #header-inner
        {
            margin-top: 16px;
            h1, ul {
                font-size: 39px;
                margin: 0;
                padding: 0;
            }
            // 最終リストはバーガーメニュー
            &-rightside {
                height: 60px;
                ul.sidebyside li:last-of-type { margin-left: 10px; }
                a {
                    transition: .3s;
                }
            }
        }
    
        input#navigation {
            display: none;
    
            // チェックした時の動作
            &:checked {
                // バーガー計上変更
                &~ ul li label.burger span{
                    background-color:  #fff;
    
                    &:nth-child(1)
                    {
                        -webkit-transform: translateY(10px) rotate(-315deg);
                        transform: translateY(10px) rotate(-315deg);
                    }
                    &:nth-child(2)
                    {
                        opacity: 0;
                    }
                    &:nth-child(3)
                    {
                        -webkit-transform: translateY(-10px) rotate(315deg);
                        transform: translateY(-10px) rotate(315deg);
                    }
                }
                
                // メニューではない箇所の表示
                &~ #mask {
                    display: block;
                    position: fixed;
                    top: 0;
                    left: 0;
                    width: 100%;
                    height: 100%;
                    background: #000;
                    opacity: .8;
                    z-index: 2;
                    cursor: pointer;
                }
                
                // 
                &~ nav {
                    left: 0;
                    opacity: 1;
                    height: auto;
                    overflow-y: auto;
                }
    
    
            }
    
        }
    
        .burger {
            display: block;
            position: relative;
            top: 0px;
            right: 0px;
            width: 30px;
            height: 30px;
            transition: all .5s;
            cursor: pointer;
            z-index: 3;
    
            span {
                display: block;
                position: absolute;
                left: 0;
                width: 30px;
                height: 2px;
                background-color: #333;
                border-radius: 4px;
                transition: all .5s;
    
                &:nth-child(1) { top: 4px; }
                &:nth-child(2) { top: 14px; }
                &:nth-child(3) { bottom: 4px; }
            
            }
        }
    
        nav {
            display: block;
            position: fixed;
            top: 0;
            left: -300px;
            bottom: 0;
            width: 300px;
            height: 0;
            background: #ffffff;
            overflow-x: hidden;
            overflow-y: hidden; //auto
            -webkit-overflow-scrolling: touch;
            transition: all .5s;
            z-index: 3;
            opacity: 0;
        }
          
        nav  {
          padding: 25px;
          ul {
            list-style: none;
            margin: 0;
            padding: 0;
        
            li {
              position: relative;
              margin: 0;
              border-bottom: 1px solid #333;
        
              a {
                display: block;
                color: #333;
                font-size: 14px;
                padding: 1em;
                text-decoration: none;
                transition-duration: 0.2s;
        
                &:hover {
                  background: #e4e4e4;
                }
            
              }
         
            }
          }
        }    
    }
    
    
    // フッター固定
    body {
        display: flex;
        flex-direction: column;
        min-height: 100vh;
    }
    main {
        flex: 1;
    }
    
    // メニュー以下の書き出し位置調整
    main {
        padding-top: 100px;
    }
    
    dl {
        display: inline-block;
        min-width: 200px;
    }
    
    footer {
        color: white;
        background-color: aquamarine;
    }
    
    <!doctype html>
    <html>
        <head>
            <meta charset="utf-8" />
            <title>Menu Sample</title>
            <meta name="viewport" content="width=device-width,initial-scale=1">
            <meta name="format-detection" content="telephone=no">
            
            <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
            <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.3/css/all.css" />
            <link rel="stylesheet" href="./css/style.css">
        </head>
    
    <body>
    <header>
        <div id="header-inner" class="container d-flex  justify-content-between align-items-center">
            <h1>TITLE</h1>
    
            <div id="header-inner-rightside">
                <input type="checkbox" id="navigation" />
                <nav>
                    <ul>
                        <li><a href="#">Text</a></li>
                        <li><a href="#">Text</a></li>
                        <li><a href="#">Text</a></li>
                    </ul>
                </nav>
        
                <ul class="sidebyside">
                    <li><a href="#"><i class="fab fa-facebook-square"></i></a></li>
                    <li><a href="#"><i class="fab fa-twitter-square"></i></a></li>
                    <li><a href="#"><i class="fab fa-instagram-square"></i></a></li>
                    <li><a href="#"><i class="fab fa-line"></i></a></li>
                    <li>
                        <label class="burger" for="navigation">
                            <span></span>
                            <span></span>
                            <span></span>
                        </label>
                    </li>
                </ul>
            
                <label id="mask" for="navigation"></label>
            </div>
    
        </div>
    </header>
    
        <main class="container">
            01_abcdefghijklmnopqrstuvwkyz</br>
            02_abcdefghijklmnopqrstuvwkyz</br>
            03_abcdefghijklmnopqrstuvwkyz</br>
            04_abcdefghijklmnopqrstuvwkyz</br>
            05_abcdefghijklmnopqrstuvwkyz</br>
            06_abcdefghijklmnopqrstuvwkyz</br>
            07_abcdefghijklmnopqrstuvwkyz</br>
            
            <div class="d-flex flex-wrap justify-content-around">
                <dl><dt>Item name</dt><dd>商品情報</dd></dl>
                <dl><dt>Item name</dt><dd>商品情報</dd></dl>
                <dl><dt>Item name</dt><dd>商品情報</dd></dl>
                <dl><dt>Item name</dt><dd>商品情報</dd></dl>
                <dl><dt>Item name</dt><dd>商品情報</dd></dl>
                <dl><dt>Item name</dt><dd>商品情報</dd></dl>
                <dl><dt>Item name</dt><dd>商品情報</dd></dl>
                <dl><dt>Item name</dt><dd>商品情報</dd></dl>
                <dl><dt>Item name</dt><dd>商品情報</dd></dl>
                <dl><dt>Item name</dt><dd>商品情報</dd></dl>
                <dl><dt>Item name</dt><dd>商品情報</dd></dl>
                <dl><dt>Item name</dt><dd>商品情報</dd></dl>
            </div>
    
        </main>
        <footer>footer</footer>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
    </body>
    </html>
    
  • メディア保存先を変更

    メディア保存先は通常、uploads/年/月になる
    そうならないようにするにはどうすればよいか

    function custom_upload_directory($uploads) {
        $custom_dir = WP_CONTENT_DIR . '/custom-uploads'; // 新しいディレクトリ(例: wp-content/custom-uploads)
    
        $uploads['path'] = $custom_dir;
        $uploads['url'] = content_url('custom-uploads');
        $uploads['subdir'] = '';
        
        return $uploads;
    }
    add_filter('upload_dir', 'custom_upload_directory');