ウェブスクレイピングとは、Webサイトから欲しい情報を取り出すことです。
注意事項
Webサイトによってはロボットによる巡回、つまりスクレイピングを禁止している場合があります。まずは /robots.txt を参照してスクレイピングが禁止されていないことを確認しましょう。
たとえば日本経済新聞のサイト https://www.nikkei.com/robots.txt ではこのような指定があります。
User-agent: * Disallow: /search/site/
ということで、/search/site/ 以下をウェブスクレイピングしないようにしましょう。
(2018/11/28追記) スクレイピングはAkamaiに低評価をつけられる可能性があります。 こちらにも書きましたが、何をしたらどれくらい低評価をつけるのかは教えてくれません。複数サイトに対してスクレイピングをするのは問題ありです。今回のスクレイピングで5日間ペナルティくらいました。 スクレイピングする前に、nslookupしてakamaiの場合はスクレイピングしない方が安全です。
必要なソフトウェアたち
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()