ICLR2019 OpenReview ページから論文一覧を取得する

# 目的
- [ICLR2019 OpenReview](https://openreview.net/group?id=ICLR.cc/2019/Conference) に上がってる論文の一覧情報を取得し，表形式で管理をしたい

# 結果
- 全論文情報を含む html を取得し，取得した html から論文の下記情報を取得した．(本記事は htmlの取得まで）
    - title
    - date
    - keyword
    - abstract
    - tldr 

# 課題
ページのhtmlを beautiful soup を用いてスクレイピングすればよいが，下記課題が存在

- ICRL2019 OpenReview ページは動的コンテンツになっておりスクロールしないとコンテンツが表示されない
- ボタンをクリックしないと abstract が表示されない

-> **Selenium を用いてchrome を操作し，全てのコンテンツが表示された状態のhtmlを取得すれば良い**

# 解決手順
1. selenium を通じてchromeを操作し，コンテンツを全て表示するまでスクロール
2. 全て表示したら，各論文の show detailsボタンを順次クリックすることで abstractを表示させる
3. ページのソースを取得し，beautiful soup を通じて論文の情報を取得する

```python
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

CHROME_PATH = 'path/to/chromedriver'

def get_all_source(url):
　　# 全てのソースを取得する関数
    browser = webdriver.Chrome(CHROME_PATH)
    browser.get(url)  # 指定した url をクロームで開く
    
    scroll_down(browser)  # 全てのコンテンツが表示されるまで下にスクロールを行う
    toggle_all(browser)  # 各論文の show details ボタンをクリックし，abstract を表示させる
    source = browser.page_source  # 全てのコンテンツ情報が表示された状態のページの html を取得
    browser.close()
    time.sleep(0.3)    
    
    return source
```

## 全てのコンテンツが表示されるまで下にスクロール
- コンテンツは有限なので，全てのコンテンツが表示されるまで下にスクロールを続ける

```python
def scroll_down(browser):
    prev_source = None
    for i in range(0, 1000):
        if i == 0:
            time.sleep(1)
            
        browser.execute_script('window.scrollBy(0,2000)', '')  # ページを下へスクロール
        scroll_count += 1
        time.sleep(0.3)
        print ('Scroll: {}'.format(scroll_count))
        
        if scroll_count % 10 ==0:
            if browser.page_source == prev_source:  # スクロールしてもページの情報が変わらなければ終了
                break
            prev_source = browser.page_source
    return
```

## 各論文の Show details ボタンをクリックして abstract を表示させる
- クリックをさせる際には，クリック対象が画面に表示されていなければならない
    - 表示されていない状態でクリックさせようとすると element is not visible エラー
    - クリック対象の位置まで画面遷移 & is_displayed() メソッドで表示されていることを確認して対処
- is_displayed()==True でもクリックできない場合が存在
    - クリック対象の上に何かしらのオブジェクト等が存在する場合
        - 今回は，ページ最上段のクリック対象まで移動してもクリック対象が chrome画面上部に隠れてしまっていてクリックできなかった
        - ページ最上の位置までスクロールさせることで対処 [Scrolling to top of the page in Python using Selenium
](https://stackoverflow.com/questions/45576958/scrolling-to-top-of-the-page-in-python-using-selenium)
    - [Selenium: Element not clickable … Other Element Would Receive Click](https://stackoverflow.com/questions/35636155/selenium-element-not-clickable-other-element-would-receive-click)


```python
def wait_for_element(element):
    # クリック対象が画面に表示されているか確認，表示されていなければしばらく待つ関数
    if element.is_displayed():
            print(element.is_displayed())
            return
    else:
            time.sleep(.1)
            return wait_for_element(element)

def toggle_all(browser):
　　# 全てのShow details ボタンをクリックし，abstract を表示させる関数
　　
　　# ページ最上部へ移動
    browser.find_element_by_tag_name('body').send_keys(Keys.CONTROL + Keys.HOME)
    time.sleep(3)

　　# 全ての Show details ボタンを取得 (note-contents-toggle 属性を持つオブジェクトが show details ボタンに対応することは 別途ソースから確認）
    buttons = browser.find_elements_by_class_name('note-contents-toggle')

　　# 全てのボタンをクリック
    for button in buttons:
　　　　# ボタンの位置まで画面を移動させる
        webdriver.ActionChains(browser).move_to_element(button).perform()
　　　　
　　　　# ボタンが画面に表示されていることを確認
        wait_for_element(button)
        print('---------------------')
        time.sleep(0.2)

　　　　# ボタンをクリック
        button.click()

def toggle_all(browser):
　　# 全てのShow details ボタンをクリックし，abstract を表示させる関数
　　
　　# ページ最上部へ移動
    browser.find_element_by_tag_name('body').send_keys(Keys.CONTROL + Keys.HOME)
    time.sleep(3)

　　# 全ての Show details ボタンを取得 (note-contents-toggle 属性を持つオブジェクトが show details ボタンに対応することは 別途ソースから確認）
    buttons = browser.find_elements_by_class_name('note-contents-toggle')

　　# 全てのボタンをクリック    
for i, button in enumerate(buttons):
　　　　# ボタンの位置まで画面を移動させる
        webdriver.ActionChains(browser).move_to_element(button).perform()
　　　　
　　　　# ボタンが画面に表示されていることを確認
        wait_for_element(button)
        print(f'toggle {i}')
        time.sleep(0.2)

        # ボタンをクリック
        button.click()

    return 
```
## ソースまとめ
上記のコードまとめたソース

```python
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

CHROME_PATH = 'path/to/chromedriver'

def wait_for_element(element):
    if element.is_displayed():
            print(element.is_displayed())
            return
    else:
            time.sleep(.1)
            return wait_for_element(element)

def toggle_all(browser):
    browser.find_element_by_tag_name('body').send_keys(Keys.CONTROL + Keys.HOME)
    time.sleep(3)
    buttons = browser.find_elements_by_class_name('note-contents-toggle')
    for i, button in enumerate(buttons):
        webdriver.ActionChains(browser).move_to_element(button).perform()
        wait_for_element(button)
        print(f'toggle {i}')
        time.sleep(0.1)
        button.click()

    return 
    
def scroll_down(browser):
    prev_source = None
    for i in range(0, 1000):
        if i == 0:
            time.sleep(1)
            
        browser.execute_script('window.scrollBy(0,2000)', '')
        time.sleep(0.3)
        print (f'Scroll: {i}')
        
        if i % 10 ==0:
            if browser.page_source == prev_source:
                break
            prev_source = browser.page_source
    return 

def get_all_source(url):
    browser = webdriver.Chrome(CHROME_PATH)
    browser.get(url)
    
    scroll_down(browser)
    toggle_all(browser)
    source = browser.page_source
    browser.close()
    time.sleep(0.3)    
    
    return source
```

## 参考
- [PythonでSeleniumを使ってスクレイピング (基礎)](https://qiita.com/kinpira/items/383b0fbee6bf229ea03d)
- [Selenium: How can I make WebDriver ignore “Element is not visible” error?](https://stackoverflow.com/questions/7340680/selenium-how-can-i-make-webdriver-ignore-element-is-not-visible-error)
- [PythonとBeautiful Soupでスクレイピング](https://qiita.com/itkr/items/513318a9b5b92bd56185)