C#でChromeDriverを使用してChromeブラウザを操作する際、Web上に表示されたPDFファイルをダウンロードするというケースにおいて、ダウンロード処理の実装に少々時間が掛かってしまったので手順を纏めておきます。
経緯
発端は以下のツールです。
上記ツールは『クリックポスト』と呼ばれるWebサービスにアクセスし、荷物の発送に使用する送り状を発行後、それを保存する。という仕様になっています。
その送り状はサービス上でPDFとして出力されます・・・が、問題となったのはこのPDFをどうやって任意のローカルフォルダに保存するかという点です。
単純にURLさえ判明していれば、大抵の場合は「WebClient」や「WebRequest」のクラスを用いてファイルを直接ダウンロードする事ができるのですが、今回のクリックポストPDFについてはそれが出来ません。
なぜか?
理由は”会員制のサービスで、サービス内のURLにアクセスするにはログインが必要なため”です。
「・・・あれ?でも最初にChromeでYahooアカウントを使ってログインしてるよね?」
「ログインしてるからPDF作れてるんだよね?」
と思いますよね。
確かにログインはしていますが、それは「Chromeでの話」なんですよね。
例えばWebClientのクラスを使ってダウンロードをしようとする場合、WebClientは別にChromeを経由してダウンロードしている訳では無く、”全く別のセッション”を構築してアクセスしています。違うブラウザを起動しているイメージですね。
つまりWebClientを使ってクリックポストPDFのページに直接アクセスしようとしても「ログインしていない状態」と見なされてしまい、アクセスできないという事になります。
では非ログイン状態でPDFのURLを開くとどういう事になるか。
以下の画像は、私が自分のアカウントで作成したクリックポストのPDFファイルをWebClientクラスを使用して保存した後、ファイルを開こうとした際の画面です。
「末尾3281.pdf」のファイルを開こうとすると「ファイル形式がサポートされていないか、ファイルが破損している」と表示されてしまい開く事ができませんでした。
最初は保存処理の仕方やURLが間違っているのかと思いましたが、そのすぐ下にある同じクリックポストのファイルと比較すると、ファイルサイズが妙に小さい事が分かります。
この二つはそれぞれPDF出力後のページからダウンロードしたものですが、上はWebClientを用いたもの。下は手動で「名前を付けて保存」を行ったものです。
同じクリックポストのPDFなのに、3倍近いサイズ差なんてどう考えてもおかしいですよね。
そこでもしかしてと思い、拡張子をpdf ⇒ htmlに変更してみた所(pdfの拡張子はそもそも保存処理の際に「.pdf」を付けて保存する様にしていたため、中身に関係なくpdfファイルとして保存される様になっていた)、
この様な画面が出現。htmlで開けてしまいました。
pdfファイルではなくhtmlファイルが保存されていたという事ですね。納得。
ログインが必要なページに直接アクセスしようとしたため、ログインページに飛ばされてしまっていた様です。
私はこれに気付くのに数時間掛かってしまいましたけどね!
ただファイルをダウンロードすれば良いだけだと思って完全に失念していました。
ダウンロードの方法
Web上のPDFファイルをダウンロードする方法は大まかに以下の3つ。
1.WebClient等のクラスを用いる方法
2.ブラウザ側でJavaScriptを実行させ、ページごと名前を付けて保存を行う方法
3.ブラウザ上でPDFファイルを開くオプション(PDFビューア)をOFFにし、
既定フォルダに自動保存を行う方法
※今回は書いていませんが、他にも方法はあります。
1は既に試していて×。(※今回の対象は会員専用サイトだったが、フリーで公開されているPDFであればこちらの方法でも問題無し)
2は自動操作中にダイアログが開いてしまうのが嫌だったので×。(※誤ってダイアログを操作し、保存に失敗してしまう事を避けたかったため)
という事で3の方法で実装を行いました。
PDFビューアをオフにして直接保存
以下がPDFビューアをオフにして保存する部分のC#コードです。
ChromeOptions options = new ChromeOptions(); // 既定のファイル保存先を設定 options.AddUserProfilePreference("download.default_directory", path); // ダウンロード先フォルダを訪ねるダイアログの抑止 options.AddUserProfilePreference("download.prompt_for_download", false); // options.AddUserProfilePreference("download.directory_upgrade", true); // 「Chrome PDF Viewer(Chrome上でPDFを開ける様にするプラグイン)」をOFF options.AddUserProfilePreference("plugins.plugins_disabled", "Chrome PDF Viewer"); options.AddUserProfilePreference("plugins.always_open_pdf_externally", true); driver = new ChromeDriver(options);
ドライバーをnewする前にこれらのオプションを設定する必要があるらしい。
この設定を行った上でPDFファイルのリンクをクリックすると、自動的に既定のフォルダにファイルが保存される様になります。ダイアログもメッセージも出ません。
デメリットは途中で保存先を変えられないという所ですね。都度オプションを変更してブラウザを再起動する必要があります。
・・・でも通常はユーザーが任意のタイミングでオプションから変更出来ますし、何か方法は有りそうな気はしますが。今回は保存先固定で問題無いのでまた必要になったら調べてみます。
ちなみにPythonだとこんな感じ↓。
options = webdriver.ChromeOptions() options.add_experimental_option("prefs", { "download.default_directory": download_dir, "plugins.always_open_pdf_externally": True }) driver = webdriver.Chrome(options=options)
・・・Pythonの書き方なら何件もヒットするんですけどね。なぜかC#含め他の言語のコードは全然見付からなかった。
WebClientを用いて保存
一応WebClientクラスを使う場合の方法も載せておきます。
ログイン不要のサイトであれば、PDFビューアをオフにしなくてもこちらの処理で問題無く保存できるはず。
// pdfファイルのリンクをクリックし、ビューア起動まで待つ ※クリック処理は含まれていないので、コピーする場合は各自実装して下さい // pdfタブが追加されるまでループで待機 do { waitTime(1000); //独自関数 Thread.Sleep等で代用可 } while (driver.WindowHandles.Count == 1); // pdfタブをアクティブ化 driver.SwitchTo().Window(driver.WindowHandles.Last()); System.Net.WebClient wc = new System.Net.WebClient(); fileName = System.IO.Path.GetFileName(driver.Url); wc.DownloadFile(driver.Url, folderPath + "\\" + fileName + ".pdf"); wc.Dispose(); // 保存後にpdfタブを閉じる driver.Close()
予めPDFのURLが判明している場合、前半の処理は不要。WebClientをnewする所からで問題ありません。