Pythonでディズニーチケットを自動取得【スクレイピング】

趣味で、チケットの販売を監視するスクリプトを作成してみました。
ツイッターで少しだけバズったので、ソースコードを公開します!


最近スクレイピングを始めた、スクレイピング素人が書くソースコードということを許してください。アドバイス・訂正・ご意見などは本記事の最後のコメントへ記入をお願いします。

【今回実装する機能】

1.ディズニーチケットの再販を監視

2.販売されれば自分のアカウントへのLINE通知

3.チケット取得画面へ遷移

4.自動でチケット取得

ディズニーへと迷惑がかからないように、留意事項をまとめましたので一読お願いします。(ディズニーから訴えられたくないので…)

本記事の留意事項
◆本記事を読むに当たり「LINEのAPIシステム」「Pythonスクレイピング」「Selenium」等の理解ができている事が必要になり、Pythonの実行環境が必要です。
◆今回のソースコードを闇雲にエンコードしてしまっては、ディズニーのサーバーに迷惑がかかってしまいDos攻撃等にもなりかねないため、Pythonの理解ある方へ向けて記事をまとめました。
◆プログラミングを知らない方には向かない記事であることをお許しください。また、悪用をする方もご遠慮ください。

インポート

まずはインポート。
seleniumが必要になりますので、pipなどでインストールしておきましょう。
中には必要のないものもインポートしていますが、本ソースコードを更に拡張する際にはあったら手助けになるモジュールも入れています。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.alert import Alert
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.action_chains import ActionChains
import time
import requests
import sys
import datetime
import signal
import os

seleniumの起動

次に、まずはseleniumの起動です。
driver_pathが間違えると動かないので、相対パス・絶対パスのいずれかを間違えないように指定しましょう。

# Seleniumを起動
from urllib3.util import wait
options = Options()
options.add_argument('--disable-gpu')
options.add_argument('--disable-extensions')
options.add_argument('--proxy-server="direct://"')
options.add_argument('--proxy-bypass-list=*')
options.add_argument('--start-maximized')

driber_path = "driver/chromedriver"
driver = webdriver.Chrome(executable_path=driber_path)

次は、LINEへ通知を送る関数を定義しておきます。
今回はトークンを2つ入手して、2つの関数を定義しました。
1つでもいいのですが、しっかり動作しているか確認するために2つ入手します。
>>トークンの取得はこちらから(LINEのサイトへ遷移します)

この関数を実行すると、LINEへは下のようなメッセージが届きます。

#ラインへ通知を送る関数
def line_ntfy(mess):
    url = "https://notify-api.line.me/api/notify"
    token = '<ここに取得したトークンを記入>'
    headers = {"Authorization": "Bearer " + token}
    payload = {"message": str(mess)}
    requests.post(url, headers=headers, params=payload)
def line_ntfy_kakuninn(num):
    url = "https://notify-api.line.me/api/notify"
    token = '<ここに取得したトークンを記入>'
    headers = {"Authorization": "Bearer " + token}
    payload = {"message":'調査回数  '+str(num)}
    requests.post(url, headers=headers, params=payload)

販売中の判定関数

もうひとつ関数を定義しておきます。
この関数が実行されると下の画像のようなメッセージが届きます。
チケットを確実に取得するために、上手く動作しなかった場合に報告する関数です。

#販売中の場合の判定&クリック関数.販売中であればNone,販売中でなければ「現在、販売していません」の要素を返す
def PssportJudgeAndCilck(day, when, notbuy_xpath, click_xpath):
    if len(driver.find_elements_by_xpath(notbuy_xpath)) > 0:
        NotBuyElement = driver.find_element_by_xpath(notbuy_xpath)
        if NotBuyElement.text != '現在、販売していません':
            line_ntfy('\n【緊急事態】\n4月'+day+'日'+when+'のチケット再販開始\n急いでとってください\n\
            https://reserve.tokyodisneyresort.jp/')
            driver.find_element_by_xpath(click_xpath).click()
            return None
        else:
            return NotBuyElement
    else:
        line_ntfy('\n【緊急事態】\n4月'+day+'日'+when+'のチケット再販開始\n急いでとってください\n\
        https://reserve.tokyodisneyresort.jp/')
        driver.find_element_by_xpath(click_xpath).click()
        return None

何気に、非常に重要なコードが下のコードです。
Dos攻撃にならないようにseleniumをしっかりと待機させることは大切です。
動作によって使い分けるために2種類の待機関数を定義しておきます。

#しっかり待機させる関数
def wait():
    driver.implicitly_wait(20)
    WebDriverWait(driver, 2000).until(EC.invisibility_of_element_located((By.ID, 'loading_modal0overlay')))
    time.sleep(5)

