Pythonによるスクレイピングで『小説家になろう』から小説本文を取得する
『小説家になろう』](https://syosetu.com/)とはウェブ小説を掲載している小説投稿サイトであり、色んな面白い小説が無料で読めちゃうすごいサイトです。『小説家になろう』から作品本文を取得する方法を知っていれば自然言語処理やその他機械学習の入力データとして使えて色々便利なので、この記事ではその方法について述べます。本文の取得はPythonによるスクレイピングによって行います。
ちなみに,『小説家になろう』には作品をテキストファイルとしてダウンロードするための正規のサービスが存在します。各作品ページの下部には「TXTダウンロード」のリンクがあり、そこから作品をダウンロードできます(※このリンクはログインしなければ表示されません)。しかし、これには大きな問題が存在し、ダウンロードは作品の各部分ごとのみで可能で作品全文の一括ダウンロードはできません。そのため、何百部分もある長編作品を全文ダウンロードしようとすると何百回もダウンロードボタンを押さなければならず、膨大な労力と時間を消費する羽目になります(経験談)。
注意事項
スクレイピングについての注意事項は以下の記事にまとめられています。
スクレイピングに使用するライブラリ
スクレイピングにはBeautifulSoup4を用います。codnaを用いる場合、インストールは次のようにして行います。
conda install beautifulsoup4
環境
Nコードについて
Nコードとは『小説家になろう』における小説の管理番号です。Nコードは小説閲覧ページのURLhttps://ncode.syosetu.com/n○○/
のn○○などに入っており、小説情報でも確認することができます。例えば、『転生したらスライムだった件』なら「n6316bn」、『Re:ゼロから始める異世界生活』なら「n2267be」です。
全文取得(最もシンプル)
次のコードは『Re:ゼロから始める異世界生活』の全文をnovel.txt
に保存します。
本文の取得は各部分ごとにしかできないため、for文で繰り返すことにより全文を取得しています。
10行目で作品本文ページのURLを指定しています。他の作品の取得を行うためには次のようにすればOKです。
url = "https://ncode.syosetu.com/nXXXXXX/{:d}/".format(part) # 「nXXXXXX」にはNコードを代入する
16行目でCSSセレクタによって作品本文を指定し、本文を抽出しています。なんと#novel_honbun
だけで抽出できちゃいます。超簡単...!
honbun = soup.select_one("#novel_honbun").text
24行目はサーバに過度な負荷を与えないための処置です(1秒は長すぎ?)。
time.sleep(1) # 次の部分取得までは1秒間の時間を空ける
全部分数取得+全文取得
上の方法には微妙な点が一つ。それは、作品の全部分数を手動で入力している点です。全部分数もスクレイピングにより取得すればこれは解決です。
8~13行目で全部分数を取得しています。全部分数は小説情報ページに記載があります。
9行目では小説情報ページのURLを指定しています。
info_url = "https://ncode.syosetu.com/novelview/infotop/ncode/{}/".format(ncode)
10~13行目でページから全部分数を抽出しています。CSSセレクタのみでは抽出できなかったため、正規表現も併せて使っています。なお、この記事の筆者はCSSセレクタの超ビギナーであるため、セレクタのみで抜き出す方法は本当はあるかもしれないです。
info_res = request.urlopen(info_url) soup = BeautifulSoup(info_res, "html.parser") pre_info = soup.select_one("#pre_info").text num_parts = int(re.search(r"全([0-9]+)部分", pre_info).group(1))
差分取得
『小説家になろう』の作品は日々更新されていきます。そのため、ローカルに保存されている部分番号と『小説家になろう』で公開されている部分番号を比較して、その差分だけを取得するようにすると作品の更新に対応できて便利です。以下のコードはそれを実現します。初回の取得および差分の取得はこのコードを実行するだけでできます。なお、このコードでは指定のNコードを名前としてもつディレクトリ内に小説の各部分をそれぞれ個別のファイルとして保存します。
上記コード実行後のディレクトリ構成図 . ├── fetch_novel.py └── n2267be ├── n2267be_1.txt ├── n2267be_2.txt : :
24~30行目でローカルに保存されている部分番号と公開されている部分番号の差分をとっています。ここではset型を利用して差分を取得しています。とても便利。
# すでに保存している部分番号のsetを取得 re_part = re.compile(r"{}_([0-9]+).txt".format(ncode)) existing_parts = {int(re_part.search(fn).group(1)) for fn in os.listdir(novel_dir)} # 新たに取得すべき部分番号のリストを生成 fetch_parts = set(range(1,num_parts+1)) - existing_parts fetch_parts = sorted(fetch_parts)
コマンドラインツール化した(GitHub)
上をコマンドラインツール化したものをGitHubで公開しているので、良かったら使ってみてください。
↓ ↓ ↓
github.com