mcpField
T
ticketsatwork

ticketsatwork

github.com/borskiその他78068 回インストール2 日前に更新
リポジトリ

ticketsatwork について

ticketsatworkは、TicketsAtWork会員向けにホテルや観光チケットの情報を取得するコマンドラインスキルです。Patchrightでボット対策のあるサイトにアクセスし、構造化データを返します。レンタカー検索は実験的です。

主な機能

  1. 01都市と日付を指定したホテル検索
  2. 02合計料金と表示割引の比較
  3. 03カテゴリまたはキーワードでのチケット閲覧
  4. 04JSON形式での結果出力
  5. 05実験的なレンタカー検索

活用シーン

  1. 01旅行先のホテル料金比較
  2. 02別の検索で見つけたホテルの確認
  3. 03テーマパークのチケット特典探し

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/borski/travel-hacking-toolkit --skill ticketsatwork

インストール後、Claude Code で/ticketsatworkを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

Dockerfile
scripts/
browse_tickets.py
entrypoint.sh
search_cars.py
search_hotels.py
taw_common.py

詳細

TicketsAtWork

TicketSatWork.com から在庫情報をスクレイピングします。TicketSatWorkは、Entertainment Benefits Group(EBG)が運営する、企業向け従業員特典プラットフォームです。会員は、勤務先のアカウントを通じて、契約料金を利用できます。

公開 API はありません。 TaW は requests、標準の Playwright、ヘッドレス Chrome をブロックするボット対策の背後にあるため、Patchright(検知回避機能を備えた Playwright のフォーク)が必要です。

旅行をお得にするうえで TaW が重要な理由

TaW は、EBG の契約済み卸売在庫プールから在庫を集約しています(Costco Travel、AAA などと同様の在庫フィードです)。同じ宿泊施設・日程では、TaW の料金が次のサービスを上回ることがよくあります。

  • ホテルの公式予約
  • Booking.com/Hotels.com/Expedia
  • Chase Travel(UR ポータル)と Amex Travel(MR ポータル)
  • 該当するプログラムに含まれていない宿泊施設では、Amex FHR/THC や Chase Edit よりもお得な場合もあります

割引バッジは通常、実際の割引を示しています。「$X の節約」の金額は、検索時点でホテルが公開している料金を基準に算出されます。

トレードオフ: ポータル利用特典、カードプログラムのエリート特典、宿泊時のポイント獲得はありません。TaW の「Funlife」ロイヤルティポイントを獲得できますが、今後の TaW 予約にのみ、約 1 cpp 相当で利用できます。

機能

サブコマンド内容ステータス
hotels都市と日程を指定してホテルを検索します。価格、通常料金との差額、緯度・経度、評価、距離とともに、約 10~200 件以上の宿泊施設を返します安定
cars受け取り場所と日時を指定してレンタカーを検索します実験的。 TaW はオートコンプリートの緯度・経度を解決しますが、レンタカーのバックエンドが必要とする空港コードを解決できない場合があります。有効に見える検索でも、主要空港で「この場所に車がありません」と返されることがあります。詳しくは、以下の「既知の制限」を参照してください。
ticketsカテゴリのランディングページ(例:disneyland、wdw、usf)を閲覧するか、Reflektion でキーワード検索します安定。お得情報のタイトル、割引表示、説明、画像、詳細 URL を返します。一覧には価格が表示されず、詳細ページに表示されます。

前提条件

  • ローカルで Docker が実行されていること(公開イメージを使用する場合)、または Patchright がローカルにインストールされていること(pip install patchright && patchright install chromium)
  • TaW アカウント(勤務先の TaW URL または法人コードから作成)
  • TaW のメールアドレスとパスワードが、環境変数 TAW_USER と TAW_PASS に設定されていること

認証

メールアドレスとパスワードによるフォーム認証です。このスキルは、JavaScript から直接ログインフォームを送信します。スクリプトを実行するたびに新しいブラウザープロファイルを使用するため、毎回ログインします。

