自分のためのコードを書こう - Web小説からepubを作る

この投稿は、Ruby Advent Calendar 2012 の21日目の記事です。前回は [@tadsan](http://qiita.com/items/7afce87fe2cae3580f61) 先生でした。

今回は、自分のために書いたコードを解説してみます。基本的なライブラリばかりで申し訳ない感じはありますが……。

## なれる！SEとは

ところで、みなさんは『なれる！SE』と言う小説をご存知でしょうか。

[なれる！SE](http://www.amazon.co.jp/gp/product/4048686054/ref=as_li_ss_tl?ie=UTF8&camp=247&creative=7399&creativeASIN=4048686054&linkCode=as2&tag=udzura1984-22) という、青少年向けの体裁で、イラストレーションがアニメ風美少女の小説があります。この小説は、そのキャッチーな見た目とはうらはらに、苦労が多く報われづらいシステムエンジニアのものづくりの仕事をハードコアに描いた、2010年代のプロレタリアート文学です。筆者の観測範囲内でも、じわじわと読者が増えつつあるのを感じます。

そのなれS（略称）ですが、ドラマCDの発売を記念して[ウェブ小説](http://nareru-se.dengeki.com/webnovel/?pagenum=1)を公開しています。しかし、職場のブラウザで見ると周りの目が気になるので、落としてきてepubなどに変換して専用デバイスで見られたらなあ……などと。

と言うことで、そのためのツールを作ってみることにしました。

## ウェブ小説をScrapingする - Nokogiri

まず、サイトの内容をそのまま落としてきては、epubにした際に読みづらいし、余計な内容が含まれていますので、コンテンツを抽出する必要があります。いわゆる Web Scraping です。

Ruby の場合は、Nokogiriライブラリを利用するのが定番になっています。言ってみればlibxmlのラッパーですが、柔軟なAPIを持っています。

```ruby
require 'nokogiri'
require 'open-uri'
module SurugaUtils
  class NA00S3
    def initialize(uri="http://nareru-se.dengeki.com/webnovel/?pagenum=1", index=1)
      source = open(uri).read
      @doc = Nokogiri::HTML(source)
      @index = index
    end
    attr_reader :doc, :index
  end
end
```

こうすれば、 `Nokogiri::HTML::Document` のインスタンスを得られます。このオブジェクトは `#css` という、 CSSセレクタで要素を絞り込むメソッドを持ち合わせてます（jQueryのCSSセレクタ指定などと同じように使えます）。

CSSセレクタは、Chromeのインスペクタなどでさくっと確認するのが速いでしょう。

![inspection](http://blog.udzura.jp/wp-content/uploads/2012/12/inspection-nareru.png)

```ruby
module SurugaUtils
  class NA00S3
    include Nokogiri::XML
    def to_readable_paragraphs
      main_nodes.
        chunk{|node| (node.is_a? Element and node.name == 'br') ? nil : true }.
        map(&:last).
        map{|parts| parts.map{|node| node.text.strip }.join }
    end

    def main_nodes
      doc.css("#main_frame .novel > p").children
    end

    def title
      doc.css("#main_frame > #left_frame > img").map{|img| img.attributes["alt"].value }.max_by(&:length)
    end
  end
end
```

こんなイメージで、「`<br>`」区切りでパラグラフを分割します。`#to_readable_paragraphs`は、最初eachで頑張ってたんですが、[`Enumerable#chunk`](http://doc.ruby-lang.org/ja/1.9.3/method/Enumerable/i/chunk.html) と言う素晴らしいメソッドが標準で存在するのでそれを使うと短く書けていいですね。

![paragraphs](http://blog.udzura.jp/wp-content/uploads/2012/12/paragraphs.png)

## Scrapingしたデータを epub に直す - gepub, Maliq

データは取り出せたので、これをepubにふさわしく整形しましょう。

epub生成には、gepubと、そのラッパーである[Maliq](http://melborne.github.com/2012/12/03/when-bloggers-become-publishers/)を使いました。詳細な使い方はリンク先の解説の通りです。各ページをmarkdownにしてコマンドを実行すればepubを作ってくれるので素晴らしく楽です。

以下、markdown生成個所の抜粋です。

```ruby
require 'fileutils'
require 'erb'
require 'date'
module SurugaUtils
  class NA00S3
    def generate_build_files(dir='./', file_name="nareru-se-%02d.md")
      unless File.directory? dir
        FileUtils.mkdir dir
      end
      file = File.join dir, (file_name % index)
      File.open(file, 'w') do |file|
        body = to_readable_paragraphs.join("\n\n")
        file.write ERB.new(TEMPLATE).result binding
      end
      STDERR.puts "generated #{file}"
    end

  TEMPLATE = <<-EOT.gsub(/^ {4}/, '')
    ---
    language: 'ja'
    unique_identifier:
     - 'http://nareru-se.dengeki.com/'
    title: 'ドラマCD発売記念特別短編「立華ズ・ブートキャンプ」'
    subtitle: '萌えるSE残酷物語'
    creator: '夏海公司'
    date: '<%= Date.today.to_s %>'
    ---
    # <%= title %> - <%= index %>

    ## chapter <%= index %>

    <%= body %>
  EOT
end
```

## Scrapingからepub生成までを自動化 - Rake

Maliqのおかげで素晴らしく楽なのですが、せっかくなのでScrapingからepub生成までをコマンド一発にしたいですね。そこで、Rakeを使ってみます。

Rakeはすっかり一般的なタスク実行のためのツールとして知られていますが、そもそもの元ネタの`make`のように、ファイルをビルドするための依存関係を記述することができます。

今回作るファイルには、

```
nareru.epub # GOAL
↑
*.xhtml, *.css, *.png
↑
*.md
↑
ウェブサイトからスクレーピング
```

と言う依存関係があるので、それを素直に書いて行けば大丈夫です。

```ruby:Rakefile
PAGES = 1..7
FILE_NAME = "nareru-se-%02d.md"
MD_FILES = PAGES.map {|index| FILE_NAME % index }
XHTML_FILES = MD_FILES.map {|f| f.sub '.md', '.xhtml' }

file 'nareru.epub' => (XHTML_FILES + ["images/cover.png", "css/style.css"]) do
  sh 'maliq_gepub -o nareru.epub'
end

rule '.xhtml' => '.md' do
  sh 'maliq *.md'
end

# せっかくなので表紙画像
file 'images/cover.png' do
  mkdir_p "images"
  sh "wget http://nareru-se.dengeki.com/common/img/top/bg_kv_top.png -O images/cover.png"
end

file 'css/style.css' do
  mkdir_p "css"
  open('css/style.css', 'w') do |f|
    f.write <<-EOCSS
h1, h2, h3 {
  color: #8B1A1A;
}

ol {
  list-style-type: none;
}
    EOCSS
  end
end

rule '.md' do
  require './my-own-purpose-script'
  PAGES.each do |index|
    uri = "http://nareru-se.dengeki.com/webnovel/?pagenum=#{index}"
    SurugaUtils::NA00S3.new(uri, index).generate_build_files("./", FILE_NAME)
  end
end

desc "Scrape web contents and generate epub"
task :default => 'nareru.epub'

desc "Cleanup files"
task :clean do
  (MD_FILES + XHTML_FILES + %w(nareru.epub images css)).each {|path| rm_rf path }
end
```

一応クリーンアップのタスクも用意しました……。

これで、

```bash
bundle && rake
```

を実行すれば、同じディレクトリに `nareru.epub` がめでたく作成されます。適当なリーダーで閲覧してください。

全てのスクリプトは [Gist](https://gist.github.com/4350990) に上がっています。

と言うことで、皆さんも是非

# 自分のためのコードを書きましょう！！！１

## 注意

最後に、当該ウェブ小説の諸権利はアスキーメディアワークスと作者の夏海公司さまにありますので、Web Scrapingした結果作られた epub は、個人での利用にとどめてください。

## 明日の予定

明日は揚がる！からあげ、 [@sasata299](https://twitter.com/sasata299) さんです。