seleniumでウェブスクレイピング

ウェブスクレイピングとは、Webサイトから欲しい情報を取り出すことです。

注意事項

Webサイトによってはロボットによる巡回、つまりスクレイピングを禁止している場合があります。まずは /robots.txt を参照してスクレイピングが禁止されていないことを確認しましょう。

たとえば日本経済新聞のサイト https://www.nikkei.com/robots.txt ではこのような指定があります。

User-agent: *
Disallow: /search/site/

ということで、/search/site/ 以下をウェブスクレイピングしないようにしましょう。

(2018/11/28追記) スクレイピングAkamaiに低評価をつけられる可能性があります。 こちらにも書きましたが、何をしたらどれくらい低評価をつけるのかは教えてくれません。複数サイトに対してスクレイピングをするのは問題ありです。今回のスクレイピングで5日間ペナルティくらいました。 スクレイピングする前に、nslookupしてakamaiの場合はスクレイピングしない方が安全です。

tomo3i.hatenablog.com

必要なソフトウェアたち

OSX 10.14上で python3, selenium, chromedriver を想定しています。

python3は標準では入っていないので、Homebrewでインストールしましょう。

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

表示に従ってHomebrewをインストールしたら

brew install python3
brew cask install chromedriver
pip3 install selenium

日経からドル円を取り出す

日経のサイトは一番上にドル円が買いてあります。1ドル=112.96-97円というやつ。この部分をどうやって指定するかですが、次の方法が良いでしょう。 1 普通にChromeで実際にアクセスします 2 112.96-97と買いてあるところで右クリックして検証を選びます 3 右側に表示された検証ウィンドウから112.96-97と買いてあるところを探し、右クリックして Copy - Copy selector を選びます

この手順により、このようなCSSセレクタが取得できたと思います。

#js-ticker_list > ul > li > div:nth-child(3) > a > span.m-miH01C_rate

あとはこのCSSセレクタを使用して、seleniumとchromedriverでこんな風にすれば、112.96-97という文字列を取り出すことができます。

from selenium import webdriver

url = "https://www.nikkei.com/"
selector = "#js-ticker_list > ul > li > div:nth-child(3) > a > span.m-miH01C_rate"

driver = webdriver.Chrome()
driver.get(url)

elements = driver.find_elements_by_css_selector(selector)
for e in elements:
    print (e.text)
driver.close()

たとえば、nikkei.py という名前で保存したとすると、

python3 nikkei.py

と実行します。すると実際にChromeが立ち上がって、読み込みが終わるまでしばらく待つと、ターミナルには112.96-97 と出力されます。

実際にChromeをたちあげたく無い場合はheadlessモード

headlessモードを使用すればChromeが見えなくなります。見えなくなるだけで実際には起動しています。JavaScirptの実行が終わらないと値が取得できないとか、今どんな状況かわからないとか、そういうよくわからない状況があるので、最初は実際にChromeが開いて読み込みが進んでいる様子と、ターミナルへの出力を一緒に眺めた方が動作確認しやすいと思います。

headlessモードを有効にした場合は、このような記述になります。selenium.webdriver.chrome.optionsを使うのがポイントです。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options

url = "https://www.nikkei.com/"
selector = "#js-ticker_list > ul > li > div:nth-child(3) > a > span.m-miH01C_rate"

options = Options()
options.headless = True

driver = webdriver.Chrome(options=options)
driver.get(url)

elements = driver.find_elements_by_css_selector(selector)
for e in elements:
    print (e.text)
driver.close()

epic passの価格を取り出す

本当にやりたかったのはこれでした。CSSセレクタを取り出すところまでは日経のときと同じですが、同様にやると問題が起きました。何も表示されないままスクリプトの実行が終わってしまいます。headlessモードを無効にしていると、まだぜんぜんページの表示が終わっていないのにスクリプトが終わっていることがわかりました。

時間を指定して適当に待つ

なんだかよくわからない場合、10秒ぐらい待てばなんとかなるだろうという考えのもと、time.sleepで待つのも良いと思います。とりあえず待てばなんとかなります。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time

url = "https://www.epicpass.com/passes/epic-4-day.aspx"
selector = "#c27_Product_Detail_0 > div.col-xs-3.hidden-xs.pass_category_detail__price_col > span"

options = Options()
options.headless = True

driver = webdriver.Chrome(options=options)

driver.get(url)
time.sleep(10)

elements = driver.find_elements_by_css_selector(selector)
for e in elements:
    print (e.text)
driver.close()

WebDriverWaitで待つ

指定した要素が表示されるまで待つ、ということもできます。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By

url = "https://www.epicpass.com/passes/epic-4-day.aspx"
selector = "#c27_Product_Detail_0 > div.col-xs-3.hidden-xs.pass_category_detail__price_col > span"

options = Options()
options.headless = True

driver = webdriver.Chrome(options=options)
driver.get(url)
wait = WebDriverWait(driver, 10)
e = wait.until(expected_conditions.visibility_of_element_located((By.CSS_SELECTOR, selector)))
print (e.text)
driver.close()

正規表現で欲しい部分だけ取り出す