画面に表示される「サインイン/登録」UI ではモーダルの操作が必要ですが、Patchright では安定して処理できません(Cookie バナーとの重なり、モーダルを開くボタンの表示状態の問題など)。ログインフォーム(#member_login_form)は常に DOM 内にあるため、JavaScript で値を設定し、フォームの送信ボタンをクリックします。

Docker の使用方法

docker pull ghcr.io/borski/ticketsatwork:latest

# Hotels
docker run --rm -e TAW_USER -e TAW_PASS \
  ghcr.io/borski/ticketsatwork hotels \
  --city "Carlsbad, CA" --checkin 2027-03-04 --checkout 2027-03-07 \
  --rooms 1 --adults 2 --json

# Tickets / attractions (category)
docker run --rm -e TAW_USER -e TAW_PASS \
  ghcr.io/borski/ticketsatwork tickets \
  --category disneyland --json

# Tickets / attractions (keyword)
docker run --rm -e TAW_USER -e TAW_PASS \
  ghcr.io/borski/ticketsatwork tickets \
  --keyword "universal studios" --json

# Live events in destination cities (concerts, theater, sports)
docker run --rm -e TAW_USER -e TAW_PASS \
  ghcr.io/borski/ticketsatwork tickets \
  --section events --json

# Rental cars (experimental)
docker run --rm -e TAW_USER -e TAW_PASS \
  ghcr.io/borski/ticketsatwork cars \
  --pickup "San Diego Airport, CA" \
  --pickup-date 2026-06-15 --pickup-time 12:00 \
  --dropoff-date 2026-06-18 --dropoff-time 12:00 \
  --age 30 --json

ローカルでの使用方法(Chrome ウィンドウが短時間開きます)

# Hotels
[email protected] TAW_PASS=yourpass \
  python3 scripts/search_hotels.py --city "Carlsbad, CA" \
  --checkin 2027-03-04 --checkout 2027-03-07 --json

# Tickets
[email protected] TAW_PASS=yourpass \
  python3 scripts/browse_tickets.py --category disneyland --json

# Cars
[email protected] TAW_PASS=yourpass \
  python3 scripts/search_cars.py \
  --pickup "San Diego Airport, CA" \
  --pickup-date 2026-06-15 --pickup-time 12:00 \
  --dropoff-date 2026-06-18 --dropoff-time 12:00 --json

ホテル:引数と出力

引数
フラグ必須デフォルト説明
--cityはい都市または住所です。目的地のオートコンプリートに入力されます。
--checkinはいチェックイン日を YYYY-MM-DD で指定します。
--checkoutはいチェックアウト日を YYYY-MM-DD で指定します。
--roomsいいえ1部屋数です。
--adultsいいえ21 部屋あたりの大人の人数です。
--childrenいいえ01 部屋あたりの子どもの人数です。
--jsonいいえオフ人が読みやすい表の代わりに、構造化 JSON を出力します。
--debugいいえオフ各ステップのスクリーンショットと HTML を /output(Docker)または /tmp/taw_debug/(ローカル)に保存します。
--raw-html-outいいえオフライン分析用に、結果の生 HTML をファイルに書き込みます。
出力スキーマ(JSON モード)
{
  "city": "Carlsbad, CA",
  "checkin": "2027-03-04",
  "checkout": "2027-03-07",
  "rooms": 1,
  "adults": 2,
  "children": 0,
  "display_pages": 14,
  "listing_count": 134,
  "listings": [
    {
      "id": "hotel_NNNNNN_x",
      "name": "Property Display Name",
      "name_normalized": "property display name",
      "rating": 4.5,
      "guest_rating": 9.0,
      "price_per_night_usd": 184,
      "total_price_usd": 746,
      "strike_price_usd": 1122,
      "savings_usd": 375.68,
      "loyalty_points": 1492,
      "distance_miles": 0.89,
      "distance_label": "0.89 miles from the center of Carlsbad",
      "property_type": "hotel",
      "lat": 33.121,
      "lng": -117.310,
      "tripadvisor": 0.0,
      "room_id": "...",
      "rate_code": "...",
      "featured": false,
      "discount_ranking": 717.95,
      "detail_url": "https://www.ticketsatwork.com/tickets/hotels.php?sub=details&id=NNNNNN_x"
    }
  ]
}

フィールドの説明:

  • rating = 施設の星評価(0.0 - 5.0)。
  • guest_rating = 宿泊後のレビュー平均評価(0.0 - 10.0)。
  • total_price_usd = 宿泊の総額(税金・手数料込み。UI に「すべての手数料込み」と表示されます)。
  • strike_price_usd = 公開されている比較料金(直接予約、または割引なしのチャネルで支払う料金)。
  • savings_usd = バッジに表示される strike_price_usd - total_price_usd。
  • loyalty_points = この予約で獲得できる TaW Funlife ポイント。
  • discount_ranking = TaW 独自の割引額スコア(数値が大きいほど、比較料金に対する割引額が大きくなります)。
ページネーションの動作

TaW は一致するすべてのホテルを初期 DOM に表示し、simple-pagination(jQuery)を使って画面上で分割します。追加で取得するリスティングはないため、1 回の解析で結果セット全体を処理できます。display_pages フィールドは TaW がユーザーに表示するページ数を示すもので、スクリプトが処理するページ数ではありません。

チケット:引数と出力

引数

--category または --keyword のいずれか一方だけを指定してください。

フラグ説明
--category SLUGdisneyland、wdw、usf、seaworld などのカテゴリスラッグです。以下のカテゴリ一覧を参照してください。
--keyword TEXTフリーテキストのキーワードです。Reflektion のサイト内検索を使用します。Enter キーを押すと、TaW は最も関連性の高いカテゴリのランディングページに直接リダイレクトすることがよくあります。
--json構造化 JSON 出力です。
--debugスクリーンショットと HTML を保存します。
--raw-html-outランディングページの生 HTML をファイルに書き込みます。
主なカテゴリスラッグ

以下は固定のカテゴリランディングページです(/tickets/pages.php?sub=<slug>)。

スラッグページ
wdwWalt Disney World
disneylandDisneyland Resort(カリフォルニア)
disneyland-parisDisneyland Paris
usfUniversal Orlando
ushUniversal Studios Hollywood
seaworldSeaWorld parks
busch-gardens-parksBusch Gardens parks
sesame-placeSesame Place parks
all-theme-parks-attractionsテーマパークとアトラクションの統合インデックス

その他のカテゴリについては、ブラウザーで /tickets/index.php にアクセスし、カテゴリを見つけて、URL から ?sub= のスラッグをコピーしてください。

出力スキーマ
{
  "category": "disneyland",
  "keyword": null,
  "landed_url": "https://www.ticketsatwork.com/tickets/pages.php?sub=disneyland",
  "deal_count": 6,
  "deals": [
    {
      "type": "ticket",
      "filter_entity_id": "4704",
      "title": "Disneyland Resort - California",
      "subtitle": "Save up to $65 on Multi-Day Tickets",
      "description": "Explore 1-5 Day Disneyland Resort theme park tickets...",
      "image_url": "https://www.ticketsatwork.com/common_resources/...",
      "detail_url": "https://www.ticketsatwork.com/tickets/packages.php?sub=packages&action=view&id=4704"
    }
  ]
}

type は ticket(TaW のパッケージ。料金は詳細ページに表示されます)または url(外部リンク。ホテルの詳細ページへのリンクが多く含まれます)です。

料金に関する注意: 多くのカテゴリページでは、リスティングに料金が表示されません。subtitle フィールドには割引の表示(「最大 $65 割引」「チケット 1 日 $48 から」)が含まれます。正確な料金を確認するには、detail_url に進む必要があります。

車:引数と既知の制限事項

引数
フラグ必須デフォルト説明
--pickupはい受け取り場所です。空港名が最適です。
--dropoffいいえ受け取り場所と同じ返却場所です。
--pickup-dateはいYYYY-MM-DD です。
--pickup-timeいいえ12:0024 時間表記の HH:MM です。時刻は :00 または :30 に限られます。
--dropoff-dateはいYYYY-MM-DD です。
--dropoff-timeいいえ12:00同じ形式です。
--ageいいえ30運転者の年齢です。TaW では 20~25 歳の場合は追加料金がかかり、25 歳以上は標準料金です。
既知の制限事項

TaW の車の検索結果ページでは、TaW の車両バックエンドへの XHR 呼び出しによって入力される Handlebars テンプレートが使用されています。オートコンプリートで解決された latlng があっても、バックエンドが必要とする空港コードが、オートコンプリートウィジェットのコールバックで入力されない場合があります。スクリプトは表示されている入力値を非表示の値フィールドに補完しますが、TaW の車両 API では、設定していない追加の pickup_code フィールドが必要になる場合があります。

症状:車の検索結果ページにスケルトンが表示された後、「選択した日時では、この場所で利用可能な車はありません」というメッセージが表示されます。直近の日付に主要空港を指定した場合でも同様です。

急いで車を探す場合の回避策: 通常のブラウザーで ticketsatwork.com を開き、手動で検索してオファーを確認してください。その後、スクリプトで価格比較データを取得したい場合は、このスキルで TaW のホテルとチケットの在庫状況を引き続きプログラムから確認できます。

ページに車のカードが表示されれば、スクリプトは正しく接続され、カードを解析できます。問題は、フォームに正しい受け取りコードのメタデータを渡せていないことです。PR を歓迎します。

使用する場面

  • 特定の目的地と日付について、ホテル料金を直販/ポータル/Booking と比較する場合
  • Trivago/Google Hotels でホテルを絞り込んだ後、選んだ施設を TaW でより安く利用できるか確認する場合
  • TaW が現在提供しているテーマパーク/アトラクションの割引を調べる場合(割引内容は季節ごとに変わります)
  • Chase Travel/Amex Travel/ホテルへの直接予約で「これが最安値」という主張を予約前に確認する場合

使用しない場面

  • ホテルが Amex FHR/THC に含まれており、ユーザーが施設クレジットと朝食を希望している場合(プログラムの特典を組み合わせると、通常は TaW の宿泊料金の節約額を上回ります)
  • ユーザーがホテルプログラムのエリートクレジットやポイントを希望している場合(TaW で予約しても Marriott Bonvoy、Hilton Honors などのクレジットは付与されません)
  • ユーザーが変更可能/返金可能な料金を必要としている場合(TaW の料金は返金不可の場合が多いため、施設の詳細ページを確認してください)
  • 料金よりもエリートステータスの特典が重要な予約

実装上の注意点(今後の保守担当者向け)

今後の作業に備えて記載しています。スクリプトではすでに対応済みです。

モーダルではなく JS でログイン

表示されている「サインイン/登録」ボタンを押すと、実際のログインフォームを含むモーダルが開きます。トリガーが Patchright から見えない場合があります(オーバーレイの z-index、OneTrust バナー)。フォーム自体は常に DOM に存在します。JS で値を直接設定して、送信ボタンをクリックします。

const form = document.querySelector('#member_login_form');
const emailEl = form.querySelector('input[name="login_email"]');
const passEl = form.querySelector('input[name="login_password"]');
// ...set values, dispatch input/change events, click submit
目的地のオートコンプリートは jQuery UI + ElasticSearch

目的地の入力欄(ホテルでは #place_name、車では #pickup_location)は、候補をクリックするまで装飾的なものにすぎません。オートコンプリートのコールバックによって、クリック時に非表示の lat/lng フィールドが入力されます。

セレクター(ホテル):

  • 候補のドロップダウン:.ui-autocomplete.ebg-autocomplete .ui-menu-item:visible
  • 緯度:#place_lat、経度:#place_lng

セレクター(車):

  • 同じドロップダウンのクラス
  • 表示フィールド:#pickup_location、#dropoff_location
  • 非表示の値:#origin_search_value、#destination_search_value
  • 非表示の latlng(結合済み):#origin_lat_lng、#destination_lat_lng

競合状態: 最初の入力では失敗することがよくあります。毎回フィールドをクリアしながら、最大 3 回再試行してください。各試行では、対象フィールドに値が入力されるまで最大 8 秒待ちます。

車に固有の注意点: latlng は安定して入力されますが、コールバックでは正式な場所の値フィールドが必ずしも入力されません。スクリプトは表示されている入力テキストから値を補完します。フォームの送信にはこれで十分ですが、車両バックエンドが空港コードを解決するには不十分な場合があります。

日付入力は readonly

#check_in、#check_out、#pick_up_date、#drop_off_date は readonly="readonly" で、jQuery datepicker によって制御されています。通常の入力欄のように fill() することはできません。基になる HTMLInputElement.value セッターを使用し、JS で .value を設定してください。

日付形式: MM/DD/YY(年は 2 桁)

ホテルの一覧はシェルの後に表示されます

結果ページのシェルが最初に読み込まれ、一覧は XHR 経由で表示されます。少なくとも 1 つの <li id="hotel_..." data-name="..."> が DOM に追加されるまで待ってください。これがないと、解析が早く実行され、一覧が 0 件になります。

ホテルの一覧は事前にレンダリングされます

結果一式は初期レスポンスで配信されます。ページネーションは simple-pagination による視覚的な表示にすぎません。ページリンクをクリックする必要はありません。初回のレンダリング時に 1 回解析すれば、すべて取得できます。

車の結果には後から Handlebars テンプレートが反映されます

車の結果には Handlebars 形式のテンプレートページが使われます。ページは {{var}} プレースホルダーを含んだ状態ですぐにレンダリングされ、その後、バックエンドの XHR によって実際のオファーが反映されます。解析する前に、.rc-totals の内容が {{total}} と一致しなくなるまで待ってください。

Cookie バナー

初回アクセス時に OneTrust バナーが表示され、クリックを妨げます。フローの早い段階で button#onetrust-accept-btn-handler または button:has-text('Accept') を使って閉じてください。

チケット/カテゴリページは静的 HTML です

ホテルや車とは異なり、カテゴリのランディングページでは、初期 HTML レスポンスにお得な商品一覧がレンダリングされます。XHR を待つ必要はありません。カードは <li class="grid-template-seen"> で、data-unq(URL)とインラインのタイトル/サブタイトル/説明が含まれます。

Docker イメージのビルド

cd skills/ticketsatwork
docker build -t ghcr.io/borski/ticketsatwork:latest .
docker push ghcr.io/borski/ticketsatwork:latest

ベースイメージ(ghcr.io/borski/patchright-docker:latest)は、southwest、chase-travel、amex-travel およびその他の Patchright ベースのスキルと共有されています。

プライバシー/安全性

  • このスキルを通じて予約しないでください。 価格の確認のみを目的としています。予約を完了するには、必ず実際のブラウザーで TaW に移動してください。
  • TaW の利用規約では、自動アクセスが制限されている可能性があります。このスキルは呼び出しごとに 1 回検索し、過度なクロールは行わず、ページ読み込み 1 回分の処理だけを実行します。個人の価格比較に使用し、大量のスクレイピングには使用しないでください。
  • 認証情報は環境変数経由でのみ渡されます。このスキルは認証情報をディスクに書き込まず、ログにも出力しません。
  • デバッグ出力には個人データが含まれる場合があります。 TaW は、ログイン中のユーザーのメールアドレス、勤務先、IP、アカウントハッシュを結果ページの HTML に埋め込みます。スクリプトがこれらを出力することはありませんが、--debug モードと --raw-html-out では、これらを含むページ HTML 全体が保存されます。デバッグ用ファイルを一般公開しないでください。

今後の拡張

  • ギフトカードや家電などに対応する search-shopping(/tickets/shopping.php?sub=...)
  • 休暇パッケージに対応する search-packages(/tickets/packages.php?sub=packages&action=view&id=N)
  • 空港コードの入力問題を解決する fix-cars
  • チケットの各 detail_url にアクセスして価格を抽出する with-prices モード(低速、複数ページ)

コメント