頑張らないために頑張る

ゆるく頑張ります

Headless ChromeをPythonで使おうとしたら空っぽのページが返ってきた

Posted at — Nov 13, 2019

はじめに

Python、SeleniumにChromeの組み合わせは、PhantomJSが息絶えてしまった今では自動化の王道だと思います。そんな王道の組み合わせをWindowsで試してたら、Headlessモードの時だけページの取得が上手くできない事象を目撃しましたので、メモしておきます。Headlessモードじゃないなら正常なのに、Headlessモードへ変更した途端におかしくなってしまいました。

環境

現象

どんなURLを指定しても、HeadlessモードではSeleniumで取得した結果が空っぽのHTMLになってしまいます。Headlessモードを外すだけで、ちゃんと取得します。

Chrome用ソース

Chromedriverは、インストール先のパスが通っている前提です。

import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument('--headless')
options.add_argument('--disable-gpu')
options.binary_location = 'Chrome Canaryのアドレス'

driver = webdriver.Chrome(options=options)

driver.get('https://www.yahoo.co.jp/')

time.sleep(3)

html = driver.page_source
print(html)

driver.save_screenshot("hoge.png")

driver.quit()

多分、極端に変なことはしてないと思うんですが、これが動作するとコンソールには下記のHTMLソースが表示されます。

<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body></body></html>

中身空っぽじゃねーかよ!

実際save_screenshotで生成されるファイルを見てみると下記の通りです。

pic

オドロキの白さ!

まぁそうですわな、HTMLファイル中に何もないんだから。

Headlessモードを外してみる

ソースはこんな感じ。Headlessモードをコメントで外しただけです。

import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
# options.add_argument('--headless')
options.add_argument('--disable-gpu')
options.binary_location = 'Chrome Canaryのアドレス'

driver = webdriver.Chrome(options=options)

driver.get('https://www.yahoo.co.jp/')

time.sleep(3)

html = driver.page_source
print(html)

driver.save_screenshot("hoge.png")

driver.quit()

これを実行すると数秒だけChromeの画面が出現します。Headlessモードじゃないから当たり前ですね。その後コンソールを見てみると、下記の通り取得したHTMLソースが表示されています。なお、すべてを記述すると長いので省略しています。

<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml" lang="ja"><head><meta charset="utf-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /><title>Yahoo! JAPAN</title><meta name="description" content="あなたの毎日をアップデートする情報ポータル。検索、ニュース、天気、スポーツ、メール、ショッピング、オークションなど便利なサービスを展開しています。" /><meta name="robots" content="noodp" /><meta name="viewport" content="width=1010" /><link rel="dns-prefetch" href="//s.yimg.jp" /><link rel="dns-prefetch" href="//yads.c.yimg.jp" /><meta name="google-site-verification" content="fsLMOiigp5fIpCDMEVodQnQC7jIY1K3UXW5QkQcBmVs" /><link rel="alternate" href="android-app://jp.co.yahoo.android.yjtop/yahoojapan/home/top" /><link rel="alternate" media="only screen and (max-width: 640px)" href="https://m.yahoo.co.jp/" /><link rel="canonical" href="https://www.yahoo.co.jp/" /><link rel="shortcut icon" href="https://s.yimg.jp/c/icon/s/bsc/2.0/favicon.ico" type="image/vnd.microsoft.icon" /><link rel="icon" href="https://s.yimg.jp/c/icon/s/bsc/2.0/favicon.ico" type="image/vnd.microsoft.icon" /><meta property="og:title" content="Yahoo! JAPAN" /><meta property="og:type" content="website" /><meta property="og:url" content="https://www.yahoo.co.jp/" /><meta property="og:image" content="https://s.yimg.jp/images/top/ogp/fb_y_1500px.png" /><meta property="og:description" content="あなたの毎日をアップデートする情報ポータル。検索、ニュース、天気、スポーツ、メール、ショッピング、オークションなど便利なサービスを
展開しています。" /><meta property="og:site_name" content="Yahoo! JAPAN" /><meta property="twitter:card" content="summary_large_image" /><meta property="twitter:site" content="@Yahoo_JAPAN_PR" /><meta property="twitter:image" content="https://s.yimg.jp/images/top/ogp/tw_y_1400px.png" /><meta property="fb:app_id" content="472870002762883" /><link rel="stylesheet" href="//s.yimg.jp/images/top/orion/1.0.17/bundle_1.0.17.css" /><script
async="" data-jsonpid="" src="https://cdn-gl.imrworldwide.com/novms/js/2/nlsSDK600.bundle.min.js"></script>・・・(以下省略)

