[Python, Selenium, PhantomJS] lazy loadの入っているwebサイトをスクレイピングした時の話

こんばんは、amazonプライムのバッチェラージャパンを見ながら書いてるのですが、僕も奪いあわれたいなぁと思っています。

突然ですが、データ分析の仕事で、外部からデータかき集める必要が出てきてスクレイピングをしました。Lazy Loadのかかっているwebサイトでちょっと苦労したので、その部分だけでもとりあえず備忘録的にまとめておきます。結構調べるの大変だった。

環境は次の通り。

```
macOS 10.2.3
python 3.6.0
phantomjs 2.1.1
selenium 3.0.2
```


## Lazy Loadの処理

最近アニメにはまっているので、練習のために[dアニメストア](https://anime.dmkt-sp.jp/animestore)から、アニメのジャンルを取得することにした。

```lazy_load_scrape.py

import lxml.html as lh
import requests as rq
import cssselect
from selenium import webdriver
import time

# lazy loadが読み込まれるのを待機する時間
pause = 5


# rootの取得
t_url = 'https://anime.dmkt-sp.jp/animestore/gen_sel_pc'
t_html = rq.get(turl).text
root = lh.fromstring(t_html)

# ジャンルのテキストと、リンクの取得
ls = []
for i in root.cssselect('.btnList > a'):
    ls.append({'genre': i.text_content(), 'https://anime.dmkt-sp.jp/animestore' + i.attrib['href']})

# 現状のls
# [{'genre': '\nSF/ファンタジー(733)\n\n', 'child_url': 'https://anime.dmkt-sp.jp/animestore/gen_pc?genreCd=11'}, {'genre': '\nロボット/メカ(214)\n\n', 'child_url': 'https://anime.dmkt-sp.jp/animestore/gen_pc?genreCd=12'}, {'genre': '\nアクション/バトル(606)\n\n', 'child_url': 'https://anime.dmkt-sp.jp/animestore/gen_pc?genreCd=13'}, {'genre': '\nコメディ/ギャグ(466)\n\n', 'child_url': 'https://anime.dmkt-sp.jp/animestore/gen_pc?genreCd=14'}, {'genre': '\n恋愛/ラブコメ(370)\n\n', 'child_url': 'https://anime.dmkt-sp.jp/animestore/gen_pc?genreCd=15'}, {'genre': '\n日常/ほのぼの(112)\n\n', 'child_url': 'https://anime.dmkt-sp.jp/animestore/gen_pc?genreCd=23'}, {'genre': '\nスポーツ/競技(122)\n\n', 'child_url': 'https://anime.dmkt-sp.jp/animestore/gen_pc?genreCd=16'}, {'genre': '\nホラー/サスペンス/推理(160)\n\n', 'child_url': 'https://anime.dmkt-sp.jp/animestore/gen_pc?genreCd=17'}, {'genre': '\n歴史/戦記(75)\n\n', 'child_url': 'https://anime.dmkt-sp.jp/animestore/gen_pc?genreCd=18'}, {'genre': '\n戦争/ミリタリー(55)\n\n', 'child_url': 'https://anime.dmkt-sp.jp/animestore/gen_pc?genreCd=19'}, {'genre': '\nドラマ/青春(556)\n\n', 'child_url': 'https://anime.dmkt-sp.jp/animestore/gen_pc?genreCd=20'}, {'genre': '\nショート(218)\n\n', 'child_url': 'https://anime.dmkt-sp.jp/animestore/gen_pc?genreCd=22'}, {'genre': '\n舞台/ライブ/etc.(87)\n\n', 'child_url': 'https://anime.dmkt-sp.jp/animestore/gen_pc?genreCd=24'}]

# 1個1個子要素にアクセスするためにfor文
for l in ls:
  genre = l['genre']
  c_url = l['child_url']

  # seleniumのドライバにphantomJSを指定し、urlを食わせる
  driver = webdriver.PhantomJS()
  driver.get(curl)

  # 子リソースのrootの取得
  croot1 = lh.fromstring(driver.page_source)

  # cssselectを用いて要素を取得する
  t_element = croot1.cssselect('.webkit2LineClamp')
  
  #lazy loadされてる部分を読み込むために、スクロールダウンしていく
  lastHeight = driver.execute_script("return document.body.scrollHeight")  # スクロールされてるか判断する部分
  while True:
      driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")  # スクロールダウン
      
      time.sleep(pause)  # 読み込まれるのを待つ

      # スクロールされてるか判断する部分
      newHeight = driver.execute_script("return document.body.scrollHeight")
      if newHeight == lastHeight:
          break
      lastHeight = newHeight

  # 全部読み込まれたタイミングで、rootの取得
  croot = lh.fromstring(driver.page_source)

  # cssselectを用いて、要素を指定して取得
  ts = croot.cssselect('.webkit2LineClamp')

  # 欲しかった作品名をリストに格納
  c_elements = [t.text_content() for t in ts]

  # 目標値していた、ジャンルとそれに紐づく作品の一覧
  {genre: c_elements}
```

とりあえずこれで、ジャンルと作品の一覧がとれる。

## まとめ

主にハマるのは非同期の部分だと思います。こんな感じで読み込み前と読み込み後の高さをとってあげると、スクロールダウンしていくことができる。これでとりあえずは、問題ないと思われます。

これで取得できるのは確認しているのですが、とってきたデータはもしかしたら欲しいの以外も混ざっているかもしれないです。

アニメのジャンル分けを一生懸命してるサイトってあんまりないんですよね〜、、　これはこれでまとまってると、アニメ初心者にはいいかも


## 最後に

これを書いている間に、バッチェラージャパンで僕のお気に入りの鎖骨のきれいな女の子が消えてしまった。ショックで泣きそうです。