Edited at

Windows + Goutte 3.0でHTTPSでのリクエストで発生するSSL証明書のエラー

More than 3 years have passed since last update.

$client = new \Goutte\Client();

$crawler = $client->request('GET', 'https://example.com');
$html = $crawler->filter('body')->html();

Windows環境にて、スクレイピングライブラリ Goutte (FriendsOfPHP/Goutte) を使って、上記のようなコードでHTTPS経由でコンテンツを取得しようとしたところ、以下のようなメッセージで GuzzleHttp\Exception\RequestException がスローされました。

"cURL error 60: SSL certificate problem: unable to get local issuer certificate (see http://curl.haxx.se/libcurl/c/libcurl-errors.html)"

適当にWebで検索したところ、要はWindows環境のcURLでクライアント証明書が見つけられないために発生する問題のようです。


回避方法1 証明書の検証を行わない

今回は GuzzleHttp\Handler\CurlFactory で例外が発生しており、このクラスで生成されるcURLハンドラに、サーバー証明書の検証を行わないようオプションを設定すれば、例外の発生を回避できるようです。

ただ、 Goutte 3.0 が内部で利用している GuzzleHttp\Client のバージョンが 6.0 と最近のもので、オプションの設定方法がWebでよく紹介されているものから変わっています。

$client = new \Goutte\Client();

$client->setClient(new \GuzzleHttp\Client([
\GuzzleHttp\RequestOptions::VERIFY => false,
]));
$crawler = $client->request('GET', 'https://example.com');
$html = $crawler->filter('body')->html();

こうすることで回避できました。

なお、リクエスト時に使われるオプションの詳細については、オプションの定数のみが定義されているクラス GuzzleHttp\RequestOptions を見れば分かります。

    /**

* verify: (bool|string, default=true) Describes the SSL certificate
* verification behavior of a request. Set to true to enable SSL
* certificate verification using the system CA bundle when available
* (the default). Set to false to disable certificate verification (this
* is insecure!). Set to a string to provide the path to a CA bundle on
* disk to enable verification using a custom certificate.
*/

const VERIFY = 'verify';

trueでシステム依存、falseで検証しない、文字列でクライアント証明書のパスを指定できるようです。

コメントで警告されている通り、自社で運用しているドメインでない場合は、単純に検証を回避するのは危険ですので、念のため。

ちなみに Goutte 3.0 は PHP 5.5+ and Guzzle 6+ が必要ですが、Guzzle 6 は PSR-7 を採用した意欲的な作りのようで、Composerでインストールすると guzzlehttp/promises 1.0, psr/http-message 1.0, guzzlehttp/psr7 1.0 といったライブラリが一緒にインストールされました。

PHP 5.4系の場合は Goutte 2.x (Guzzle 4-5) を、5.3系の場合は Goutte 1.x (Guzzle 3) を使うしかないようです。


回避方法2 クライアント証明書を指定する

検証を回避したくない場合はクライアント証明書を調達するしかありませんが、私の環境では Git for Windows (Git Bash) に同梱されている curl コマンド用に証明書が付いてました。

試してみたところ、このように指定することでもエラーを回避できました。

$client = new \Goutte\Client();

$client->setClient(new \GuzzleHttp\Client([
\GuzzleHttp\RequestOptions::VERIFY => 'C:\Applications\Git\bin\curl-ca-bundle.crt',
]));
$crawler = $client->request('GET', 'https://example.com');
$html = $crawler->filter('body')->html();

この証明書の有効期間は2018年8月14日となってましたので、それまではこれで大丈夫ですね。ちゃんと Git for Windows を更新し続ければ問題ないのかな。

また、こちらに習って php.ini の設定ディレクティブで curl.cainfo ="C:\Applications\Git\bin\curl-ca-bundle.crt" としたところ、上記の \Goutte\Client::setClient() しなくてもOKでした。

GoutteやGuzzle以外でもcurl関数を使うことはあるでしょうし、できればこちらのディレクティブで対応した方が良さそうですね。

curl.cainfo ディレクティブは PHP_INI_SYSTEM となってますので、php.iniまたはhttpd.confのみで設定可能です。(要再起動)