<   2012年 07月 ( 4 )   > この月の画像一覧

近デジ PDFダウンロード 「目次」に対応してみた。

 くらひと様にコメントでご質問頂いた内容に関して、自分の備忘もかねて補足しておく。

 というかホントのことをいうと、「目次」に関してはまったく頭が及んでなかったので、コメントに答える為に改めて見直してみたら「あ。できそうじゃん」と思い、ウソいってたらマズイので実際に試してみたというわけだ(笑)。

 最終コマ数を取得する為に、ビューワの画面をGETしていることは既に述べたが、このページの中にはいわゆる「書誌情報」も含まれている。
 また、このページはJavaScriptでインタフェイスを切り替えているだけで、元々1ページの中に目次の情報も含まれている。

 例によって
 http://kindai.ndl.go.jp/info:ndljp/pid/1137942
 このページをサンプルとする。

 ブラウザでこのページのソースを表示してみると、そのhtmlの中に書誌情報・目次情報が埋め込まれていることが確認できる。

 コメントにも書いたとおり;
  •  「タイトル」・「著者」・「出版者」などは、 class="simple-metadata-list"(簡易表示用)あるいは class="detail-metadata-list"(詳細表示用)とタグ付けされたブロックに含まれている。
  •  「目次情報」(がある場合)は、class="ndltree" の下に存在する。この class の startContentNo が目次のコマ数、title が目次見だしのようである。

PDFにはいくつか「目次」処理を行う手段があるが、ここではもっともノーマルな「しおり」(ブックマーク)として、ndltreeのデータを使うことを考えてみる。

 何度も繰り返しているように、本来はこの部分は構造化文書(html.xml)としてきちんとパースするべきである。だが、残念なことに.netではいわゆる「HTMLパーサー」が標準としては提供されていない。
 もちろんxmlパーサーで代用することは可能だろうが、近デジの提供するhtmlがvalidなxmlとして解釈できるかどうかがよく判らないし、真面目にhtmlパーサーを作ると面倒くさいので(爆)、例によって単なる検索で処理してみた。

 まずはndltreeブロックの明細行を検索して、PDFの「しおり」付けに必要な最低限のデータ、startContentNoとtitleを取得する。

 下の例で、DocHtmlは近デジ画面からGETしたhtmlを格納したString、TocInfoは startContentNo,Title のリストである。



string Reg_anchor =
"<input type=\"hidden\" value=\"startContentNo=(?<startContentNo>.*?)\".+?class=\"ndltree-item ndltree-label\" title=\"(?<title>.*?)\">";
Regex re = new Regex(Reg_anchor, RegexOptions.IgnoreCase | RegexOptions.Singleline);
System.Text.RegularExpressions.MatchCollection mc = re.Matches(DocHtml);

foreach (System.Text.RegularExpressions.Match m in mc) {
startNo = m.Groups["startContentNo"].Value;
Title = m.Groups["title"].Value;
TocInfo.Add(TocStartNo, TocTitle);
}



 こんな感じで、startContentNoとtitleのリストを作る。

 今回、俺はiTextSharpを使っているのだが、「しおり」生成のところはかなり悩んだ―というか、書き方がよく判らなかった(今でもよく判ってない)のだが、PDFを結合するために使用しているPdfCopyオブジェクトに、以下のダンドリで処理するとなんとなく上手くいくようである。

訂正:PdfDestinationの値がおかしかったので直した。ズームを継承するにはマイナスを入れろとドキュメントに書いてあるのでそのとおりにしたが、これでホントに良いのかどうか不安だなぁ。


ここで、copyオブジェクトはPdfCopy型であり、個別PDFを結合した結果が格納されている。