#軽く待機させる関数
def waitShort():
    driver.implicitly_wait(20)
    WebDriverWait(driver, 2000).until(EC.invisibility_of_element_located((By.ID, 'loading_modal0overlay')))
    time.sleep(3)

ディズニーアカウントへの自動ログイン

ここからが本題です。
seleniumを起動させ、ログイン処理を進めます。
ディズニーのアカウントへログインするためのアカウント名とパスワードを入力させます。

#起動
driver.get("https://reserve.tokyodisneyresort.jp/")
wait()

#ログインクリック
driver.find_element_by_xpath('//*[@id="header"]/div/ul[2]/li/a/img').click()
wait()

#ログイン
LoginId = driver.find_element_by_xpath('//*[@id="_userId"]')
LoginId.send_keys("<自分のディズニーログインアカウント名>")
password = driver.find_element_by_xpath('//*[@id="_password"]')
password.send_keys("<自分のディズニーログインパスワード>")
driver.find_element_by_xpath('//*[@id="_loginConection"]/form/p/a/img').click()
wait()

#トラベルバッグクリック
driver.find_element_by_xpath('//*[@id="header"]/div/ul[3]/li[2]/a/img').click()
wait()

#チケットを追加するクリック
driver.find_element_by_xpath('//*[@id="dayTable"]/tbody/tr[1]/td/table/tbody/tr/td[2]/a').click()
wait()

# >クリック
driver.find_element_by_xpath('//*[@id="searchCalendar"]/div/div/ul/button[2]').click()
wait()

チケット取得

ここから長いコードになりますが、ディズニーのチケット画面へ進んでいく作業を繰り返します。
while文でひたすら繰り返しますが、サーバーの負荷がかからないように待機関数をここでもしっかりと利用します。
今回は過去の4月1日と4月2日の設定でコーディングをしています。
また、HTML要素などは変わっている可能性があります。

#調査回数をカウントする変数
RandNum1day = 1
SeaNum2day = 1

#繰り返し4月1日と4月2日をチェックする。販売中であればbreakしてその画面のままにする
while True:
    #4月1日
    wait()

    # 日付のクリック(ホバーしてクリック、1秒の待機)
    ActionHover = ActionChains(driver)
    ActionHover.move_to_element(driver.find_element_by_xpath(\
    '//*[@id="searchCalendar"]/div/div/ul/div/div/li[2]/div/table/tbody/tr[1]/td[4]/a')).perform()
    driver.find_element_by_xpath(\
    '//*[@id="searchCalendar"]/div/div/ul/div/div/li[2]/div/table/tbody/tr[1]/td[4]/a').click()
    waitShort()

    #「自宅でプリントアウト」のクリック
    driver.find_element_by_xpath('//*[@id="searchEticket"]').click()
    wait()

    #青四角クリックができればクリック、できなければ次のアクションへ
    Rand1dayElements = PssportJudgeAndCilck(1, '1day', '//*[@id="searchResultList"]/ul/li[1]/div/p[3]',\
    '//*[@id="searchResultList"]/ul/li[1]/div')
    Rand1030toElements = PssportJudgeAndCilck(1, '10:30-', '//*[@id="searchResultList"]/ul/li[2]/div/p[3]',\
    '//*[@id="searchResultList"]/ul/li[2]/div')
    RandNoontoElements = PssportJudgeAndCilck(1, '昼-', '//*[@id="searchResultList"]/ul/li[3]/div/p[3]', \
    '//*[@id="searchResultList"]/ul/li[3]/div')
    if Rand1dayElements==None or Rand1030toElements==None or RandNoontoElements==None:
        break

    #確認用出力
    RandNum1day += 1
    print('4月1日チケット売り切れ中  :', RandNum1day, "周目", datetime.datetime.now())
    print(Rand1dayElements.text, Rand1030toElements.text, RandNoontoElements.text)


    #4月2日
    wait()

    # 日付のクリック
    ActionHover = ActionChains(driver)
    ActionHover.move_to_element(driver.find_element_by_xpath(\
    '//*[@id="searchCalendar"]/div/div/ul/div/div/li[2]/div/table/tbody/tr[1]/td[5]/a')).perform()
    driver.find_element_by_xpath(\
    '//*[@id="searchCalendar"]/div/div/ul/div/div/li[2]/div/table/tbody/tr[1]/td[5]/a').click()
    waitShort()

    #「自宅でプリントアウト」のクリック
    driver.find_element_by_xpath('//*[@id="searchEticket"]').click()
    wait()

    # もしクリックができればクリックとライン通知
    Sea1dayElements = PssportJudgeAndCilck(2, '1day', '//*[@id="searchResultList"]/ul/li[1]/div/p[3]', \
    '//*[@id="searchResultList"]/ul/li[1]/div')
    Sea1030toElements = PssportJudgeAndCilck(2, '10:30-', '//*[@id="searchResultList"]/ul/li[2]/div/p[3]', \
    '//*[@id="searchResultList"]/ul/li[2]/div')
    SeaNoontoElements = PssportJudgeAndCilck(2, '昼-', '//*[@id="searchResultList"]/ul/li[3]/div/p[3]', \
    '//*[@id="searchResultList"]/ul/li[3]/div')
    if Sea1dayElements == None or Sea1030toElements == None or SeaNoontoElements == None:
        break

    # 確認用出力
    SeaNum2day += 1
    print('4月2日チケット売り切れ中  :', SeaNum2day, "周目", datetime.datetime.now())
    print(Sea1dayElements.text, Sea1030toElements.text, SeaNoontoElements.text)

    # 確認用出力総合
    if(SeaNum2day%5 == 0):
        line_ntfy_kakuninn(SeaNum2day*2)

