Pixivのスクレイピング

Pixivに投稿された小説の中で服部の瞳の色がどんな感じで描写されてるのか気になったので、調べることにした。そのために、「服部平次」タグのついた小説の本文から「瞳」を検索し、その前後10文字を取ってくるという作業をPythonで書いた。そのときにいろいろ試行錯誤したので、メモしとく。

まず、PixivのHTMLの構成を調べた(2017/7/22時点ではこんなんだったけど、いつ変わるかわからない)。

検索画面

タグ=服部平次, 順番=古い順で検索したときのURLは以下の通り。

https://www.pixiv.net/novel/tags.php?tag=%E6%9C%8D%E9%83%A8%E5%B9%B3%E6%AC%A1&order=date&p=ページ数

ページに作品があるときとないとき

あるとき(*‘▽’)

<dl class="column-related inline-list"></dl>
<ul class="novel-items autopagerize_page_element novel-list">
    <li>小説</li>
    <li>小説</li>
</ul>
<dl class="column-related inline-list"></dl>

novel-itemsの中にそのページの作品がリストで並んでて、上下のcolumn-relatedっていうのは関連タグ。

ないとき(._.)

<div class="_no-item">見つかりませんでした</div>

一つ一つの作品

検索ページでの作品一つ一つのHTMLの構成。さっき<li>小説</li>って略したところ。

<li>
  <section class="_novel-item list-b">
    <div class="novel-contents">
      <div class="imgbox">
        <a href="/novel/show.php?id=000000">
          <img class="cover ui-scroll-view" data-filter="thumbnail-filter lazy-image" data-tags="" data-type="novel" data-id="000000" data-user-id="00000" data-src-filtered="https://source.pixiv.net/www/images/filtered-100.png?1" src="https://source.pixiv.net/www/images/common/transparent.gif">
        </a>
        <div class="chars">0文字</div>
      </div>
      <div class="novel-right-contents">
        <div class="_ui-tooltip bookmark-box" data-tooltip="0件のブックマーク">
          <a href="/novel/bookmark_detail.php?id=000000" class="bookmark-count _ui-tooltip bookmark-box"><i class="_icon sprites-bookmark-badge"></i>0</a>
        </div>
        <div class="title-box">
          <h1 class="title"><a href="/novel/show.php?id=000000">Title</a></h1>
        </div>
        <ul class="data">
          <li class="author">by <a href="/novel/member.php?id=00000" class="user ui-profile-popup" data-user_id="00000" data-profile_img="" data-user_name="Name">Name</a></li>
        </ul>
        <ul class="tags">
          <li><a href="" class="tag-icon">c</a><a href="">Tag1</a></li>
          <li><a href="" class="tag-icon">c</a><a href="">Tag2</a></li>
        </ul>
        <p class="novel-caption">Caption</p>
        <div class="_one-click-bookmark js-click-trackable " data-click-category="abtest_www_one_click_bookmark" data-click-action="novel" data-click-label="000000" data-type="novel" data-id="000000"></div>
      </div>
    </div>
  </section>
</li>

だいたいこんな感じ。作品情報もユーザー情報もほとんどわかるが、投稿日時等は詳細画面に移らないとわからない。

小説の詳細画面

小説の情報

<ul class="meta">
  <li>2017年8月10日 08:10</li>
  <li>小説 2P</li>
</ul>

「3/4」って感じで現在位置とページの総数を教えてくれる表示もあるけど、そこはhtmlでは「読み込み中…」になってて後から書き換えられるタイプなので、スクレイプするのめんどくさそう。

本文

<textarea name="novel_text" id="novel_text">本文本文本文</textarea>

こんな感じで入ってる。このtextareaに全ページ分の文章が入ってて、ページ区切りは[newpage]で仕切られてる。

re.sub(r'\s|\n| ', '', 本文)

↑のように書いたらスペースとか改行取り除けた。

ログインする

初め、ログイン処理書かずにそのままスクレイプしたら、R-18小説とかが取得できない上に検索結果の10ページまでしか見れなかった。それでは困るので、まずPixivに自分のアカウントでログインする。

ログイン処理にはmechanizeがいいって聞いたけど、Python3をサポートしてくれてないので、RoboBrowserを使った。

from robobrowser import RoboBrowser

browser = RoboBrowser(parser='lxml', history=True)
browser.open('https://accounts.pixiv.net/login')

form = browser.get_forms('form', class_='')[0]
form['pixiv_id'] = email_address
form['password'] = password
browser.submit_form(form)

page = 100
browser.open('https://www.pixiv.net/novel/tags.php?tag=%E6%9C%8D%E9%83%A8%E5%B9%B3%E6%AC%A1&order=date&p=' + str(page))
novel_items = browser.find(class_='novel-items')
print(novel_items)

ログインして、試しに服部平次タグで検索した100ページ目にある小説一覧を取ってきた。

Pixivのログイン画面のformタグにはidも何もついてないのでどうしようかと思ったけど、class指定を空にしてget_formsしたやつにインデックス0を指定したら取得できた!

小説情報を取る

novel_items = browser.find(class_='novel-items')
    
if novel_items == None:
    break
    
# ページ内の小説をループ
for novel in novel_items.find_all(class_='_novel-item'):
        
    novel_url = pixiv_url + novel.find('h1').find('a')['href']
    browser.open(novel_url)
    
    # 小説の情報
    title = novel.find('h1').find('a').text
    author = novel.find('li', class_='author').find('a').text
    date = browser.find('ul', class_='meta').find('li').text.split(' ')[0]
    text = re.sub(r'\s|\n| ', '', browser.find('textarea', id='novel_text').text)

これで、タイトル・作者名・投稿日・本文を取得できる。

「瞳」という表現の前後それぞれ10文字を抜き出す

import re
for match in re.finditer('瞳', text):
    print(text[max(0, match.start()-10):min(len(text), match.start()+10+1)])

Wikipediaの文章に使ってみたら下のような感じで出てくる。

膜状組織であり中央に瞳孔と呼ばれる開口部を
後方奥は眼底と言い、瞳孔を通じて検視鏡で観
される。虹彩筋のうち瞳孔括約筋は動眼神経支
約筋は動眼神経支配、瞳孔散大筋は交感神経支
交感神経支配であり、瞳孔径の変化に寄与する
経となり、毛様体筋と瞳孔括約筋に分布する。

こんな感じで出てきたやつを集計した結果、以下のようになった。

あほらしい集計だけど、Pythonでのスクレイピングは初めてだったので楽しかった! 小説書く方たちの豊富な表現も見てて面白かった(*‘▽’)