「小説家になろう」の作品をオフラインで読めるようにするツールとしては、Ruby版の Narou.rb が既にある。かなり作り込まれた実績のあるツールで、サイトごとの取得設定・キャッシュ・差分更新・EPUB化まで一通り揃っている。だが、自分の普段使いのスタックは Python + Emacs で、Ruby環境をわざわざ立ち上げるのも面倒だ。というわけで、Narou.rb のエッセンスだけ拝借して、 Python だけで完結する narou_dl を作ることにした。
リポジトリはこちら: https://github.com/ac1965/narou_dl/
アーキテクチャの分岐点#
最初のバージョンは素直な作りで、なろうのHTMLをスクレイピングして、 <ruby> や <img> タグを保持したまま ebooklib で直接EPUB化するだけのものだった。1話=1ファイルの単純なパイプラインで、これはこれで動く。
ただ、青空文庫形式のEPUB変換ツールとしては AozoraEpub3 (改造版)というJava製の実績あるツールが既にあり、傍点・外字・縦中横・画像回り込みといった高度な組版を持っている。ならば、これを外部プロセスとして呼び出すバックエンドも用意すれば、「シンプルなPython実装」と「AozoraEpub3の組版品質」を両方選べるようになるはずだ、と考えた。
そこで、本文を一旦「青空文庫記法」のテキストに変換し、それを AozoraEpub3.jar にsubprocessで渡してEPUB化する、というもう一つのバックエンドを実装した。narou.rb自身も、実は本文取得だけを自前でやって、EPUB化はAozoraEpub3に丸投げする、という同じ構成を取っている。車輪の再発明を避けるなら、これは筋の良いアプローチだと思う。
実機で踏んだ地雷#
ここからが今回の本題で、机上の実装だけでは絶対に見つからない不具合が、実際になろうの長編を変換してみるとぼろぼろ出てきた。
- コマンドラインの出力先オプションが
-dstだと思い込んでいたが、実際は-d(または--dst)だった。AozoraEpub3は変換に失敗しても exit codeが常に0 を返す仕様なので、これは標準出力の文字列を見ないと気づけないタイプのバグだった。 - なろうの地の文には、著者が装飾記号として
|《》(パイプ・二重山括弧)をそのまま使っているケースがある。これが青空文庫記法のルビ構文の予約文字と衝突し、「ルビ開始文字無し」という警告付きで変換が壊れる。実際に「無職転生」の実データで再現し、地の文由来の記号だけをエスケープする処理を追加した。
無職転生.txt の該当行を見ると:
《五龍将》と呼ばれる配下を操って他の世界を滅ぼした。これはHTMLの
<ruby>タグ由来ではなく、なろうの地の文がそのまま二重山括弧を使っているケースだった。
- 一番厄介だったのは、Apple Booksで縦書き表示したときに 句点だけが行頭に孤立して浮く という不具合だった。CSSの
text-align-lastやline-break/word-breakを、AozoraEpub3が実際に使っている電書協標準CSSに合わせて何度か調整したが直らない。最終的に、AozoraEpub3が生成するXHTMLには一切無いdir“rtl”= 属性が、narou_dl側だけ<html>/<body>両方に付いていることに気づいた。これは本来アラビア語やヘブライ語向けの双方向テキスト(BiDi)制御属性で、縦書き用のものではない。ebooklibのEpubHtmlにdirectionを渡すとこの属性が自動で付与される実装になっており、これを外したら一発で直った。
もう一つ、実際に検証していて肝を冷やしたのが、途中まで「自分のリポジトリのベースは最初にアップロードしたアーカイブと同一」だと思い込んだまま作業していたことだ。実際には来歴が違っていて、削除したはずの古い分割ロジックが復活して見えたりした。最後は個々のパッチを積み上げるのをやめ、最終版のファイル一式を丸ごと比較・上書きする方式に切り替えて収束させた。 git am のcontext不一致に振り回されるくらいなら、最初からこうすればよかった。
落とし所#
一通り直してみると、ebooklib版とAozoraEpub3版の見た目の差の大半は「CSSとHTML構造の実装差」で説明が付くものだった。 dir 属性を外し、 text-align-last 等をAozoraEpub3に合わせ、タイトル・著者はOPFメタデータのみに寄せ、目次もspineから外して視覚的な1ページ目にしないようにした結果、既定バックエンドは再び ebooklib に戻すことにした。AozoraEpub3バックエンドは、傍点・外字・縦中横といった、より踏み込んだ組版が必要なときの選択肢として残してある。
今回のバックエンド比較の要点(まとめ)
- ebooklib: Pure Python、依存が少ない。CSS/HTML構造をAozoraEpub3に寄せたことで品質差はほぼ解消
- aozoraepub3: JRE + AozoraEpub3.jar が別途必要。傍点・外字・画像回り込みなど、より高度な組版が必要な場合向け
地味な作業の積み重ねだったが、「実機で確認しないと分からないバグ」がこれだけ出てくるのは、EPUB×縦書きという組み合わせの厄介さを改めて実感させられた。



