kimagre inrash

感想を書きます

とあるデータを取り出してPDF化(Python+Selenium)

time 2024/01/30

とあるサービスで騒ぎがありました。

結局カスタマーの勘違いということで「収めた」ようですが、近い将来起こりうる話だと思い、わたしは来る日に向けて準備を行うことにしました。

(別のサービスで購入したものが諸般の事情で削除される、ということがあり返金対応もなかったので自衛していくしかないと判断した次第。)

ここからは自己責任で。

画面キャプチャ編

該当サービスはブラウザで起動できるので「画面キャプチャすれば回収出来るのでは?」とココを参考にお試し。

def save_screenshot(driver, file_path, is_full_size=False):
    # スクリーンショット設定
    screenshot_config = {
        # Trueの場合スクロールで隠れている箇所も含める、Falseの場合表示されている箇所のみ
        "captureBeyondViewport": is_full_size,
    }

    # スクリーンショット取得
    base64_image = driver.execute_cdp_cmd("Page.captureScreenshot", screenshot_config)

    # ファイル書き出し
    with open(file_path, "wb") as fh:
        fh.write(base64.urlsafe_b64decode(base64_image["data"]))

画面キャプチャになるのでSeleniumで動作している際に表示されてしまう表示をオフにしたり、

# 「Chromeは自動テストソフトウェアによって制御されています」非表示
options.add_experimental_option("excludeSwitches", ['enable-automation'])

フルスクリーンにして試しています。

# フルスクリーン
browser.fullscreen_window()

マウスカーソルもキャプチャされてしまうので動作時は他の作業はしないのを推奨。

とりあえず目的は達成できそう・・ということで回すものの、一定間隔毎撮影・・としていたのでデータが取得できていないケースが発生。

これではキャプチャしきれない・・ということで別の方法を考えることに。

キャッシュから取り出し編

通信データを眺めていると「該当データが分割されて届いている」のを発見。

この分割データを取り出してくっつければ復元できそう。

キャッシュには残っているのでそこから復元は出来そう。

ツール的には以下。

Cache viewer for Google Chrome Web browser
http://www.nirsoft.net/utils/chrome_cache_view.html

欠点としては一度キャッシュに落とす必要があるので手動で該当箇所を閲覧する必要あり。

集めないといけないデータが少ないのであればこれで良さそうですが、かなりの量があるのでやってられない可能性。

また該当データは順番が重要になるため、その情報も欠落してしまう。(ファイル名や内部データにインデックスみたいなものはあったのでそこから復元することは可能そうだけど・・?)

ということで方向としてはこの方式でこれを自動化する方向で。

該当データダウンロード編

Selenium上でDevToolsの情報を取得する。

DevToolsとはChromeでF12押したときに表示される開発用デバッグ機能。

飛んでる通信データを見たり、パフォーマンスや該当箇所のタグを調査する際に重宝。

ココを参考に。

# 
 caps = DesiredCapabilities.CHROME
 caps["goog:loggingPrefs"] = {"performance": "ALL"} 

# 起動時に渡す
browser = webdriver.Chrome(options=options, desired_capabilities=caps)

これでDevToolsにアクセスできるようになり、

# パフォーマンス情報にアクセス
 netLog = browser.get_log("performance")

 events = [process_browser_log_entry(entry) for entry in netLog]
 events = [event for event in events if 'Network.response' in event['method']]

method が “Network.response” のモノをフィルタリングしてピックアップ。

for entry in events:
    if 'type' in entry['params']:
    type = entry['params']['type']

    if type == 'XHR':
        request_id = entry["params"]["requestId"]
        print("request_id:" + request_id)

さらに拾いたいものをループで。
(事前にフィルタリングしても良かったけど、デバッグしながら作る必要があったので簡単に追加・削除できるこちらのタイプで)

requestIdがわかったらその該当データをダウンロードする。

try:
    resbody = browser.execute_cdp_cmd("Network.getResponseBody", {"requestId": request_id})
    resbody['body'] # ココにデータが入ってくる
        ...

except selenium.common.exceptions.WebDriverException:
    print(f"Failed to fetch: {request_id}")
    pass

結構簡単に例外が出るので対応。

受け取ったデータはBase64になっていたので復元。

decode = base64.urlsafe_b64decode(base64Body)

該当データの結合

復元したデータは場合によっては分割されている模様。

ただ分割方法は単純で簡単な結合で戻せそう。

ツールは ImageMagick を使うことにしました。

これはフレームレート検証の際に大昔に使っていたモノ。(最新版では未使用)

00000002.png と 00000003.png を縦に合わせて output.png にするサンプル。(-append で縦結合、+append で横結合になるらしい、未検証)

$ magick convert -append 00000002.png 00000003.png _output.png

結合作業をしていたらうまくいかないケースが。

どうやらファイルによっては結合されていないページもあるが、結合されているページもある・・という状態。

法則を見ると response データから抽出したインデックス値を元に判断できそうだったのでそちらで結合する・結合しないの判定を行い解決。

PDFを作る

該当データの抽出が出来たのでそれをまとめるためにPDF化する。

当初は「PDFCreator」というものを使っていましたが、データのクオリティが落ちてしまったのでしょうがなく「Adobe Acrobat」契約。

順調にまとめることが出来ていたが、一部のデータ群で問題が。

解像度が異なる

「Adobe Acrobat」で結合PDF化してましたが、一部ファイルでエラーになることが判明。

「処理中にエラーが発生しました」としか表示されないので原因の特定が困難。

おそらく解像度だろう・・という目測を立てリサイズで整えることとした。

使ったのは「Ralpha」と呼ばれる随分前のソフト。

解像度を整え(アッパー方向に)、もう一度PDF化したらうまくいきました。

まとめ

なんとかやろうと思っていたことは達成できました。

ただ、似たようなサービスはこれ以外にもあるのでそちらでも対応が必要になりそう。

2024.01.30追記

「Adobe Acrobat」でエラーが頻発し、うまくいかないため別のツールを探す。

結論としては「画像梱包」にしました。

かなり古いツールではあるものの、特に問題なく作成ができ、わたしが使っているPDFツールでも開けました。

PDF化した際の画質の比較は以下で。
https://x.com/inrash/status/1752119035284914355?s=20

前後記事

住所から地図上にプロットしよう(python+geopandas)
とあるデータを取り出してPDF化 Part2(Python+Selenium,C#+PdfSharpCore)