LoginSignup
7
8

More than 5 years have passed since last update.

(メモ)とあるWebページから欲しい部分だけを抜き出してSphinxページに直し、それをPDFにして印刷するまで

Last updated at Posted at 2015-03-14

わけあってこのページにある健歩体操というものを資料化したかった。
お試しでSphinxの勉強をしていたのでこれを使う。無理やり使っている感もあるので実はものすごく効率が悪かったかもしれないけれど、割とよく出来たからまあ良し。

成果物(健歩体操)

空白ページとかが途中で混じっているけれどもまあ良し。

使ったもの

Sphinx

BeautifulSoup

HTMLParser というものがあるみたいだが使い方がよく分からず、個人的に分かりやすかったのでこれを使った。

pip install BeautifulSoup

あとはこれを使って問題のページのHTMLから解説画像と文章を抜き出し、Sphinxで資料の形に直していく。
詳しくは後で。

Sphinxページに直す

だいたいの流れを考えよう

まず体操のページのURLは、

http://kaigoouen.net/program/gymnastics/gymnastics_{体操の種類の番号}_{ページ番号}.html

という感じで、数字の入る箇所が二つある(どうでもいいけど、このサイトはまさかページごとにHTMLを作ってるのだろうか?)。

体操の種類の番号

健歩体操にはホップ、ステップ、ジャンプと三種類のレベルがあるのだが、ホップなら2、ステップなら3、ジャンプなら4が入るらしい(ちなみに1には前説みたいなもののようなので今回は不要)。

ページ番号

ホップ、ステップ、ジャンプいずれの体操も複数のページから成っているのでここにはそのページ数が入る

これらを踏まえて考えたら大体こんな感じの流れか?

  1. 体操の種類を選択
  2. 選んだ体操からページを選択
  3. 選んだページのHTMLから欲しいところを抜き出す
  4. 次のページに進んで2に戻る(最終ページになるまで繰り返す)

作る資料はホップ、ステップ、ジャンプでそれぞれ個別に作るつもりなのでだいたいこれでOK。

細かい流れを考えよう

上で考えた流れでいうと、3の部分を考える。

どうやら自分が欲しい部分はbox02というクラスのdiv要素内にある模様。

例えばホップならこんな感じ。

<div class="box02">
    <h2>
        <img alt="項目名" src="text.gif"></img>
    </h2>
    <div class="box02_txt_hop">
        <div class="hop_box">
            <div class="hop_txt">解説</div>
            <div class="hop_image">
                <img alt="参考写真" src="image.jpg"></img>
            </div>
        </div>
        <div class="hop_box">
            <!--項目の続き-->
        </div>
</div>
<div class="box02">
    <!--次の項目-->
</div>

で、この中で欲しいのは・・・

  • 項目名
  • 解説
  • 参考写真のsrc部分

以上。BeautifulSoupのモジュールを使ってこれらを絞り込む。要素名とかクラス名でfindやらfindAllできるのでそれほど難しくなかった。
あとは絞り込んだものをSphinx用の.rst形式に書き換えるPythonスクリプトを書く。ドキュメントを見ながら頑張るのみ。

ひとまず項目名は見出しにし、解説文と画像は横に並べたかったのでテーブル方式とした。

少し困ったこと

元のページのHTMLが変だった

各項目の解説文と画像を抜き出す際に最初は該当箇所をクラス名で上位から辿ってみようと考えた。で、Firefoxの要素を検証で調べてみたところ、どうやらbox02>box02_txt_hop>hop_box>hop_imageまたはhop_txtという感じで掘り下げていけばよさそうだったのでそれでやっていたのだが、そうするとどうにも各項目ともに最後の解説が抜け落ちてしまう。
それでよく見てみたところ、どうやらなぜか各項目の最後になる解説だけは文章、画像ともにhop_boxクラスに含まれておらず、上位のbox02_txt_hopの直下となっているようだった(理由は不明)。なので、hop_boxは最初から無視する方向に決定。そもそもBeautifulSoupのfindやfindAllは対象とした要素以下全てから条件に沿って探してくれるっぽいので、わざわざ一階層ずつ探っていく必要はなかったみたいだ。

テーブルが自動で縞模様になる

中身がテキストだけならそのほうが綺麗なのだろうが画像込みだと個人的には見づらくて気に入らなかった。しかしスタイルの変え方(HTMLはともかくPDFのほう)がよく分からなかったのでいちいち新しいテーブルを作る形式にして対処した。

UnicodeDecodeErrorとUnicodeEncodeError

Pythonスクリプトから.rstファイルを出力する際に頻出。

type(text)

とかで調べながら地道に直していく。

PDFにして印刷するまで

rst2pdflatexという二通りのやり方があるようだが、前者のほうが簡単そうに見えたので今回はrst2pdfを使うことにした。

そして、次々とハマったのだ(pipでインストールしたのがいけなかったのかも)。

make pdfでエラーになる

ImportError: reportlab requires Python 2.7+ or 3.3+; 3.0-3.2 are not supported.

で、ここを参考に