PdfContentByte cb = copy.DirectContent;
PdfOutline root = cb.RootOutline;
PdfWriter wrt = cb.PdfWriter;
wrt.ViewerPreferences = PdfWriter.PageModeUseOutlines; // しおり開くモード
PdfOutline oline = new PdfOutline(root, PdfAction.GotoLocalPage(1, new PdfDestination(0,-1.0F,-1.0F,0.0F), wrt), "Contents");
for (int i = 0; i < TocInfo.Count; i++) {
PdfOutline oline1 = new PdfOutline(oline, PdfAction.GotoLocalPage(TocInfo[i].startNo, new PdfDestination(0,-1.0F,-1.0F,0.0F), wrt), TocInfo[i].title);
}



 ようするに、copyオブジェクトのRootOutlineを取得し、そこにまずトップノード(ここでは"Contents"という見だし)を作成し、その配下(section)として、取得した目次情報を追加してしまう、という処理である。
 PDFのOutlineオブジェクトはもちろん階層化できるのだが、どうも近デジデータはフラットなデータのようなので、そのまんまループで回して貼り付けている。

 ということで、この機能を追加してはき出させたPDFはこうなる。
c0071416_13474395.jpg


 なるほど、ある種の文書はやっぱり目次があった方が圧倒的に便利である。

 気づかせて下さった くらひと様 に感謝申し上げます。
[PR]
by signal-9 | 2012-07-24 13:49 | TIPSとかKludgeとか

