【2026年】SUUMOのお気に入りを一括削除するスクリプト(コンソールにコピペするだけ)
TL;DR
コードだけほしい方はこちらへ:
目次
- はじめに
- やりたいこと
- 経緯:Claude in Chrome で雑に投げた
- 260330 修正:最初のコードは実は動いていなかった
- 手順(共通)
- 掲載終了物件を全件削除する
- 件数を指定して削除する
- その他のバリエーション
- コードの解説
- 注意事項
- おわりに
はじめに
SUUMOで物件を探していると、お気に入りリストがすぐ50件の上限に達します。
問題は、一括削除の機能が存在しないこと。
1件ずつ「お気に入りから削除」→「削除する」を繰り返す必要があります。
2026年にこの機能がないのは正直理解に苦しみますが、
さらに驚いたのは、これをスクリプトで解決するQiitaやZennの記事が
1件も見つからなかったことです。
SUUMOのuserscriptといえば、レオパレス物件を除外するTampermonkeyスクリプトや、お気に入り物件情報を一括取得するスクレイピングツールは存在するのに、一括削除だけは誰も書いていない。
ないなら書くしかない!と思いました!
やりたいこと
SUUMOのお気に入りリストから、掲載終了になった物件(グレーアウト表示)を一括で削除する。
経緯:Claude in Chrome で雑に投げた
今回は Claude in Chrome 拡張機能を使って、SUUMOのお気に入りページ上で以下のように指示しました。
掲載終了でグレーアウトしている物件を削除したい。「お気に入りから削除」ボタンをクリックすると「削除する」の確認ボタンが表示されるので、それを順番にクリックしていってほしい。
確認ボタンのHTMLはこう:
<a href="javascript:void(0);" class="cassette_btn_round cassette_btn_round--action js-cassette_delete-confyes">削除する</a>直接操作するのではなく、ページの構造を分析して、開発者コンソールで実行できるJavaScriptコードを出力してほしい。
これだけで動くコードが出てきました。
……と思ったのですが、実は動いていませんでした。次のセクションで詳しく書きます。
260330 修正:最初のコードは実は動いていなかった
Claude in Chromeが最初に出力したコードは、一見動いているように見えて実際には削除できていませんでした。
新規のコードを試す前にちゃんとお気に入りのリストを確認しないとですね!
最初のコードの流れはこうでした:
-
.cassette.is-inactiveで掲載終了の要素を見つける - モーダルを
style.display = 'block'で強制表示 -
.js-cassette_delete-confyes(「削除する」ボタン)を.click()
問題が2つありました。
問題①:最初の削除ボタンのクリックが丸ごと抜けていた
元のコードは「お気に入りから削除」ボタン(.js-cassette_delete)を一切クリックせず、いきなり確認モーダルの「削除する」ボタンを叩いていました。
SUUMOのコードを見てみると、この最初のボタンのクリックハンドラーで
jQuery(confBtn).data('clipkey', this.id)
という処理が走り、confirm ボタンに clipkey(物件識別子)をセットしています。この手順を飛ばすと、「削除する」を押してもサーバーに空の clipkey が送られ、サーバーは200 OKを返すものの実際には何も削除されないという状態でした。
問題②:jQuery 1.4.2 のイベント二重発火
仮に問題①を修正しても、.click() でプログラム的に確認ボタンをクリックすると、jQuery 1.4.2のイベント委任の仕組み上、ハンドラーが2〜4回重複実行されます。
ここで致命的なのは、SUUMOの削除APIがトグル方式だということ。呼び出すたびに「削除 ↔ 復元」が切り替わるため、偶数回呼ばれると削除した直後に復元されます。画面上では要素がグレーアウトして削除されたように見えるのに、ページをリロードすると全部戻っている——という現象が起きていました。
修正後のアプローチ
-
deleteBtn.click()— 最初の削除ボタンをクリックしてclipkeyデータをセット -
XMLHttpRequestで直接APIを1回だけ呼び出し — jQueryを完全に迂回して二重発火を防止 - サーバー応答のcookie値を同期
- DOMから要素を削除
要約すると:「最初のボタンクリックが抜けていた」+「jQueryの .click() が二重発火する」の2つを修正して、raw XHRに置き換えたということです。
手順(共通)
以下の手順はどのバージョンのスクリプトでも共通です。
1. お気に入りページを開く
SUUMOにログインし、お気に入り物件リストのページを開きます。
2. 開発者ツールを開く
ページ上で 右クリック → 検証(Inspect) を選択し、開発者ツールを開きます。
上部のタブから Console を選択してください。
3. ペーストを許可する
セキュリティ上の理由で、初回はコンソールにコードを貼り付けできないことがあります。
コンソールに以下を入力してEnterを押してください:
allow pasting
これでコードの貼り付けが可能になります。
4. 下記のスクリプトをコピペして実行
用途に合わせて、以下のいずれかのスクリプトを選んでください。
掲載終了物件を全件削除する
一番シンプルなバージョンです。掲載終了(グレーアウト)の物件をすべて削除します。
とりあえず掲載終了を全部消したい方はこちらをどうぞ。
(async () => {
const sleep = ms => new Promise(r => setTimeout(r, ms));
const inactives = [...document.querySelectorAll('.cassette.is-inactive')];
console.log(`Found ${inactives.length} expired listings. Deleting all.`);
for (let i = 0; i < inactives.length; i++) {
const item = inactives[i];
const title = item.querySelector('.cassette_item-title-text')?.textContent?.trim() || 'unknown';
const deleteBtn = item.querySelector('.js-cassette_delete');
if (!deleteBtn) { console.warn(`[${i+1}] No delete button for "${title}", skipping.`); continue; }
const clipkey = deleteBtn.id;
if (!clipkey) { console.warn(`[${i+1}] No clipkey for "${title}", skipping.`); continue; }
console.log(`[${i+1}/${inactives.length}] Deleting: ${title}`);
deleteBtn.click();
await sleep(500);
const result = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/jj/common/service/JJ901FL020/', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onload = () => resolve(JSON.parse(xhr.responseText));
xhr.onerror = () => reject(new Error('XHR failed'));
xhr.send('clipkey=' + clipkey);
});
console.log(` ✓ Server says ${result.myListBknCnt} remaining`);
if (result.myListCookieBknCntInfo) {
suumo.headerfooter.setCookie({ name: 'mylist_bukken_cnt', value: result.myListCookieBknCntInfo });
}
item.remove();
await sleep(1000);
}
console.log('Done! All expired listings deleted. Reload the page to confirm.');
})();
実行すると、掲載終了の物件が約1秒間隔で順番に削除されていきます。コンソールに物件名と進捗が表示されるので、完了まで待つだけです。
件数を指定して削除する
「いきなり全部消すのは不安」「まず数件だけ試したい」という方はこちら。
コード冒頭の MAX の数字を変えるだけで、削除する件数を調整できます。
-
const MAX = 2→ 掲載終了物件のうち、最初の 2件だけ 削除 -
const MAX = 10→ 10件 削除 -
const MAX = 50→ 50件 削除(お気に入りの上限いっぱい)
初めて実行するときは 2 のままにして、ちゃんと消えることを確認してから数字を増やすのがおすすめです。
(async () => {
const MAX = 2; // ← 削除したい件数に変更してください
const sleep = ms => new Promise(r => setTimeout(r, ms));
const inactives = [...document.querySelectorAll('.cassette.is-inactive')];
console.log(`Found ${inactives.length} expired listings. Will delete ${MAX}.`);
for (let i = 0; i < inactives.length && i < MAX; i++) {
const item = inactives[i];
const title = item.querySelector('.cassette_item-title-text')?.textContent?.trim() || 'unknown';
const deleteBtn = item.querySelector('.js-cassette_delete');
if (!deleteBtn) { console.warn(`[${i+1}] No delete button for "${title}", skipping.`); continue; }
const clipkey = deleteBtn.id;
if (!clipkey) { console.warn(`[${i+1}] No clipkey for "${title}", skipping.`); continue; }
console.log(`[${i+1}/${MAX}] Deleting: ${title} (clipkey: ${clipkey})`);
// Step 1: Click delete button (sets clipkey data on confirm button)
deleteBtn.click();
await sleep(500);
// Step 2: Direct XHR (bypasses jQuery double-fire)
const result = await new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/jj/common/service/JJ901FL020/', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onload = () => resolve(JSON.parse(xhr.responseText));
xhr.onerror = () => reject(new Error('XHR failed'));
xhr.send('clipkey=' + clipkey);
});
console.log(` ✓ Server says ${result.myListBknCnt} remaining`);
// Step 3: Update cookie
if (result.myListCookieBknCntInfo) {
suumo.headerfooter.setCookie({ name: 'mylist_bukken_cnt', value: result.myListCookieBknCntInfo });
}
// Step 4: Remove from DOM
item.remove();
await sleep(1000);
}
console.log('Done! Reload the page to confirm.');
})();
その他のバリエーション
掲載中の物件も含めて全件削除する
掲載終了だけでなく、お気に入りに登録されている物件をすべて削除したい場合は、セレクタを変更するだけです。
上記のどちらのスクリプトでも、以下の1行を変えてください:
// 変更前(掲載終了のみ)
const inactives = [...document.querySelectorAll('.cassette.is-inactive')];
// 変更後(掲載中も含めて全件)
const inactives = [...document.querySelectorAll('.cassette')];
.is-inactive を外すことで、掲載中(アクティブ)な物件も削除対象になります。
掲載中の物件は復元できません。本当に全件削除して問題ないか確認してから実行してください。
掲載中の物件だけを削除する
逆に掲載終了はそのまま残して、掲載中の物件だけ削除したい場合はこうします:
// 掲載中(アクティブ)のみ — is-inactive でない .cassette を取得
const inactives = [...document.querySelectorAll('.cassette:not(.is-inactive)')];
コードの解説
上記の修正経緯を踏まえた、最終的なコードの各ステップです。
| ステップ | 処理 | 説明 |
|---|---|---|
| 準備 | .cassette.is-inactive |
掲載終了でグレーアウトしている物件要素を全件取得 |
| Step 1 | deleteBtn.click() |
「お気に入りから削除」ボタンをクリック。confirm ボタンに clipkey がセットされる |
| Step 2 |
XMLHttpRequest で直接POST |
削除API(/jj/common/service/JJ901FL020/)に clipkey を送信。jQuery経由だと二重発火するため直接XHRで叩く |
| Step 3 | setCookie |
サーバーから返ったお気に入り件数をcookieに反映 |
| Step 4 | item.remove() |
削除済みの要素をDOMから除去して画面に即反映 |
| 待機 | await sleep(1000) |
サーバーへの過剰なリクエストを防ぐため、1件ごとに1秒待機 |
注意事項
- 掲載終了の物件のみがデフォルトの削除対象です。掲載中の物件は削除されません。
- セレクタを変更することで対象を変えられます(その他のバリエーションを参照)。
- SUUMOのHTML構造が変更された場合、スクリプトが動作しなくなる可能性があります。
- 実行は自己責任でお願いします。
おわりに
50件しか登録できないのに一括削除がないのは、良くないUXだなとはいつも思いますね。
まぁ、正直なところ自分でUrlをbookmarkすれば良い話なので、そんなに大したことではないと思いますが、
そしてそれを解決する記事が2026年まで存在しなかったのはとても不思議だったので!書いてみました。
なんかQiitaにしょうもないけどオリジナルなものがかけてうれしいですけどね!
同じ悩みを持った方の助けになれば幸いです。