pip install -U reportlab==2.5

したらエラーが変化。

[ERROR] pdfbuilder.py:130 need more than 3 values to unpack
Traceback (most recent call last):
File "/home/vagrant/www/public/hopstepjump/venv/lib/python2.6/site-packages/rst2pdf/pdfbuilder.py", line 78, in write
docname, targetname, title, author = entry[:4]
ValueError: need more than 3 values to unpack
FAILED
build succeeded, 1048 warnings.

要はconf.pyの書き方が違った。最初自分はpdf_documentsにはpdfにしたいドキュメントを

('hop','step','jump'),

みたいにして連ねるのだと思っていたがそれは違っていて、

('docName', u'ファイル名', u'PDFの表紙にくるタイトル', u'著者'),

ということらしい(というか実はマニュアルに書いてあった)。このタプルはリストの中に含まれているので、複数のドキュメントをPDF化したいのであれば複数のタプルを加えればいい。

ともあれ気を取り直してまたmake pdf

IOError: decoder jpeg not available
identity=[ImageReader@0x3a3ce10 filename='/home/vagrant/www/public/hopstepjump/venv/lib/python2.6/site-packages/rst2pdf/images/image-missing.jpg']
FAILED
build succeeded.

またしても新たな問題。jpegが駄目?

調べてみたところ別にPILが悪いのではなく、libjpegというのが必要らしい。
http://kwmt27.net/index.php/2013/07/14/python-pil-error-decoder-jpeg-not-available/

これをインストールしてPILも再インストールとのこと
yumでいけるかな?

yum search libjpeg

============================= N/S Matched: libjpeg =============================
libjpeg-turbo-devel.i686 : Headers for the libjpeg-turbo library
libjpeg-turbo-devel.x86_64 : Headers for the libjpeg-turbo library
libjpeg-turbo-static.x86_64 : Static version of the libjpeg-turbo library
libjpeg-turbo.x86_64 : A MMX/SSE2 accelerated library for manipulating JPEG
: image files
libjpeg-turbo.i686 : A MMX/SSE2 accelerated library for manipulating JPEG image
: files

どれか分からないのでyum install libjpegしてみる(勝手に最適なやつを選んでくれることを期待)。

Updated:
libjpeg-turbo.x86_64 >0:1.2.1-3.el6_5

Complete!

できたようなので参考にしたページに従い、

pip install -I pillow

make pdf

IOError: decoder jpeg not available
identity=[ImageReader@0x221e150 filename='/home/vagrant/www/public/hopstepjump/venv/lib/python2.6/site-packages/rst2pdf/images/image-missing.jpg']
FAILED
build succeeded.

まさかの進展なし・・・。
yum list | grep libjpegしてみたらちゃんとインストールはできてるんだけど、pillowとpilって同じじゃないんですかね。

ということで、

pip install pil

しかし変わらず・・・・。

これか?http://all-rounder-biz.blogspot.jp/2013/06/macioerror-decoder-jpeg-not-available.html
違う・・・・・。

じゃあこれ?http://d.hatena.ne.jp/rougeref/20130116
はい残念でした・・・・・・。

えーと、PILインストール後の表示を見ると


*** TKINTER support not available
*** JPEG support not available
--- ZLIB (PNG/ZIP) support available
*** FREETYPE2 support not available
*** LITTLECMS support not available
--------------------------------------------------------------------

となっている。確かにJPEGはnot availableのままだ。エラーメッセージは嘘をつかない。

ここか?http://dev-pao.blogspot.jp/2010/04/python-imaging-library-piljpeg.html
駄目・・・・・・。

http://d.hatena.ne.jp/rougeref/20130116

あ、ひょっとして・・

yum install libjpeg-devel

で、PILを消して再インストール。

--- JPEG support available

develがインストールできていなかったのが原因だったらしい。
ともあれJPEG問題は解決ということでmake pdf

[ERROR] image.py:110 Missing image file: /home/vagrant/www/public/hopstepjump/http://kaigoouen.net/img/hop_pic_108.jpg
\u304b\u304b\u3068\u304b\u3089\u3064\u3044\u3066\u30fb\u30fb\u30fb\u3064\u307e\u5148\u3067\u3051\u308a\u51fa\u3059\u3001\u3092\u7e70\u308a\u8fd4\u3057\u306a\u304c\u3089\u3001\u6b69\u304d\u307e\u3057\u3087\u3046\u3002 line
done
build succeeded.

よし、上手くいっていないけれども画像を読み込もうとしてる場所が明らかに変ですね。
しかし直し方はよく分からない。外部から画像を取ってくることは想定していないのか?

エラーメッセージ的に調べるのが難しかったので色々と実験してみたところ、どうやら.rst内でreplaceを使わなければ大丈夫なようだった。
一種のバグなのだろうか?なんにせよ使わない方向で解決。

次、文字化けしている。

怪しいのは、

sh: fc-match: コマンドが見つかりません
[ERROR] findfonts.py:208 Unknown font: DejaVu Sans Mono-Bold

