なろう作品をKindleで読めるePub形式に変換
つかいかた
1. この記事の下部にあるRubyスクリプトを、任意のディレクトリに"narou2epub.rb"というファイル名で保存する。
2. 以下のようにコマンドラインで実行すると、epubファイルができる。[ncode]には、なろう作品のものを指定する。
$ ruby narou2epub.rb [ncode]
注記:未インストールのときは、事前に一度だけNokogiriとRubyzipのインストールが必要。
$ gem install nokogiri $ gem install rubyzip
3. できたファイルをメールに添付し、Kindle端末のメールアドレスに送信する。
Rubyスクリプト
require 'open-uri' require 'nokogiri' require 'zip' def scrape(url) opt = {} opt['Cookie'] = "over18=no" charset = nil html = open(url, opt) do |io| charset = io.charset io.read end return Nokogiri::HTML.parse(html, nil, charset) end def replace(text) text.gsub!(/<p id="L?\d+">/, '<p>') text.gsub!('<br>', '<br />') text.gsub!(/<img .*?>/, '(画像削除)') text.gsub!(/<a .*?>/, '') text.gsub!("</a>", '') return text end def puts_novel(io, doc) results = doc.xpath('//div[@id="novel_p"]') unless results.length == 0 io.puts replace(results.inner_html) io.puts '<hr />' end results = doc.xpath('//div[@id="novel_honbun"]') io.puts replace(results.inner_html) results = doc.xpath('//div[@id="novel_a"]') unless results.length == 0 io.puts '<hr />' io.puts replace(results.inner_html) end end ncode = (ARGV[0].nil?) ? 'n6316bn' : ARGV[0] doc = scrape('https://ncode.syosetu.com/'+ncode+'/') title = doc.xpath('//p[@class="novel_title"]').inner_text creator = doc.xpath('//div[@class="novel_writername"]').inner_text.gsub(/[\r\n]/,"").gsub(/作者:/,"") chapters= [] episodes = [] results = doc.xpath('//dl[@class="novel_sublist2"]') results.each do |r| chapter_title = r.xpath('preceding-sibling::div[@class="chapter_title"][1]').inner_text subtitle = r.xpath('descendant::a').inner_text link = r.xpath('descendant::a/@href').inner_text chapters.push chapter_title unless chapters.last == chapter_title episodes.push [chapters.length-1, subtitle, link] end io = Zip::OutputStream.open(File.basename(title)+'.epub') io.put_next_entry('mimetype') io.puts 'application/epub+zip' io.put_next_entry('META-INF/container.xml') io.puts <<"EOS" <?xml version="1.0" encoding="UTF-8"?> <container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container"> <rootfiles> <rootfile full-path="content.opf" media-type="application/oebps-package+xml" /> </rootfiles> </container> EOS io.put_next_entry('content.opf') io.puts <<"EOS" <?xml version="1.0" encoding="UTF-8"?> <package version="3.0" xmlns="http://www.idpf.org/2007/opf" xml:lang="ja"> <metadata xmlns:dc="http://purl.org/dc/elements/1.1/"> <dc:title>#{title}</dc:title> <dc:creator>#{creator}</dc:creator> <dc:language>ja</dc:language> </metadata> <manifest> <item id="toc" href="toc.xhtml" media-type="application/xhtml+xml" properties="nav" /> <item id="title" href="title.xhtml" media-type="application/xhtml+xml" /> <item id="main" href="main.xhtml" media-type="application/xhtml+xml" /> <item id="style" href="style.css" media-type="text/css" /> </manifest> <spine page-progression-direction="rtl"> <itemref idref="title" /> <itemref idref="toc" /> <itemref idref="main" /> </spine> </package> EOS io.put_next_entry('toc.xhtml') io.puts <<"EOS" <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="ja"> <head> <title>目次</title> <link rel="stylesheet" type="text/css" href="style.css" /> </head> <body> <nav epub:type="toc"> EOS if chapters.length > 1 io.puts ' <h2>目次</h2>' io.puts ' <ol>' chapters.each_index {|i| io.puts ' <li><a href="main.xhtml#Ch_'+i.to_s+'">'+chapters[i]+'</a></li>'} io.puts ' </ol>' elsif episodes.length > 1 io.puts ' <h2>目次</h2>' io.puts ' <ol>' episodes.each {|e| io.puts ' <li><a href="main.xhtml#Ep'+e[2].gsub("/", "_")+'">'+e[1]+'</a></li>'} io.puts ' </ol>' else io.puts ' <ol hidden="hidden">' io.puts ' <li><a href="main.xhtml">本文</a></li>' io.puts ' </ol>' end io.puts <<"EOS" </nav> </body> </html> EOS io.put_next_entry('style.css') io.puts <<"EOS" html { -webkit-writing-mode: vertical-rl; -epub-writing-mode: vertical-rl; writing-mode: vertical-rl; } nav ol { list-style: none; } EOS io.put_next_entry('title.xhtml') io.puts <<"EOS" <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="ja"> <head> <title>表題</title> <link rel="stylesheet" type="text/css" href="style.css" /> </head> <body> <h1>#{title}</h1> #{creator} </body> </html> EOS io.put_next_entry('main.xhtml') io.puts <<"EOS" <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="ja"> <head> <title>本文</title> <link rel="stylesheet" type="text/css" href="style.css" /> </head> <body> EOS if episodes.length == 0 io.puts '<section>' puts_novel(io, doc) io.puts '</section>' else temp= nil episodes.each do |e| STDERR.puts e.join(' ') io.puts '<section>' unless temp == e[0] io.puts '<h2 id="Ch_'+e[0].to_s+'">'+chapters[e[0]]+'</h2>' temp = e[0] end io.puts '<h3 id="Ep'+e[2].gsub('/', '_')+'">'+e[1]+'</h3>' puts_novel(io, scrape('https://ncode.syosetu.com'+e[2])) io.puts '</section>' sleep 3 end end io.puts '</body>' io.puts '</html>' io.close
今後の課題
- 画像は表示できない。