Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

この投稿は、Ruby Advent Calendar 2012 の21日目の記事です。前回は @tadsan 先生でした。

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

なれる!SEとは

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

なれる!SE という、青少年向けの体裁で、イラストレーションがアニメ風美少女の小説があります。この小説は、そのキャッチーな見た目とはうらはらに、苦労が多く報われづらいシステムエンジニアのものづくりの仕事をハードコアに描いた、2010年代のプロレタリアート文学です。筆者の観測範囲内でも、じわじわと読者が増えつつあるのを感じます。

そのなれS(略称)ですが、ドラマCDの発売を記念してウェブ小説を公開しています。しかし、職場のブラウザで見ると周りの目が気になるので、落としてきてepubなどに変換して専用デバイスで見られたらなあ……などと。

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

ウェブ小説をScrapingする - Nokogiri

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

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

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

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 と言う素晴らしいメソッドが標準で存在するのでそれを使うと短く書けていいですね。

paragraphs

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

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

epub生成には、gepubと、そのラッパーであるMaliqを使いました。詳細な使い方はリンク先の解説の通りです。各ページをmarkdownにしてコマンドを実行すればepubを作ってくれるので素晴らしく楽です。

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

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
↑
ウェブサイトからスクレーピング

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

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

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

これで、

bundle && rake

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

全てのスクリプトは Gist に上がっています。

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

自分のためのコードを書きましょう!!!1

注意

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

明日の予定

明日は揚がる!からあげ、 @sasata299 さんです。

udzura
投稿しているコードは、指定が無い限り MIT とします。
http://udzura.hatenablog.jp
pepabo
「いるだけで成長できる環境」を標榜し、エンジニアが楽しく開発できるWebサービス企業を目指しています。
https://pepabo.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした