今日も微速転進

ここではないどこかへ

某雑誌の総集編から特定の連載記事だけ抜き取りたい (その1)


スポンサーリンク

PDFの目次?部分をパースすればいいかと思ったらまたしても単純ではない……。

やりたいこと

WEB+DB PRESS総集編[Vol.1~102] (WEB+DB PRESS plusシリーズ)

WEB+DB PRESS総集編[Vol.1~102] (WEB+DB PRESS plusシリーズ)

  • 作者: 川合史朗,秋葉拓哉,中嶋謙互,木村廉,酒井政裕,ninjinkun,渡辺訓章,WEB+DB PRESS編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2018/04/26
  • メディア: 単行本
  • この商品を含むブログを見る

WEB+DB PRESSの総集編のPDFの一部のページを取り出したい。目視でページ番号を割り出してPDF分割コマンドにしてするのはちょっと面白みがない。

そこで、目次部分のページ番号を参照して必要な箇所だけ取り出したい。

まずはPDFの「しおり」とか「ブックマーク」とか呼ばれているデータを取り出したい。正式名称は「ドキュメントアウトライン(document outline)」。

フォントのアウトライン化の話と用語が似ていてややこしいし、ググりにくい。

文章の概観というか外観。「目次としても」使われるデータ、だそうな。

f:id:atuyosi:20180503102306j:plain:w320

方針および手順

言うまでもないことだが、対象のPDFを入手する必要がある。暗号化などのPDFの状態を考慮して使うツールを決める。

  1. 総集編の目録PDFまたは検索用のPDFで読みたい連載を選び出す
  2. PDF群をパースして、対象の連載の掲載されているページ範囲を特定
  3. 各号のPDFからページを切り出して再結合

可能な限り勉強を兼ねてPythonでやりたかったが、上記の2. に関してはちょうどいいライブラリがないのでpdftkを使うことに。

手元のマシンにはDVDドライブがないのでzip形式のデータをGihyo Digital Publishingのマイページから入手。

なお、PDFのファイル構造の関係上、目次(Outline? bookmark?)データに直接ページ番号は記録されておらず、ページと結びつけるためのリソースIDが記録されているので対応関係を割り出す必要がある。

課題と方式検討。

まず、この総集編のPDFのバージョンは1.6。
そしてこのPDF、セキュリティ保護がかかっている......。そしてその暗号化アルゴリズムにPyPDF2対応していない……。

結局、暗号化を外せたからいいけど。