の部分(いっぱい出ている)。fc-matchとは何ぞや?ということでググってみると、どうやらfontconfigというライブラリで入るコマンドみたい。

ではyumで

yum search fontconfig

=========================== N/S Matched: fontconfig ============================
fontconfig.i686 : Font configuration and customization library
fontconfig.x86_64 : Font configuration and customization library
fontconfig-devel.i686 : Font configuration and customization library
fontconfig-devel.x86_64 : Font configuration and customization library

また普通のとdevelとがある。同じ轍は二度踏まない・・・ということでdevelをインストール。
そしてまたmake pdfしてみたところ、先の「コマンドが見つかりません」は解消。
ただ下の行は未解決。そもそも'DejaVu Sans Mono-Bold'てあるけど、そんなフォント知らないし何これ?

あ、どうみても英語用のフォントだ。ということはスタイルシートを読み込めてない?
conf.pyは間違っていなさそうだったので、スタイルシートとしているja.jsonを調べてみる。ドキュメント通りに書いたと思うのだが。
で、数時間くらい悩んだ結果が、一ヵ所だけクォーテーションを閉じていなかったという凡ミス。あとシングルでは駄目で、ダブルじゃないといけないらしいこともついでに分かった。

何にせよそれでようやく設定した日本語フォントを探してくれるようになった・・・のだが、見つからない
というかそもそもfc-listコマンドで何も返ってこない状態だった。

まあ、フォントファイルを/usr/share/fonts以下に移動したら無事解決し文字化けも直ったのだが、そうなるとconf.pyのpdf_font_pathの意味が分からない。現状は中身を空にしているが問題なく日本語表示できているし、試しに他の場所のパスを書いても反映されていない感じ。他の場所に置きたいときはどうすればいいのだろうか?(Fontconfigの設定をどこかで書き換える?)

こんな感じのPythonスクリプトになった

hopstepjump.py
#! /usr/bin/env python
# *-*coding:utf-8*-*
import sys,re,urllib2

from BeautifulSoup import BeautifulSoup


#### reference page ####
base_url = 'http://kaigoouen.net'

hopstepjump = {
    'hop':{
        'index':2,
        'last_page':25,
    },
    'step':{
        'index':3,
        'last_page':39,
    },
    'jump':{
        'index':4,
        'last_page':46,
    },
}


#### for make .rst files ####
br = u'\n'

page_break = u".. raw:: pdf%s   PageBreak" % (br*2)

def soup_to_sphinx(pg):

    p = 1

    while p <= pg['last_page']:

        url = base_url + '/program/gymnastics/gymnastics_{index}_{page}.html'.format(index=pg['index'],page=p)

        htmldata = urllib2.urlopen(url)

        soup = BeautifulSoup( unicode(htmldata.read(),'utf-8') )

        for box in soup.findAll('div',{'class':'box02'}):

            lessons = box.find('div',{'class':'box02_txt_%s' % choice})

            if lessons is not None:

                title = box.contents[1].contents[0]['alt']
                print( sphinx_head(title) )

                images = lessons.findAll('div',{'class':'%s_image' % choice})

                texts = lessons.findAll('div',{'class':'%s_txt' % choice})
                texts = iter(texts)

                for image in images:
                    src = base_url + image.contents[0]['src']
                    image = sphinx_image(src)

                    text = texts.next().renderContents()
                    text = sphinx_text(text)

                    print( sphinx_listtable(image,text) )

                print(page_break)               

        htmldata.close()
        p += 1


def sphinx_head(txt):
    return br.join([br,txt,u"="*30+br]).encode('utf-8')


def sphinx_listtable(s_img,s_txt):
    table = u".. list-table::" + br
    image = u"   * - %s" % s_img
    text = u"     - | %s" % s_txt

    return br.join([table,image,text,br]).encode('utf-8')


def sphinx_image(src):
    option = u":width: 150pt"
    return u".. image:: %s" % src + br + u"          %s" % option


def sphinx_text(txt):
    text = txt.decode('utf-8').replace(u"<br />",br)
    if br in text:
        texts = text.splitlines()
        text = reduce(lambda x,y: x +  br + u"       | " + y,texts)
    return text



if __name__ == '__main__':

    try:
        choice = sys.argv[1]
        page = hopstepjump[choice]
    except IndexError:
        print("[エラー]:オプションが必要です('hop'か'step'か'jump'のいずれか)。")
        exit("    - 例: 'python %s hop'" % sys.argv[0])
    except KeyError:
        print("[エラー]:オプションが違います('hop'か'step'か'jump'のいずれか)。")
        exit("    - 例: 'python %s hop'" % sys.argv[0])

    soup_to_sphinx(page)

使い方

python hopstepjump.py hop > hop.rst

一行のコマンドで1000行を超えるファイルが書き上がるのはなかなかの快感だ。
ともあれこれで構成を作ったらあとはmake htmlなりmake pdfをするだけ。

GitHub

ところで知らない外国人の人がauthoredになっているのだが、これってひょっとして何かイタズラされてる?

7
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8