正確にうまく動作はせずたまにエラーは残りますが、ある程度は動くことは確認できました。
しかし、この先の画面に進んで、自動で取得画面へ遷移して名前などの入力作業は宿題ということで今回のコードはここまでにしておきます。

スクレイピングは不慣れなもので、もっと良い書き方もあるのかとは思いますがスクレイプングは面白いものですね。
半分、宿題とした続きのコードが思いつかなくて挫折してしまったわけでもあります。コードのアドバイスなんかがあればコメントくださいね。

ということで本記事は、
Pythonでディズニーチケットを自動で取得する方法をまとめました!
最後まで読んでいただきありがとうございました(^^)

あなたにオススメの本

【独習Python】
高評価レビュー多数の、Pythonを完璧にマスターできる参考書です。
この1冊でPythonのほとんどが完結する、間違いのない本ですよ\(^o^)/

つまづいたときに逆引きするPythonの辞書代わりにもなります(^^)


【Pythonクローリング&スクレイピング】
スクローリングとスクレイピングを学ぼうと思ったら、この1冊でカンペキです。
僕自身も読んでいるおすすめ本です\(^o^)/

5 COMMENTS

栗花落カナヲ

とても参考になるブログありがとうございます!
Pythonはかじった程度、seleniumへはじめて挑戦しますが、
いい題材とヒントを示していただけて非常に勉強になります!

1つ質問させてください。

PssportJudgeAndCilck関数の1番初めにif-elseがありますが、このelseはどんな事象を想定されているのでしょうか。

len(driver.find_elements_by_xpath(notbuy_xpath)) > 0:は、notbuy_xpathで示されるものがあるかないかを見ている(0以上なら要素あり)と思われますが、
Elseの時(=「”現在販売しておりません”」や「””」の文字列の入れ物すらないとき)でも、チケットが売っていると通知しているように見受けられます。

私は、ロードが終わってない時にjudgeに進んでしまった時の分岐に苦労しているのですが、このif-elseはそれに関係しているとでしょうか。

アメリカ応援しています!

返信する
前田航汰

栗花落カナヲさん
お返事が遅くなってしまって本当にすみません!
遅くなってしまったお返事ですので、解決済みかも知れませんが回答致します。

def PssportJudgeAndCilck
if 要素があったら(要素の内容によって通知をするかを決定する)
  if 要素が「現在、販売していません」でなければ
    販売している可能性が高いため通知
  else
    販売していないため処理をスルー
else 要素が見つけらなかったら(ディズニーのサイト側でHTMLのidなどの要素を変更された可能性・販売している間のみHTML要素が変更される仕様の可能性)
  いちよう販売している可能性が高いため通知

栗花落カナヲさんのご指摘どおりでPssportJudgeAndCilckの関数部分は苦戦しました。
おそらく、このコードが最善ではないと思います。
私もseleniumに関しては素人ながら挑戦したコードですので、本当に自信がありません。。。
返信が大変遅くなってしまって本当に申し訳ありませんでした。
栗花落カナヲさんの思い通りのコードが完成することを願っています!
また質問があれば教えて下さいね。

返信する
taka

>この先の画面に進んで、自動で取得画面へ遷移して名前などの入力作業は宿題という>ことで今回のコードはここまでにしておきます。
こちらの部分のコードを載せていただくことは可能でしょうか?

返信する
前田航汰

takaさん
コメントありがとうございます!!
悪用されるという面・私の技術不足の面、両方の面から宿題部分のコードは控えさせていただきます。
暖かいコメントを頂いたにもかかわらず、このような対応になってしまい大変申し訳ありません。。。
本記事のソースコードに関して分からない等のご質問等あれば下さいね。

返信する
コノミ

記事を読みました。
JRAのキャンセル席を取りたいのですが、この記事のように作れますか?

返信する

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です