メルカリで販売中の検索をデフォルトにするUser Script
どんな講釈をヌかそうと、要らないものは要らない。
・ メルカリが検索結果に「売れた商品」も表示するのはなぜ? 商品検索におけるUI/UXの考え方 - ログミーTech
邪魔だから消してみると何が起きるかと言うと、全KPIがすべて下がります。並みの勢いじゃないですよ。出品転換率、つまり1人のお客様が出品行動を取ったという行動も減るし、買ったという行動も減ります。
その結果、結局取引総数も取引総額もすべて下がって、負のスパイラルに入る。それで慌てて元に戻すというのを、実はUSとJPを合わせて、4~5回は繰り返しているんじゃないかと思います(笑)。
いったい、なぜなのか? 長らく謎でした。ちなみに創業者の山田進太郎は「ユーザーインタビューとかで、邪魔だからと言われているので消してもいいですよね?」と言われたとき、「いや、俺はあったほうがいいと思うけどね」と言ったそうです。
「なんか知らないけど、あったほうがいいと思う」と言ったと。3年前くらいですね。直感的にわかっている感じでした、あの人は(笑)。予言していたんです。
僕は「こういうことなんだろうな」という仮説があるんです。検索改善をしていて、僕がうまくいったときというのは、必ずさっきの購買転換率が上がるんです。
「やった! 検索結果がよくなって統計上有意に買う人が増えた! 買う数が増えた!」と思うと同時に、出品する人も増えるんですね。「なんで検索改善しているのに出品する人が増えるんだ? 俺は出品を増やそうとはしていないんだけど」と思っていました。
🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔
結局、行き着いた答えが「出品しようとしたときに検索してるんだ」ということです。つまり、買おうとする人もいるし、売ろうとする人もいる。Nintendo Switchを持っている人が売ろうとしたときに、「Nintendo Switch」と検索して何を見ているかという話ですね。
転売ヤーの気持ちなんか知るかヴォケ。真っ当な購買には最低のUX。
ただモノを買おうとしている時に、在庫のない情報は単純にゴミ情報でしか無い。逆に、もしSOLDの情報も欲しい時というのは、純粋な情報収集だろうね。絶版CDのジャケット画像や収録曲、アルバム タイトルとか、今自分が持っている断片情報を補完したくて検索しているときは。メルカリに限らず、すべての情報を覗きたい、ということはある。でもそれって、購買とは関係なく。こういった販売プラットフォームに置いて。購買者目線第一に立てないのは全く評価できない。
環境
- Tampermonkey
User Script
// ==UserScript==
// @name メルカリで販売中の検索をデフォルトにする
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://mercari.com/*
// @match https://*.mercari.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=mercari.com
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Your code here...
let f = function(body) {
body.querySelectorAll('div[slot="input"] form:not([data-created])').forEach(function(form) {
let f2 = form.cloneNode(true);
let input = document.createElement('input');
input.type = 'hidden';
input.name = 'status';
input.value = 'on_sale';
f2.action = '/search';
f2.querySelectorAll('input[type="search"][name="query"]').forEach(function(input) {
input.name = 'keyword';
input.placeholder += ' (販売中)';
});
f2.appendChild(input);
f2.setAttribute('data-created', new Date().toLocaleString(undefined, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'short',
}));
form.parentNode.appendChild(f2);
form.remove();
console.info(f2);
});
}
document.querySelectorAll('body').forEach(function(body) {
// observer
let observer = new MutationObserver(function() {
f(body);
});
observer.observe(body, {
childList:true,
subtree:true,
attributes:true,
});
});
})();
before after
before
-
form
のaction
が.
- 検索ボックス
name
がquery
after
-
form
のaction
をsearch
に変更。 - 検索ボックス
name
をkeyword
に変更。 -
form
に、name
がstatus
でvalue
がon_sale
のinput
要素をhidden
で追加。 - SPA的な遷移ではなく従来のページ全体の遷移になる。
解説
以前は status パラメーター用に hidden を form 属性内に追加するだけで良かった。しかし、1年くらい前?の大きなWEBページの仕様変更の際にその方法が使えなくなってしまった。どうも、この form が、検索ボックスの値のみを吸い上げて遷移するようイベントになったようで。パラメーターの挿入ができなくなってしまった、ガッデーム。
しかし、検索履歴からは「販売中」の条件履歴も出てくるし、検索ページもQueryStringで動作している様子。
であれば、またパラメーターを割り込ませるほうが良さそうと判断した。
(1) パラメーターの割り込みのための変更処理。
(1-1) form要素を複製、入れ替え。
まず、検索時のイベントを潰すため。form
要素を複製して入れ替る。要素は基本的に欲しいので、子要素含めて複製する。複製対象は div[slot="input"] form
で取得する。
( form:not([data-created])
は後述の多重実行防止で記載。)
body.querySelectorAll('div[slot="input"] form:not([data-created])').forEach(function(form) {
let f2 = form.cloneNode(true);
/** (略). */
form.parentNode.appendChild(f2);
form.remove();
});
(1-2) formのリクエスト先を変更。
通常のWEBアプリケーションの form
であれば、配下の入力要素収集してQueryStringを作り、
action
に対してパラメーターを付与してリクエストが投げられる。シンプルにその挙動にするため、action
を適切に /search
に適切に変更する。
let f2 = form.cloneNode(true);
/** (略). */
f2.action = '/search';
(1-3) 検索の入力ボックスのパラメーター名の変更。
search
へリクエストを投げるため、併せて入力ボックスのパラメーター名を変更する。
f2.querySelectorAll('input[type="search"][name="query"]').forEach(function(input) {
input.name = 'keyword';
// プレースホルダーも変更して、スクリプトの実行可否の視認性を確保。
input.placeholder += ' (販売中)';
});
ホントは querySelector
で1個だけ処理をすれば良いんだけど。ワンライナー (笑) 的な思想では、オプショナル チェーニングは代入式では使えないのと、 querySelectorAll
を使うと条件式を省略出来るしとかで、何かと querySelectorAll
が便利だったりする。
(1-4) "販売中" 用のパラメーターの追加。
この値は見えなくて良いのでhiddenで追加。
body.querySelectorAll('div[slot="input"] form:not([data-created])').forEach(function(form) {
let f2 = form.cloneNode(true);
// input要素作成
let input = document.createElement('input');
input.type = 'hidden';
input.name = 'status';
input.value = 'on_sale';
/** (略). */
// 複製したformに追加
f2.appendChild(input);
/** (略). */
});
(2) 変更処理の実行するための監視処理。
SPAのせいか、最初に受信されるHTMLにはほとんど中身が無い様子。そのため、変更処理を実行するタイミングが図れない。ポーリングでも良いのだけど。body を変更監視し、変更時に処理を行うようにする。
(2-1) body要素に変更監視を設定。
document.querySelectorAll('body').forEach(function(body) {
// 監視オブジェクト作成
let observer = new MutationObserver(function() {
//ここに変更処理を書く
f(body);
});
// body要素に変更監視を登録
observer.observe(body, {
childList:true,
subtree:true,
attributes:true,
});
});
(2-2) CSS Selectorで多重実行防止。
何度も処理が行われしまうと困るため。属性を使って、2回以上実行されないようにする。また、CSS Selector
で判定を行い、処理をシンプルにする。単純に収集してif文で判定するよりも、CPUコストも低いだろうと予想。
// マーキングがされていないのを取得。
body.querySelectorAll('div[slot="input"] form:not([data-created])').forEach(function(form) {
/** (略). */
// 処理時間でマーキング。
f2.setAttribute('data-created', new Date().toLocaleString(undefined, {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'short',
}));
/** (略). */
});
以上
まだ改修したばかりだけれども。これで心の安寧が多少得られそうです。
しかし、メルカリにはこの検索UX以外にも、ログインUIにも酷いものがある。ログインにスマホが必要なのとか。2要素認証の必要性はわかるけど。WEBサービスでスマホが手元にあること前提ってのも作りが悪いし、ログイン情報も短いせいでPCでの使い勝手が悪い。デバイスとしてはスマホよりPCのほうが使いやすいはずなのに、使おうとするとPCでは再ログインが求められて使う気が失せる、ってのが良くある。
あとメルペイ周りの仕様も酷い。使う気がない人がデフォルトで使い続けられない改悪が定期的に起きる。たしか少し前の改悪だと。ログイン デバイスが増えるとメルペイの上限金額が0円にされて、改めて上限金額設定をしないといけない、みないな良くわからない仕様が追加されていたと思う。複数台持ちで、定期的にスマホのOSをリセットする私には非常にうざい。というか、これなんかセキュリティに良いことあるの?理解できない謎すぎる改悪。
あと、そもそも、メルカリは転売ヤーの巣窟になっていることも、十分に理解した方がいい。購買は信頼が担保されて行われるべきものであり、必要以上に身近になってはいけないと思う。ネットなのでよりこの信頼性の担保は重要であり、対面販売とは別の難しさ・課題があるはずである。SDG'sよろしく、不用品の再利用のエコ観点では、こういったフリマ市場は大いに歓迎されるべきではあるが。しかし実際には転売ヤーの食い物になっているのが現実で。市場破壊を助長しているプラットフォームの筆頭なのである。
転売ヤーに加担するような改悪をするのではなく。健全な購買のための市場形成をしっかり考えていただきたいと思う。利用者のリテラシー教育も当然必要だけれども。それとは別に。そもそもサービスとは、専門知識を一般の人に提供するのが本懐である。一般消費者が 安心 ・ 安全 を 気軽 に享受出来ることを目指さなければならない。今のメルカリは、 気軽 のみが先行しており 安心 ・ 安全 が低いと言わざるを得ない。
謝辞
- Tampermonkey
https://www.tampermonkey.net/
Discussion