以下、各種Pythonライブラリと留意点。

  • PyPDF2: 一部の暗号化方式に未対応。また、目次情報のエントリに参照情報がないとエラーになる
  • pdfminer.six: できないことはないけどページの構造を解析する関係で激遅
  • pikepdf: 目次部分の抽出はサポートしてないっぽい(Popplerのラッパーライブラリ)
  • [reportlab(https://www.reportlab.com/) : 出力用
  • python-poppler-qt5: macOSでビルドできず(Popplerのラッパーライブラリ)
  • pdflib: 本文の取り出しと画像の抽出ぐらいしかサポートしてなさそう(Popplerのラッパーライブラリ)
  • pdfrw: 試してないが目次情報に関するAPIはなさそう
  • PyMuPDF: 試してない。実は結構いいかも?

以下、参考情報。

$ qpdf --show-encryption webdb_sp_001-102/webdb_pdf/webdb_vol102.pdf
R = 4
P = -1324
User password =
extract for accessibility: allowed
extract for any purpose: allowed
print low resolution: allowed
print high resolution: allowed
modify document assembly: not allowed
modify forms: not allowed
modify annotations: not allowed
modify other: not allowed
modify anything: not allowed
stream encryption method: AESv2
string encryption method: AESv2
file encryption method: AESv2

制限はかなり緩いけど、暗号化されているのは事実。発行者情報を改ざんされたくないってことか。 何はともあれ、PyPDF2ではアウトライン情報を読み取れなかった。参照先のページ情報のない目次エントリがあるとエラーになってしまう。

あとはqpdfのサンプルソースを改造するという手もあるけど最近C++のコードは書いていないのでちょっと無理。

準備

暗号化の解除

このPDF、普通に暗号化を解除できる。解除できるならする必要があったのか疑問だけど、まあいいでしょう。

qpdfはHomebrewならコマンド一発でインストールできる。

$ brew install qpdf

PDFファイルの暗号化は以下のように実行すると解除できる。

$ qpdf --decrypt 暗号化を外したいファイル名  出力ファイル名

一括処理スクリプト。zipを展開したディレクトリ*1に移動して、適当なファイル名で下記のコードを保存してシェルスクリプトとして実行して一括変換。

#! /bin/bash

mkdir -p decrypt/webdb_pdf
for fname in `ls webdb_sp_001-102/webdb_pdf/*.pdf`; do
    #echo  ${fname} decrypt/webdb_pdf/$(basename $fname)
    qpdf --decrypt ${fname} decrypt/webdb_pdf/$(basename $fname)
done

ものすごく、やっつけ仕事……。

ページ番号の取り出し

pdftk のセットアップ

Homebrew ではインストール出来ないのでTex Wikiを参考にして入手、インストールする。

確かソースからビルドした気がする。 下記のリンク先の、一番上の回答に記載のリンク先からダウンロード。

macos - PDFtk Server on OS X 10.11 - Stack Overflow

参考情報:pdftk on OSX10.11(El capitan) - Qiita

$ pdftk --version

pdftk 2.02 a Handy Tool for Manipulating PDF Documents
Copyright (c) 2003-13 Steward and Lee, LLC - Please Visit: www.pdftk.com
This is free software; see the source code for copying conditions. There is
NO warranty, not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

使用方法

暗号化されたPDFの場合は復号用のパスワードが必要なはずですが、qpdfで復号化済みなので試していない。

$ pdftk  対象のPDFファイル名 dump_data_utf8 output 出力ファイル名

具体例としては以下のようにして実行。

$ pdftk  decrypt/webdb_pdf/webdb_vol102.pdf dump_data_utf8 output test_vol102.txt

出力ファイルに下記のようにメタデータが諸々出力される。各ファイルについてBookmarkTitleBookmarkPageNumberさえあればOK。

InfoBegin
InfoKey: ModDate
InfoValue: D:20180220144306+09'00'
InfoBegin
InfoKey: CreationDate
InfoValue: D:20171213141952+09'00'
InfoBegin
InfoKey: Author
InfoValue: (株)技術評論社
InfoBegin
InfoKey: Title
InfoValue: WEB+DB PRESS Vol.102
InfoBegin
InfoKey: Creator
InfoValue: Acrobat 11.0.22
InfoBegin
InfoKey: Producer
InfoValue: Acrobat Distiller 11.0 (Macintosh); modified using iText 2.1.7 by 1T3XT
PdfID0: 3c25c6900f3f6c5a51982f03fa38b8f8
PdfID1: ffb6b04b6d4411a1543b5883ef727550
NumberOfPages: 179
BookmarkBegin
BookmarkTitle: 表紙
BookmarkLevel: 1
BookmarkPageNumber: 1
BookmarkBegin
BookmarkTitle: 目次
BookmarkLevel: 1
BookmarkPageNumber: 3
(以下省略)

連載記事名とページ番号の取り出し

pdftkの出力をパースして、タブ区切りのテキストで出力する。

ファイル名決め打ちなのでそこをいじって一括処理用にする予定。

#! /usr/local/bin/python3
 # coding: utf-8

import re

filename = 'data/output_log/dump_webdb_vol102.txt'

fp = open(filename, 'r')


pdf_filename = re.search('dump_(.+).txt', filename)[1] + '.pdf'

bk_list = []
entry = { 'title': "", 'page_no': None}
last_page = 0

for line in fp:
    if line.startswith("NumberOfPages"):
        last_page = int(line.rstrip("\n").split(': ')[1])

    if line.startswith("PageMediaBegin"):
        bk_list.append(entry)
        break

    if not line.startswith("Bookmark") :
        continue
    else:

        if line.startswith("BookmarkTitle"):
            entry['title'] = line.rstrip("\n").split(': ')[1]
        if line.startswith("BookmarkPageNumber") :
            entry['page_no'] = int(line.rstrip("\n").split(': ')[1])

        if line.startswith("BookmarkBegin") :
            if len(entry['title']) > 0:
                bk_list.append(entry)
                entry = { 'title': "", 'page_no': None}

        if line.startswith("PageMediaBegin"):
            bk_list.append(entry)
            break

fp.close()


filtered_list = [ e for e in bk_list if e['page_no'] > 0  ]

sorted_list = filtered_list

sorted_list.sort(key=lambda x: x['page_no'])

for i, e in enumerate(sorted_list):
    end_page = last_page
    try:
        # print(i)
        n = sorted_list[i+1]
        next_article_page = n['page_no']

        end_page = next_article_page - 1
    except:
        pass

    print(pdf_filename, e['title'], e['page_no'],end_page, sep="\t")

元のデータでは目次が階層構造になっており、その情報を使えばどれが特集と連載記事をカテゴリ分けすることはできる。ページの並びと一部の記事の目次データ上の並びが違う関係で面倒なので考慮していない。

もっとスマートに書けるはずだけど、面倒だから頃の辺で。

出力例

こんな感じ。他のファイルも同じ要領でTSVで吐き出して、grepとpdftkなりqpdfで切り出して、あとは目次情報を追加すれば完成。手作業でもいいかも。

WEB+DB PRESSのPDFを分割するためのページ番号対応表(vol. 102)

まとめ

手作業でやるほうがラクな気がしてきた。それとこれ一個でなんでもできるよって感じのPythonのPDF用ライブラリがない......。

それはさておき、この雑誌、Pythonの連載はないのか。それにRails 5の特集も組まれてないし。

以外なことにLaravelも特集のテーマになってない。不思議。

Railsのイメージが強かったんだけど、最近はテストとかモバイルアプリ(特にiOS)に注力しているのか。

Software Design総集編【2013~2017】

Software Design総集編【2013~2017】

退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

退屈なことはPythonにやらせよう ―ノンプログラマーにもできる自動化処理プログラミング

PDFインフラストラクチャ解説 第1.1版

PDFインフラストラクチャ解説 第1.1版

  • 作者: 小林徳滋
  • 出版社/メーカー: アンテナハウスCAS電子出版
  • 発売日: 2017/03/07
  • メディア: オンデマンド (ペーパーバック)
  • この商品を含むブログを見る

*1:またはDVDからPDFをごっそりコピーした先。パスは適宜書き換え

広告