この投稿は、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のインスペクタなどでさくっと確認するのが速いでしょう。
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
と言う素晴らしいメソッドが標準で存在するのでそれを使うと短く書けていいですね。
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
↑
ウェブサイトからスクレーピング
と言う依存関係があるので、それを素直に書いて行けば大丈夫です。
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 さんです。