HTMLがちゃんと取得できているのだから、スクリーンショットも正常に取得できています。なお、スクリーンショット画像は下部分を省略しました。

pic

ただHeadlessモードを摘要するだけで、こうも挙動が違うものなんでしょうか。

なんとかしてみる

(その1)引数を追加

ここを参考に、Chrome実行時の引数を追加してみました。

import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.add_argument('--headless')
options.add_argument('--disable-gpu')
options.set_headless(headless=True)
options.add_argument('--ignore-ssl-errors=true')
options.add_argument('--ssl-protocol=any')
options.add_argument('--no-sandbox')
options.binary_location = 'Chrome Canaryのアドレス'

driver = webdriver.Chrome(options=options)

driver.get('https://www.yahoo.co.jp/')

time.sleep(3)

html = driver.page_source
print(html)

driver.save_screenshot("hoge.png")

driver.quit()

結果は・・・変化なし!

(その2)Proxy設定を追加

たまたま実行環境がProxyの影響下だったので、ここを参考にして、SeleniumにProxyの設定を盛り込んでみました。

import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.proxy import Proxy, ProxyType

options = Options()
options.add_argument('--headless')
options.add_argument('--disable-gpu')
options.binary_location = 'Chrome Canaryのアドレス'

desired_caps = options.to_capabilities()

prox = Proxy()
prox.proxy_type = ProxyType.MANUAL
prox.http_proxy = "Proxyのアドレス"
prox.add_to_capabilities(desired_caps)

driver = webdriver.Chrome(options=options, desired_capabilities=desired_caps)

driver.get('https://www.yahoo.co.jp/')

time.sleep(3)

html = driver.page_source
print(html)

driver.save_screenshot("hoge.png")

driver.quit()

結果は・・・変化なし!

2連敗・・・。

(その3)Chromedriverに設定値を追加する

ここを参考に、Chromedriverの設定を追加してみました。

import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities

options = Options()
options.add_argument('--headless')
options.add_argument('--disable-gpu')
options.binary_location = 'Chrome Canaryのアドレス'

caps = DesiredCapabilities.CHROME.copy()
caps['acceptInsecureCerts'] = True

driver = webdriver.Chrome(options=options, desired_capabilities=caps)

driver.get('https://www.yahoo.co.jp/')

time.sleep(3)

html = driver.page_source
print(html)

driver.save_screenshot("hoge.png")

driver.quit()

結果は・・・変化なし!

3連敗か・・・。

ここまで3連敗

いかんせん、もともとWindows環境で実行しているのは少数派だからか、情報が少ないことのが痛いです。さらに、「自分はこれで動いたぜ」っていう方法を実行してもことごとくだめなので、そもそも別の要素が影響しているんじゃないか、という疑心暗鬼に陥る始末。

・・・と、そんなときに救世主が表れました。

Firefoxがあるじゃないか

そうです、ChromeでダメならFirefoxにすればいいのです。動けば正義!

というわけで環境を準備します。

geckodriverをインストールする

chromedriverのFirefox版です。こちらからダウンロードした実行形式ファイルを任意の場所に展開して、パスを通しておきます。

なお、pipなどで導入すれば他のPythonパッケージと同様にバージョン管理ができます。さらに、パス設定をする必要もないので楽です。どちらを選択するかはお好みで。

$ geckodriver --version
geckodriver 0.26.0 (e9783a644016 2019-10-10 13:38 +0000)

Firefox版のソースを準備する

Firefox用に準備したソースはこんな感じ。

import time
from selenium import webdriver
from selenium.webdriver.firefox.options import Options

options = Options()
options.add_argument('-headless')
driver = webdriver.Firefox(firefox_options=options)

driver.get('https://www.yahoo.co.jp/')

time.sleep(3)

html = driver.page_source
print(html)

driver.save_screenshot("hoge.png")

driver.quit()

だいぶ簡略化してるんで何もないですが、これを実行してみましょう。

実行する

<html lang="ja"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><title>Yahoo! JAPAN</title><meta name="description" content="あなたの毎日をアップデートする情報ポータル。検索、
ニュース、天気、スポーツ、メール、ショッピング、オークションなど便利なサービスを展開しています。"><meta name="robots" content="noodp"><meta name="viewport" content="width=1010"><link rel="dns-prefetch" href="//s.yimg.jp"><link rel="dns-prefetch" href="//yads.c.yimg.jp"><meta name="google-site-verification" content="fsLMOiigp5fIpCDMEVodQnQC7jIY1K3UXW5QkQcBmVs"><link rel="alternate" href="android-app://jp.co.yahoo.android.yjtop/yahoojapan/home/top"><link rel="alternate" media="only screen and (max-width: 640px)" href="https://m.yahoo.co.jp/"><link rel="canonical" href="https://www.yahoo.co.jp/"><link rel="shortcut icon" href="https://s.yimg.jp/c/icon/s/bsc/2.0/favicon.ico" type="image/vnd.microsoft.icon"><link rel="icon" href="https://s.yimg.jp/c/icon/s/bsc/2.0/favicon.ico" type="image/vnd.microsoft.icon"><meta property="og:title" content="Yahoo! JAPAN"><meta property="og:type" content="website"><meta property="og:url" content="https://www.yahoo.co.jp/"><meta property="og:image" content="https://s.yimg.jp/images/top/ogp/fb_y_1500px.png"><meta property="og:description" content="あなたの毎日をアップデートする情報ポータル。検索、ニュース、天気、スポーツ、メール、ショッピング、オークションなど便利なサービスを展開しています。"><meta property="og:site_name" content="Yahoo! JAPAN"><meta property="twitter:card" content="summary_large_image"><meta property="twitter:site" content="@Yahoo_JAPAN_PR"><meta property="twitter:image" content="https://s.yimg.jp/images/top/ogp/tw_y_1400px.png"><meta property="fb:app_id"
content="472870002762883"><link rel="stylesheet" href="//s.yimg.jp/images/top/orion/1.0.17/bundle_1.0.17.css"><script async="" data-jsonpid="" src="https://cdn-gl.imrworldwide.com/novms/js/2/nlsSDK600.bundle.min.js"></script><script async="" src="https://cdn-gl.imrworldwide.com/conf/P2ED650F9-2101-4CB9-845D-ED37E7119BAD.js#name=nSdkInstance&amp;ns=NOLBUNDLE"></script>・・・(以下省略)・・・

やったぜ!

pic

ちゃんとスクリーンショットも取得できています。やったぜ!

まとめ

Firefoxはいいぞ

結局のところ、なんでHeadlessモードのChromeで正常にHTMLを取得できないのか、根本的な解決には至りませんでした。ちなみに、今でも謎です。とくに「ソースをいじってないのに、Headlessモードを外しただけで正常に取得できる」ってのがホントに解せないです。検索すると、そこそこ上記のような現象はあるようなのですが、レアケースっぽいですね。

とりあえず自動化しなくちゃいけないケースでは、Firefoxを利用しようと思います。よろしく、Firefox。

comments powered by Disqus