「取れた」と読んでいたのは、ただの案内ページだった:3日間、商品が0件
トレカダッシュ 開発の記録 / 2026年10月
iOS アプリ「トレカダッシュ」は、通販サイトの販売ページを定期的に見に行って、抽選や先着の情報を集めています。ある日、ポケモンセンターオンラインの取得が、3 日間ずっと、商品 0 件になっていたことが分かりました。エラーは出ていません。「取れた」と判定されていたからです。何が起きていたのか、コミットの記録で書きます。
このサイトには、トレカダッシュの作品ページはまだありません。ここに書くのは、開発の記録です。
「取れた」の判定が、甘かった
ページの取得は、外部のサービス(Scrape.do)を通して行っています。取得の設定には、安いものから高いものまであって、安い設定で取れなければ、強い設定に切り替える(昇格する)作りです(取得の費用の記事と同じ仕組みです)。
この「取れたかどうか」の判定が、問題でした。ポケモンセンターのページは、安い設定で取ると、2,410 バイトの、「JavaScript を有効にしてください」という案内ページが返ってきます。商品の一覧ではなく、ただの案内です。ところが、これを「取れた」と読んで、成功と扱っていました。成功なら、強い設定への切り替えも行われません。
こうして、9 月 4 日から 3 日間、取得は「成功」し続けて、商品は 0 件のまま、でした。
同じページを、3つの設定で叩いて測った
原因を推測で決めないために、ポケモンセンターの検索ページを、取得の設定を変えて実際に取り直しました。安い設定は 1 クレジット、強い設定(super と render を併用)は 25 クレジットかかります。
| 取得の設定 | 応答 | 中身 |
|---|---|---|
| 安い設定(1 クレジット) | 200 | 2,410 バイト。「このサイトでは JavaScript を有効にする必要があります」だけ |
| super + render(25 クレジット) | 200 | 278,322 バイト。商品 39 件 |
応答コードはどちらも 200 です。通信の成功だけを見ていたら、この 2 つは区別できません。違いは中身の大きさだけで、約 115 倍ありました。
守りはあったのに、2 つとも寸前で通り抜けていた
「取れたかどうか」を見る条件は、もともと何もなかったわけではありません。ページに次の 2 つの確認がありました。
- 本文が 2,000 文字未満なら、取れていないとみなす。案内ページは 2,410 バイトで、わずかに超えていました。
- 本文に「JavaScript が無効」という文言があれば、取れていないとみなす。実際の文言は「JavaScript を有効にする必要があります」で、この文言には一致しませんでした。
どちらも、想定した形の少しだけ外側で、通り抜けています。確認の条件は、想定した 1 つの形にしか合っていませんでした。実際に来るページの形は、いくつもあります。
通ってしまった後は、次のことが起きます。
- 強い設定に切り替え直さない。商品は 0 件のまま。
- 「安い設定で取れる」と覚えて、強い設定への切り替えを解除する。
- 以後ずっと 0 件。エラーが 1 件も出ないので、ログにも何も残らない。
3 日間の数字
| 日時 | ポケモンセンターの取得件数 |
|---|---|
| 9 月 4 日 12:45 まで | 10 件か 8 件(安定していた) |
| 9 月 4 日 12:45 から 9 月 7 日 | 0 件が 323 回連続。エラーは 1 件もなし |
| 直した後に手で実行 | 10 件に戻った |
直し方
- 「取れた」の判定を、1 か所に集める。それまで、判定が複数の場所に散らばっていました。ブロックされたページかどうかを見る関数(
scrapeDoResultLooksBlocked)を 1 本にして、全部がそれを使うようにしました。 - 通った設定を、覚える。一度成功した設定を覚えておき、次からは、そこから試します。これで、1 回の取得に使うクレジットが、36 から 25 に減りました。
新しい判定は、何を「取れていない」と読むか
scrapeDoResultLooksBlocked は、次のどれかに当てはまると「取れていない」と答え、その理由の文字列も返します。理由はログに必ず残るので、あとから「なぜ強い設定に上がったか」が読めます。
- 本文が短すぎる。
- 順番待ちのページ(Queue-it)である。
- 中身が小さいのに、JavaScript を求める案内がある。
3 つ目には、落とし穴があります。「JavaScript が無効になっています」という一文は、まっとうなページにも、noscript という要素として常に入っていることがあります。実際に、別の店(ヤマダ)のページで一度、取れているページを「取れていない」と誤診しました。そこで、この判定は8,000 バイト未満のときだけ当てる形にしました。大きなページは、noscript があっても本物とみなします。
この関門は、安い設定、super、render と super の併用の、3 つの段階すべてで同じものを通します。
通った設定を、覚えるようにした
昇格のしくみ自体にも、無駄がありました。それまでは、取得の関数が自分の中で設定を上げたときだけ、通った設定を覚えていました。呼び出し側が設定を切り替えて上げた分は、覚えていなかったのです。その結果、毎回 1 + 10 + 25 の36 クレジットを払っていました。覚えるようにしたので、次回からは 25 クレジットで始まります。安い設定も、定期的に試し直します。
書き込みは、設定が変わったときだけにしています。毎回書くと、今度はデータベースの書き込み費用が増えるからです。
同じ日に見つかった、もう一つの汚れ
同じ点検で、別の問題も見つかりました。商品名の「別表記」の食い違いを判定する処理が、照合の入口と、お掃除の側で、別々に書かれていました。お掃除の側にだけ、「商品の形が食い違う」という判定が無かったのです。その結果、トライアルデッキの別表記が、ブースターパックの商品に残ったままになり、毎日、誤った照合を呼び続けていました。判定を共通の関数にして、27 商品の 46 件を除去しました。
あわせて、「更新する文書が無い」というエラー(7 日間で 64 件)も解消しました。商品は、キャッシュが古いときに読み直す。記事は、消えていたら見送って、残りの重複処理を続ける、という形にしています。
気づいた経緯と、ほかの「0 件」4 つの確認
気づいたきっかけは、作業の最初に必ず見る定型の点検です。健全性のチェックに、「取得 0 件が 48 時間続いている」という警告が並びます。それまでは、そこに並んだ名前を「掲載が無いだけ」と流していました。この回は、流さずに中身を確かめました。
同じ警告に並んでいたほかの 4 つも、1 つずつ見ました。全部、まっとうな 0 件でした。なぜ 0 件でよいのかを書き残しておかないと、次に見た人が同じ確認をやり直すことになります。
| 情報源 | 確かめた中身 |
|---|---|
| 三洋堂 | ページに「現在受付中の抽選販売はありません」と書いてある。取得も成功している(56KB)。 |
| コナミスタイル | 16 件取れるが、バッジが全部「遊戯王カードゲームポイント交換専用」。現金では買えない景品なので、設計どおり除外している。 |
| 楽天市場 | もともと 1〜2 件が普通の情報源。14 日間で 0 件が 1,860 回、1 件が 2,345 回あった。 |
| ミニストップオンライン | どの設定でも取れない(20 秒の待ちを 2 回ずつ、3 設定すべてで応答なし)。抽選受付のときだけページが出る作りなので、休止の仕組みに任せている。 |
同じ「0 件」でも、掲載が無い 0 件、設計どおり除く 0 件、取れていない 0 件は別のものです。件数だけでは区別できないので、中身を見て、理由を書き残しています。
分かったこと
「成功」と「中身が取れた」は、別のもの。
- 成功の判定は、中身で見る。通信が通ったことと、欲しいものが取れたことは、別です。「取れたものが 0 件」が続いたら、それ自体を異常として見張る必要があります。
- 判定は、1 か所に。同じ判定が 2 か所にあると、片方だけが古くなります。照合の入口とお掃除のように。
- エラーが出ないことは、うまくいっていることの証拠にならない。
この記事は、2026 年 9 月 7 日のコミットと、その日の作業の記録から書きました。数字(2,410 バイト、278,322 バイト、323 回、36 と 25 クレジット)は、その記録に残っていた実測値です。
この話に出てくるもの
- 「しょうがない」を測り直したら、取得の費用は8分の1だった — 同じ取得の仕組みの、費用の記録
- 確認できなかったときに、通していた — 同じアプリの、別の記録
ほかの記録
- 確認できなかったときに、通していた:「fail-open」が転売品を招いた話 — 「定価を確認できなかったら通す」という設計に、検索の1日上限という盲点があった。「知らなかった」と「調べられなかった」を区別して直した記録。
- 一度だけ生まれた誤字が、店ごとに増えていった:「30th SEREBRATION」の件 — 誤字の商品が一度作られると、既存の商品として再利用され、店ごとに増える。13記事を直し、短い見出しを完全一致で拾う予防を入れ、保険のチェックの限界まで確かめた記録。
- 「しょうがない」を測り直したら、取得の費用は8分の1だった — 「イオンとあみあみは費用が重くて縮められない」は、古い単価に基づく思い込みだった。3つの取得設定で同じページを比べて、描画が丸ごと無駄だと分かった記録。
コメント
感想や、記事の間違いの指摘は、ここへ。書くには、GitHub のアカウントが要ります。
押すと、giscus(giscus.app)と GitHub に接続します。押さなければ、接続しません。詳しくはあつかいに書いています。