Playwrightの自動操作がWindowsで動かない罠3つ

ログインが要るサイトを開いて、同じ場所をクリックして、同じ欄に同じことを打つ。それを何百件ぶんかやらなあかんくなって、さすがに自動化することにしました。

使ったのは Python と Playwright。ブラウザをプログラムから動かす道具です。

結果から書くと、動かん理由を探しとる時間で半日溶かしました。ネットに載っとるコードをそのまま書いたのに、Windowsでだけ3回、別々の理由で止まったからです。全部この記事に書きます。最後に、3つとも潰した完成形のコードも置いときます。

罠1・ブラウザがそもそも起動せん

最初に出たエラーがこれ。

Error: browserType.launch: spawn UNKNOWN

一行だけ。何が起きたかは一文字も書いてません。

疑ったのは「ブラウザのファイルが壊れとる」。入れ直しました。変わらん。

実際の犯人は Windows のセキュリティ機能でした。Windows 11には署名のない実行ファイルを問答無用で止める仕組みが入っとって、Playwrightが自分でダウンロードしてきたブラウザがそれに引っかかっとった。ファイルは目の前にちゃんと在るのに、起動だけができひん状態です。

直し方は1行足すだけでした。

browser = p.chromium.launch(
    headless=False,
    channel="msedge",   # ← これ
)

Playwrightが持ってきたブラウザやなく、パソコンに最初から入っとる Edge を使わせる指定です。Edgeは Microsoft が署名しとるので止められません。ダウンロードもゼロ。

ここで一番遠回りしたのは、画面を出さないモードでは動いとったことです。動くんやからブラウザ本体は無事やろ、と思い込んで、全然ちがうところを掘ってました。

あとで知ったんですが、Playwright は 1.49 から画面ありと画面なしで別のファイルを使う仕様になっとります。画面なしで動いても、画面ありのファイルが健全な証拠にはならん。この前提を知らんかったのが遠回りの正体でした。

罠2・起動したのに、自分から「自動操作です」と名乗ってまう

ブラウザは立ち上がるようになりました。けど今度はサイト側の反応がおかしい。

ブラウザには navigator.webdriver という値があって、自動操作で立ち上げると初期状態でこれが true になります。私はプログラムに操られてます、と、こっちが何もせんでも申告しとる状態です。

何が困るかというと、自動操作を弾く作りのサイトやと、ここを見られた時点で普通のページが返ってこんくなります。エラーが出るわけやないので、こっちは「なんか動きが変やな」としか分かりません。

起動時のオプションで消せました。さっきの launch に1行足すだけです。

browser = p.chromium.launch(
    headless=False,
    channel="msedge",
    args=["--disable-blink-features=AutomationControlled"],   # ← 追加
)

これを足して、実際に navigator.webdriver の値を取り直したら false になっとりました。

ただし、これで絶対バレへんわけではないです。判別の材料はほかにもあるので、一番わかりやすい1個が消えただけ。

罠3・画面を出さんと、名乗りが変わる

ブラウザは自分が何者かを名乗る文字列を、毎回サイトに渡してます。ユーザーエージェントというやつです。

画面なしで起動して、その文字列を実際に取ってみたら HeadlessChrome という単語が入っとりました。普通の人のブラウザには絶対に入らん単語です。

やったことは単純で、画面ありで起動した時の文字列を実測して、それを固定で名乗らせただけ。

これは順番が効きます。罠1と罠2を潰しとる間はまだ画面ありで動かしとって、画面なしに切り替える直前にこれを入れました。逆やったら、切り替えた瞬間に弾かれて、また原因探しからやり直しやったと思います。

(おまけ)ログインは1回だけ手で入れたら、あとは無人でいける

罠やないんですが、今回いちばん実用的やった部分なので書いときます。

ログインが要るサイトを自動で触る時、毎回IDとパスワードを打たせる作りにすると事故が怖い。そこでプロファイルを保存するモードlaunch_persistent_context)で起動しました。

1回だけ自分の手でログインする。そのあとは、次に立ち上げた時もログイン済みのまま開きます。

ほんまに同じ状態なんか気になったので、別々に立ち上げた2回ぶんで、サイトが発行しとる値を突き合わせました。完全に一致。以後は放っといても入ったままです。

3つとも潰した形と、書く順番

Playwrightの3つの罠と対策を対にした図。起動できない→Edgeを使わせる、自動操作だとバレる→名乗りを消す、弾かれる→文字列を実測して固定
3つの罠と対策をまとめて一望できる

最終的にこうなりました。

from playwright.sync_api import sync_playwright

# 画面ありで起動して実測した文字列を入れる
UA = "Mozilla/5.0 ..."

with sync_playwright() as p:
    browser = p.chromium.launch(
        headless=False,
        channel="msedge",
        args=["--disable-blink-features=AutomationControlled"],
    )
    page = browser.new_page(user_agent=UA)
    page.goto("https://example.com")

ゼロから書くなら、この順番が安全やと思います。

  1. まず channel="msedge"。ここが通らんと何も始まらん
  2. 次に AutomationControlled を切る
  3. 最後に、画面ありで名乗りの文字列を実測してから画面なしへ切り替える

3つに共通しとるのは、なんで動かんかが画面に出てこんことです。罠1は一行のエラーだけ。罠2と罠3にいたってはエラーすら出ません。動かん、やなくて、何が違うか、を1個ずつ測っていくしかなかった。

同じ「業務を自動で回す」系やと、n8nとDifyで問い合わせ自動仕分けを半日で作った話も書いてます。あっちはコードを1行も書かんパターンです。