上記の方法で結果を取得したとき、目的の文字列が取り出せる場合とそうでない場合があります。例えば、価格をとりだしたかったのに取得した文字列がこんな風になっている場合があります。

$100 is the latest price! Buy now!

こういう場合は正規表現で欲しい部分だけ取り出すことができます。

import re
s = "$100 is the latest price! Buy now!"
r = "[0-9]+"
re.search(r, s).group()

本当は、"¥$[0-9]+" でマッチさせようと思ったんですが、なぜかマッチしませんでした。検索してもよくわからず。¥$ で $ をエスケープできているんじゃないんだろうか。わかる方教えてください。

というわけで、全ソースコードを貼っておきます。が、どうやらこれを実行するとAkamaiからマイナス評価を受けるみたいです。実行しないことをおすすめします。適当に待ち時間を入れればいいのかな。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.by import By
import re

url_epic4 = "https://www.epicpass.com/passes/epic-4-day.aspx"
url_epic7 = "https://www.epicpass.com/passes/epic-7-day.aspx"
url_epic_tahoe_local = "https://www.epicpass.com/passes/tahoe-local-pass.aspx"
url_epic_tahoe_value = "https://www.epicpass.com/passes/tahoe-value-pass.aspx"
url_epic = "https://www.epicpass.com/passes/epic-pass.aspx"

url_tahoe = "https://squawalpine.com/tickets-passes/lift-tickets/tahoe-super-4-lift-ticket-pack"

url_sugar_unristriced = "https://estore.sugarbowl.com/eStore/Content/Commerce/Products/DisplayProducts.aspx?ProductGroupCode=1101&AspxAutoDetectCookieSupport=1"
url_sugar_slightly = "https://estore.sugarbowl.com/eStore/Content/Commerce/Products/DisplayProducts.aspx?ProductGroupCode=1101&ProductCategoryCode=8023"

url_ikon = "https://www.ikonpass.com/en/ikon-pass"
url_ikon_base = "https://www.ikonpass.com/en/ikon-base-pass"


def epic(url):
    selector = "#c27_Product_Detail_0 > div.col-xs-3.hidden-xs.pass_category_detail__price_col > span"
    
    options = Options()
    options.headless = True
    
    driver = webdriver.Chrome(options=options)
    driver.get(url)
    wait = WebDriverWait(driver, 10)
    e = wait.until(expected_conditions.visibility_of_element_located((By.CSS_SELECTOR, selector)))
    result = e.text
    driver.close()
    return result

def epic4():
    print ("epic4: " + epic(url_epic4))

def epic7():
    print ("epic7: " + epic(url_epic7))

def epic_tahoe_local():
    print ("epic tahoe local: " + epic(url_epic_tahoe_local))

def epic_tahoe_value():
    print ("epic tahoe value: " + epic(url_epic_tahoe_value))

def epic_epic():
    print ("epic: " + epic(url_epic))

def tahoe4(url):
    selector = "#content > div > article > div.field.field-name-body.field-type-text-with-summary.field-label-hidden > div > div > div > div > table > tbody > tr:nth-child(1) > td:nth-child(2)"

    options = Options()
    options.headless = True
    
    driver = webdriver.Chrome(options=options)
    driver.get(url)
    wait = WebDriverWait(driver, 10)
    e = wait.until(expected_conditions.visibility_of_element_located((By.CSS_SELECTOR, selector)))
    pattern = "[0-9]+"
    print ("tahoe super4: $" + re.search(pattern, e.text).group() )

    driver.close()


def sugar(url):
    selector = "#ctl00_ctl00_ctl00_ctl00_ContentPlaceHolder1_ContentPlaceHolder1_contentMain_commerceMain_ProductSelectControl_ProductSelectList_gvProducts_ctl02_lblPrice"
    options = Options()
    options.headless = True
    
    driver = webdriver.Chrome(options=options)
    driver.get(url)
    wait = WebDriverWait(driver, 10)
    e = wait.until(expected_conditions.visibility_of_element_located((By.CSS_SELECTOR, selector)))
    result = e.text
    driver.close()
    return result

def sugar_unristricted():
    print ("Sugar unristricted: " + sugar(url_sugar_unristriced))

def sugar_slightly():
    print ("Sugar slightly: " + sugar(url_sugar_slightly))

def ikon(url):
    selector = "#app-wrapper > main > div > div.flex-container > div:nth-child(2) > div > div > div:nth-child(2) > div > div.sidebar-wrapper > div:nth-child(3) > div > div > div:nth-child(2) > div.price > h3"
    options = Options()
    options.headless = True
    
    driver = webdriver.Chrome(options=options)
    driver.get(url)
    wait = WebDriverWait(driver, 10)
    e = wait.until(expected_conditions.visibility_of_element_located((By.CSS_SELECTOR, selector)))
    result = e.text
    driver.close()
    return result

def ikon_ikon():
    print("Ikon: " + ikon(url_ikon))

def ikon_base():
    print("Ikon base: " + ikon(url_ikon_base))



epic4()
#epic7()
#epic_tahoe_local()
#epic_tahoe_value()
#epic_epic()

#tahoe4(url_tahoe)
#sugar_unristricted()
#sugar_slightly()
#ikon_ikon()
#ikon_base()