近デジ PDF一括ダウンローダー 自動版 一応完成

 ちょっとだけ気が向いたので、前回作ったモノを改善。

 追加したのは、
  1. PIDだけではなく、近デジの検索画面で張られるURL(http://kindai.ndl.go.jp/info:ndljp/pid/ほにゃらら)を入れたら、最後の/の後ろの「ほにゃらら」をPIDと見なして受け入れるようにした。
     自分で使ってみたら、ブラウザで検索画面を使っていて「お、これはおもしろそうだな」と思ったら、文書のリンクをそのままコピーしてコマンドラインに貼り付けるという方がラクだと気づいたので(気づくのが遅い)。

  2. PDFの結合。
     別DLLが必要になるのでちょっと迷ったが、iTextSharpをそのまんま使わせてもらった。
     最新版のVer 5.3.0でも、いわゆる「Web表示用に最適化」(Linearized PDF)には対応していないらしいのがちょっと残念だが、ローカル保存・ローカル閲覧が主目的なので無問題。
     これで、今まで使っていなかった文書名や作者名から、ファイル名を生成すると同時にPDFのプロパティに突っ込むことにした。iFilter経由で検索に使う予定。


 ブラウザで近デジ検索画面で検索して、一括DLしたい文書のURLをコピペして、バッチファイルを作成する。


TEST_KinDigDL.exe http://kindai.ndl.go.jp/info:ndljp/pid/ほにゃらら
TEST_KinDigDL.exe http://kindai.ndl.go.jp/info:ndljp/pid/ほげほげ
……


 で、適当な時間(寝てるときでもいいのだが、処理的にはスリープしてる時間の方が圧倒的に長いので、パソコン使ってる裏ででも特に痛痒感は感じない)に、このバッチファイルを動かすと、いつの間にやら本毎にPDFができているという寸法である。

 元々汚かったコードがますます汚くなったキライはあるが、個人的には結構ラクになった。
 で、いきおいで80冊くらいダウンロードしてしまったのだが、よく考えたら、こんなに読む時間がないや(爆)

 のんびりとだが、目を通せて面白かったモノは、今後このブログのネタにするつもり。
[PR]
by signal-9 | 2012-07-20 16:27 | TIPSとかKludgeとか

近デジ PDFで全部ダウンロード やや自動編

 さて、同じコトを考えてる人や実際にやってる人もいるので、何を今更車輪の再発明なのだが、前回テストした内容を一連で実行する簡単なテストプログラムを実装してみる。

 まず何で作るかであるが、Windows環境であまり面倒無く動いて欲しいのと、余り詳しくないC#の勉強方々、C# (.NET 3.5) を選択してみた。

 ざっくり仕様は以下の通り。バカみたいにプリミティブ。
  1. コマンドラインで実行する。
  2. コマンドライン引数としてPIDと書き込み先のディレクトリ(、必要に応じてプロクシのURL)を指定する。
  3. PIDによって近デジのビューワ画面にアクセスし、最終コマ数を取得する。
  4. 1から最終コマまで10コマずつ、ダウンロードのGETリクエストを発行し、戻ってきたのが"application/pdf"だったら、引数で指定したディレクトリに固定のファイル名(PIDと開始・終了コマ)で出力する。ただし、同じ名前のファイルが既にあったら、二重ダウンロードしないためダウンロードをスキップする
  5. ファイルに出力したら、30秒間スリープする

 元々の力不足に加え何も考えずにダァーっと書き下してしまったので、ヒドいコード(ホントにヒドい。つーか勉強の為と思ってC#を使ったが、あんまり勉強になってない)だが、とりあえず意図通り動くことは確認できた。

 以下は、実行時の画面のキャプチャである。
c0071416_17402865.jpg

 結果、ディレクトリにはこのようにPDFが取得できた。
c0071416_1740406.jpg

 で、作ってみた感想なのだが。
  1. 予想通りとはいえ30秒のスリープは、やはり待つ身にはしんどい。
     そこそこのPC+回線状況で49コマ取得するのに2分17秒かかっているが、都合4回スリープしてるので、その内2分、処理時間の87%はただ寝てるだけで何もしていない(笑)
     前回も書いたとおり、もう少し何とかできる余地がないわけではないが、そーゆーことは考えないことにした(爆)。

  2. Cookiesはサーバから渡されるが、何に使うものか判らないので、今のところダミーで受け取るだけにしている。
     動作的には問題ないように思えるが、ホントは解析が必要なのだろうな。

  3. エラー処理に代表されるステータス管理の部分が真面目に作ったら面倒そう。
     サーバがヘッダにセットしてくる"Reason"などのヘッダの値を使うのかもしれないが、今のところ良くわからないので、処理した結果返ってくるtext/htmlを解釈する必要があるのだろうが、このテストプログラムではいっさいネグッて、単にエラーとして読み捨てている。

  4.  気が向いたらPDFの結合まで自前でやってみたくなるかもしれないので、文書名や著者名などもいちおう取得してみたが、今は使っていない(画面に表示するだけ)。
     PDFの結合処理などはiTextみたいな便利なライブラリがあるので、そんなに大変ではないのはわかってるのだが、今は気が向いてないので放置。
     で、この辺の文書情報も仕方なくHTMLを読んで取得しているが、APIとしてサポートするべきだと思う。

 まとめると、やはり根本的には近デジがいわゆるWeb API的な設計ではないことはやはり問題だと思う。

 そのうちに海外の同種のサービスと比べられて、みんな侘しいキモチになりそうなので、次回リファインの際にはこのあたりガンバッてもらいたいものだ。

 つーかその前に10コマ/30秒制限の撤廃を希望。

 …ということで、ものすご~くニッチな用途だしテスト用(俺個人用)だし、酷いバグが残っていることが確実(ぉい)なので、公開は控えておくが、欲しい人いるかなぁ?

 とりあえず、コメント付いたら考えることにする(^^;)
[PR]
by signal-9 | 2012-07-11 17:44 | TIPSとかKludgeとか

近デジ PDFで全部ダウンロード 半自動編

【検索ジャンパーの方のための追記】
「結局どうなったのか」だけお求めの方は、こちらの記事をご参照ください。


 「近デジ」こと「国立国会図書館 近代デジタルライブラリー」は本好きにとってはお宝の山と言えるわけだが。

 こういうものはどんどん称揚・翼賛すべきなんであろうが、近年リニューアルされたシステムでも相変わらず、「一冊全部ダウンロード」しにくいのは非常に不満なのである。

 元々データ化することが価値であるし、システムもかなりガンバッてリファインしてくれたのは認めるし、「今の時代だったらオンライン閲覧できればいいじゃん」というのはごもっともなのだが、やっぱり一冊纏まったPDFでローカルに持っておきたいというのが人情というモノだろう…って俺だけかもしれないが。

 「近デジ」ではPDFでダウンロードする手段は標準でサポートされているが、性能上の理由で
  1. 10コマ(ページ)単位でないとダメ

  2. 1回のダウンロード毎に30秒程度アクセス間隔を空けないとダメ
という、中々にシブちんな仕様なのである。

 こういう、単純にスケールアウトで解決できるであろう問題は、そんなにバカみたいにカネはかからないはずだと思うのだが、そこはそれ、天下の国会図書館なので、俺のごとき愚民が思いもよらぬような大人の事情がきっとあるのであろう。

 ブーブー文句を言ってても仕方がないので、無いのなら作ってしまえホトトギス、とりあえず一冊全部まとめてダウンロードするにはどうしたらよいか、ちょっと調べ始めたわけである。

 例として、

 「太古日本のピラミッド 酒井勝軍 著 (国教宣明団, 1934)」

 をまとめてダウンロードすることを考えてみる。
 「なんでよりにもよってそんなヘンな本が例なの?」とかそーゆーことは気にしないように。

(1) PIDを確認する。

 ダウンロードしたい文書のPID(つまり文書番号)を調べる。
 この番号は、「検索結果」の文書名のリンクを見ればわかる。
c0071416_17304482.jpg

  http://kindai.ndl.go.jp/info:ndljp/pid/1137942

 この一番最後の「1137942」が「太古日本のピラミッド」のPIDである。

(2) 最終コマを確認する。

 上のリンクをクリックして、ビューワーが表示されると、その文書の最終コマ数がコマ数指定のドロップダウンの横に表示される。

c0071416_17302822.jpg


 この値はソースのhtmlでいうと フォーム "form-item-information" の lastContentNo の値なので、自動化する場合はこれを取得すれば良さそうだ。

 なぜこの「最終コマ数」が必要なのかというと、近デジの現在のビューワーの「印刷」(PDFはこれで返してくる)の機能は、以前と違い、コマオーバーした時にはエラーを示すhtmlを返すように変更されたようだからである。
 俺の記憶では、以前は確かオーバしていてもコマ数の終わりまではダウンロードしてくれていた。これはシステム的には改悪と評価せざるを得ない。

 この文書の場合、最終コマは49なので、例えば最終ペ-ジまで取得したくて、41-50 と指定するとエラーが発生する。この場合、41-49としなければいけない。


【2012/07/13訂正】
「お知らせ」によれば、
「「印刷する」ボタンによるPDF出力の際、終了コマを省略した場合は上限まで出力するようにしました。
例)コマ数「1-」と入力 → 1コマ目から最大出力コマ目までを出力。全コマ数がそれ以下の場合は、最終コマまで出力。
とのことだった。記述に気がつかなかった俺の完全な誤認だったので、この直前の段は撤回する。

 エラーになると、application/pdfではなく text/htmlを返してくるので、エラー処理を自動にする場合にはresultのcontentsTypeがapplication/pdfであるかチェックするといった方法が必要だろう。

(3) 「印刷」(PDF出力)で行われていることを調べてみる。

 ビューワーの「印刷」を使い、1から10コマをPDFでダウンロードする操作で、どのようなリクエストが近デジサーバに行われているのかを見てみると;

http://kindai.ndl.go.jp/view/pdf/digidepo_1137942.pdf?
pdfOutputRangeType=R&pdfPageSize=&pdfOutputRanges=1-10


という GETリクエスト であることがわかる(戻りはApplication/pdfのストリーム)。

 つまり、

http://kindai.ndl.go.jp/view/pdf/digidepo_【PID】.pdf?
pdfOutputRangeType=R&pdfPageSize=&pdfOutputRanges=【開始コマ】-【終了コマ】


 これで、PDFが戻ってくるわけだ。どうやら積極的にクッキーを使っている節もない、単純なGETリクエストである。。

 一回のアクセスで最大10コマまで取得できるので、先頭の1コマ目から、49コマを最後まで取得するには;
pdfOutputRanges=1-10
pdfOutputRanges=11-20
pdfOutputRanges=21-30
pdfOutputRanges=31-40
pdfOutputRanges=41-49

という5回のGETリクエストを繰り返せばよいようだ。

 ただし、一回ごとのリクエストの間は30秒以上空けろ、という条件がある。

 「今の時代に30秒間隔ですか!」という悪態はとりあえず飲み込んでおこう。
 どのみちこの間隔はサーバ側でチェックされており、短い間隔で3回ほどリクエストすると、エラーになってダウンロードできなくなる。

 まあ、この制限を誤魔化す手はいくらも考えられるが、岡崎図書館事件を忘れてはいけない。

 いきなり威力業務妨害容疑で引っ張られて拘留取り調べなんてことにはなりたくないので、一回ごとのリクエストには必ず30秒スリープするようにしよう。

 コマ数が多い文書だとかなり時間がかかることになるが、そこは我慢である。

(4) テスト。

 ここまで判ったことで、実際に問題なくダウンロードできるか試してみよう。
 いきなりスクリプトだのプログラムだのを作るのは面倒なので、適当なありもので試してみることにする。

 必要なのは、指定したURLにGETを発行して戻りをファイルに保存する何か取得した複数のPDFをひとつに結合する何か、である。

 どちらもフリーウェアで適当なものが入手できるはずだ。

 ここでは手前味噌だが、一例として拙作のhttpget.exeを使ってみる。
httpget "http://kindai.ndl.go.jp/view/pdf/digidepo_1137942.pdf?pdfOutputRangeType=R&pdfPageSize=&pdfOutputRanges=1-10" DUMMY01.PDF
sleep 30
httpget "http://kindai.ndl.go.jp/view/pdf/digidepo_1137942.pdf?pdfOutputRangeType=R&pdfPageSize=&pdfOutputRanges=11-20" DUMMY02.PDF
sleep 30
httpget "http://kindai.ndl.go.jp/view/pdf/digidepo_1137942.pdf?pdfOutputRangeType=R&pdfPageSize=&pdfOutputRanges=21-30" DUMMY03.PDF
sleep 30
httpget "http://kindai.ndl.go.jp/view/pdf/digidepo_1137942.pdf?pdfOutputRangeType=R&pdfPageSize=&pdfOutputRanges=31-40" DUMMY04.PDF
sleep 30
httpget "http://kindai.ndl.go.jp/view/pdf/digidepo_1137942.pdf?pdfOutputRangeType=R&pdfPageSize=&pdfOutputRanges=41-49" DUMMY05.PDF


 というバッチファイルを作る(実際はループで回すべきだが、ここでは説明の為ベタ書き)。

 これを動かして、カレントディレクトリにDUMMY01からDUMMY05.PDFの5個が出来れば成功である。

 後は必要に応じてPDFを結合すればいい。PDF結合ツールは本家 Acrobat X 始め色々あるのでお好きなモノを。

 とまあ、以上のように近デジダウンロードは比較的単純であることが判った。

 もちろん、フツーの人はいちいちバッチファイルを作るようなことはしないで、WSHでもPowerShell、ExcelのVBAでも、要は http get が出来るモノで汎用的なツールを作成するのがよいだろう。

 ということで次回は簡単なコマンドラインプログラムを作ってみる予定。予定は未定。
[PR]
by signal-9 | 2012-07-10 17:36 | TIPSとかKludgeとか