なろう作品をSend to Kindleする(ツール不要)
背景
「小説家になろう」の作品をKindleの電子インクペーパーで読みたい。以前は便利なWebサービスがあったが、つかえなくなって久しい。代替ツールはあるが、インストールに手間がかかるようだ。
Kindleは、mobiやepubのような電子書籍専用のファイル形式のほかに、textやhtmlにも対応している。よって、複数話をひとつのhtmlファイルに結合するだけで、Kindleにテキストを表示することはできる。この際、縦書きではなくてもよかろう!挿絵もいらない。
やったこと
$ ruby narou2zip.rb [ncode]
のようにコマンドラインで実行すると、zipファイルができる。このファイルを手動でSend to Kindleすればよい。
ただし、もし未インストールのときは、一度だけ事前にNokogiriとRubyzipのインストールが必要。
$ gem install nokogiri
$ gem install rubyzip
require 'open-uri' require 'nokogiri' require 'zip' def scrape(url) opt = {} opt['Cookie'] = "over18=no" html = open(url, opt) do |io| charset = io.charset io.read end return Nokogiri::HTML.parse(html, nil, charset) end # def puts_novel(io, doc) results = doc.xpath('//div[@id="novel_p"]') unless results.length == 0 io.puts results.inner_html.gsub(/<p id="L?\d+">/, '<p>') io.puts '<hr>' end results = doc.xpath('//div[@id="novel_honbun"]') io.puts results.inner_html.gsub(/<p id="L?\d+">/, '<p>') results = doc.xpath('//div[@id="novel_a"]') unless results.length == 0 io.puts '<hr>' io.puts results.inner_html.gsub(/<p id="L?\d+">/, '<p>') 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 writer = doc.xpath('//div[@class="novel_writername"]').inner_text.gsub(/[\r\n]/,"") 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 = File.new(File.basename(title) + '.html', 'w') io = Zip::OutputStream.open(ncode+'.zip') io.put_next_entry(File.basename(title) + '.html') io.puts '<!DOCTYPE html>' io.puts '<html>' io.puts '<head>' io.puts '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' io.puts '<title>'+title+'</title>' io.puts '</head>' io.puts '<body>' io.puts '<h1>'+title+'</h1>' io.puts writer if chapters.length > 1 io.puts '<h2>目次</h2>' chapters.each_index {|i| io.puts '<a href="#Ch_'+i.to_s+'">'+chapters[i]+'</a><br>'} elsif episodes.length > 1 io.puts '<h2>目次</h2>' episodes.each {|e| io.puts '<a href="#Ep'+e[2].gsub("/", "_")+'">'+e[1]+'</a><br>'} else io.puts '<hr>' end if episodes.length == 0 puts_novel(io, doc) else temp= nil episodes.each do |e| STDERR.puts e.join(' ') 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])) sleep 3 end end io.puts '</body>' io.puts '</html>' io.close
きづいたこと
- 拡張子を".html"に変更するだけでは、html形式として識別されない。「<!DOCTYPE html>」や「<html></html>」などの最低限の構成要素を含めるべき。
- ページ内リンク(<a href="#sample">...</a>と<h2 id="sample">...</h2>のセット)がつかえるので、目次もかんたんに作れる。name属性も可。
- ルビタグ(<ruby>...</ruby>)はつかえる。
- アイテム名には、ファイル名が表示される。titleタグ(<title>...</title>)やdc:titleでは表示されない。
- 著者名は、metaタグ(<meta name="Author" content="...">)やdc:creatorタグでは表示されない。
- 改行コードは、macOS/UNIX(LF)でもOK。
- CSSによる縦書き表示(writing-mode: vertical-rl;や-webkit-writing-mode: vertical-rl;)はできない。
- 挿絵の画像は、表示できない。
- MacOSでは、ファイル名を右クリックして圧縮したものをSend to Kindleすると、Amazonからエラーメールが返ってくる。zipコマンドで圧縮しないとだめ。
- 短編の場合とそうでない場合との両方に対応する必要がある。
- R18作品のときは、Cookieに"over18=yes"を指定する。
つぎやること
著者名が自動で入力できるようにしたい。