📔

PythonでクリップボードのHTMLデータを取得(Windows限定)

2024/04/09に公開

TL;DR

  • powershell Add-Type -AssemblyName System.Windows.Forms; [System.Text.Encoding]::UTF8.GetString([System.Windows.Forms.Clipboard]::GetData('html format').ToArray())subprocessで実行する

既存ライブラリについて

win32clipboard

Win32 APIの呼び出しが可能なwin32clipboardでのwin32clipboard.GetClipboardData()では、呼び出し先API自体の指定できる形式の中にHTMLがないためテキスト形式でしか取得できない。

pyperclip

win32clipboard同様pyperclip.paste()ではテキスト形式でしか取得できない。

PowerShell: Get-Clipboardについて

Get-Clipboard -TextFormatType Htmlで取得することはできるが、日本語が文字化けする。
ターミナルの文字コードを変えても効果がないので、おそらくGet-Clipboardコマンド自体の実装あるいはコマンド呼び出し元がUTF-8エンコーディングにしか対応していないと思われる。

Get-Clipboard -TextFormatType Html

Version:0.9
StartHTML:00000131
EndHTML:00000556
StartFragment:00000165
EndFragment:00000520
SourceURL:https://zenn.dev/about
<html><body>
<!--StartFragment--><div class="View_headerMainContainer__7gRMq"><h2 class="View_headerTitle__DIkXe">Write <span style="display: inline-block;">for yourself.</span></h2><p class="View_headerDescription__BXdhj">Zenn縺ッ繧ィ繝ウ繧ク繝九い縺ョ縺溘 a縺ョ譁ー縺励>諠・ア蜈ア譛峨さ繝溘Η繝九ユ繧」縺ァ縺吶€・隱ー縺九・縺溘a縺ォ縲∬・蛻・・縺溘a縺ォ遏・隕九r蜈ア譛峨@縺セ縺励 g縺・€・/p></div><!--EndFragment-->
</body>
</html>

対処方法

[System.Windows.Forms.Clipboard]::GetData()を呼び出す。
この際に引数html formatを渡すことでHTMLデータのSystem.IO.MemoryStreamを取得することができるので、これをバイト配列に直してUTF-8でテキストエンコーディングすれば生データを取り出せる。

powershell
[System.Text.Encoding]::UTF8.GetString([System.Windows.Forms.Clipboard]::GetData('html format').ToArray())

Version:0.9
StartHTML:00000131
EndHTML:00000556
StartFragment:00000165
EndFragment:00000520
SourceURL:https://zenn.dev/about
<html><body>
<!--StartFragment--><div class="View_headerMainContainer__7gRMq"><h2 class="View_headerTitle__DIkXe">Write <span style="display: inline-block;">for yourself.</span></h2><p class="View_headerDescription__BXdhj">Zennはエンジニアのための新しい情報共有コミュニティです。 誰かのために、自分のために知見を共有しましょう。</p></div><!--EndFragment-->
</body>
</html>

あとはPythonからならこのコマンドをsubprocess等でPowerShell経由で取得すれば良い。
PowerShellがモジュールから呼び出せるなら理論上どの言語でも行けるはず。

サンプルスクリプト

clipboard.py
import re
from re import MULTILINE, Match
from subprocess import PIPE, Popen

START_HTML_PATTERN, END_HTML_PATTERN = [
    re.compile(pattern, flags=MULTILINE)
    for pattern in [rb"^StartHTML:(\d+)", rb"^EndHTML:(\d+)"]
]


def main() -> None:
    stdout, _ = Popen(
        (
            "powershell",
            "Add-Type",
            "-AssemblyName",
            "System.Windows.Forms;"
            "[System.Text.Encoding]::UTF8.GetString("
            "[System.Windows.Forms.Clipboard]::GetData('html format').ToArray()"
            ")",
        ),
        stdout=PIPE,
        stderr=PIPE,
    ).communicate()
    start, end = [
        pattern.search(stdout) for pattern in [START_HTML_PATTERN, END_HTML_PATTERN]
    ]
    if 0 == len([match for match in [start, end] if not isinstance(match, Match)]):
        print(stdout[int(start.group(1)) : int(end.group(1))].decode("shift_jis"))
    else:
        print("no html data")


if __name__ == "__main__":
    main()

実行例

python clipboard.py

<html><body>
<!--StartFragment--><div class="View_headerMainContainer__7gRMq"><h2 class="View_headerTitle__DIkXe">Write <span style="display: inline-block;">for yourself.</span></h2><p class="View_headerDescription__BXdhj">Zennはエンジニアのための新しい情報共有コミュニティです。 誰かのために、自分のために知見を共有しましょう。</p></div><!--EndFragment-->
</body>
</html>

補足

StartHTMLの開始デックス~EndHTMLの終了インデックスまでのバイトデータを正規表現で取得して切り出してShift_JISでエンコードしている。これにより<html></html>までのテキストデータを取り出せる。
html formatのデータがない場合、あるいは正規表現にマッチしなかった場合は無い旨を出力する。

参考

Discussion