rubyを使ってメルカリのカテゴリのseedファイルを作る


あらまし

こんにちは、tech::expertを受講中のものです。

tech::expertではカリキュラムの後半にメルカリを目で見てチーム開発するという

マゾゲー チャレンジングなカリキュラムがあります。

その中でも屈指の面倒な作業が本件で説明する、カテゴリーのテーブルのシードファイルの作成。

「メンズ」の「トップス」の「Tシャツ」というように親、子、孫の関係にある多階層カテゴリというやつらしいです。

参考:多階層カテゴリについて(https://qiita.com/chopin3/items/ca5525406ef005086e59)

実物をご覧ください。

https://www.mercari.com/jp/category/

親カテゴリは10個くらい、子カテゴリは100個くらい、孫カテゴリは1000個くらいという

初心者には大きすぎるテーブルの仕様

え、これどうやってseedファイル作るの?作っていくのめんどくさすぎませんか!!!!

となったのでなるべくコピペなどの単純作業はせずにvs codeの置換機能とrubyでseedファイルを作れないか試行錯誤してみました。

面倒な作業をなるべく減らしてなるべく完全なseedファイルを作りたいよという方向けの記事になるかと思います。長くなってしまいましたが、参考になれば幸いです。


目標達成条件

今回作るテーブルはこんな感じ

id
name
parent_id

1
メンズ

2
レディーズ

3
トップス
1

4
パンツ
1

5

1

6
Tシャツ
2

7
パーカー
2

なのでこの場合


category.rb

# 親カテゴリの作成

parent_array = ['メンズ','レディース']
parent_array.each do |parent|
Category.create(name: parent)
end

# 子カテゴリの作成
child_array = [['トップス',1],['パンツ',1],['靴',1]]
child_array.each do |child|
Category.Create(name: child[0],parent_id: child[1])
end

# 孫カテゴリの作成
grandchild_array = [['Tシャツ',2],['パーカー',2]]
grandchild_array.each do |grandchild|
Category.Create(name: grandchild[0] , parent_id: grandchild[1])
end


こんな感じのseedファイルが作成されれば言い訳である!!

というわけで3部に分けて作り方を説明していきます。

まずは親カテゴリからやりましょう。


親カテゴリの作成

今回seedファイルを作るに当たって参考とするページはこちらになります。

https://www.mercari.com/jp/category/

一番上を見てみると親のカテゴリが一覧となって書いてあります。これを使わない手はありません。

image.png

とりあえずvscodeにコピペしてみるとこんな感じ、うん素直な感じでいいですね。

image.png

画面の右上に注目してください、これはvscodeの置換機能です。(何枚もスクショを貼ると見づらくなるので置換のナンバリングされている部分は切り貼りされています)

エディターならだいたいついてる機能ですが、実はこれ、正規表現が使えちゃうんです!!

これでテキストを加工していきます。

最終的には['メンズ','レディース']となっている配列を作りたいので


  • テキストの行頭と行末にクオーテーションをつける(画面の赤い1と2の操作)

  • 行末の改行を無くしてカンマに変える。(画面の赤い3の操作)

以上の操作をします、正規表現やvscodeの使い方がわからなかったら調べて見ましょう。

上記操作をしてから[]で囲ってあげると


category.rb

['レディース','メンズ','ベビー・キッズ','インテリア・住まい・小物','本・音楽・ゲーム','おもちゃ・ホビー・グッズ','コスメ・香水・美容','家電・スマホ・カメラ','スポーツ・レジャー','ハンドメイド','チケット','自動車・オートバイ','その他']


親の配列ができました!

これで親のカテゴリーのseedは作れそうです!

3分の1の作業が終わりましたね!


子カテゴリの作成

いよいよここから難しくなります。

child_array = [['トップス',1],['パンツ',1],['靴',1]]

child_array.each do |child|
Category.Create(name: child[0],parent_id: child[1])
end

こういった実行ができるようにchild_arrayを作っていきます。

先ほどカテゴリの一覧ページに戻り、何とかうまく取り出せないか考えて見ます。

一覧ページは子カテゴリー(水色、ハイパーリンク)まで入ってしまっており、このままコピペすると親と子カテゴリを紐づける上で邪魔になります、そこでデベロッパーツールを使います

子カテゴリは全てリンクでありaタグが使われています。

$('a').empty()

つまり上のコードをデベロッパーツールのコンソールから実行すると消えてくれます!

image.png

綺麗に消えてくれました!

このままコピペすると空白行ができるので^\nを置換して空白行を削除します。

するとこんな感じになります。

image.png

ここで着目して欲しいのが子カテゴリーの最初には親カテゴリーの名前が必ずきていること。

これを配列にしてうまいeachメソッドを作ればparent_idがついた子カテゴリーの配列を作れそうです。

というわけでrubyでアルゴリズムを組んでみました。


get_child.rb

parent_array=['レディース','メンズ','ベビー・キッズ','インテリア・住まい・小物','本・音楽・ゲーム','おもちゃ・ホビー・グッズ','コスメ・香水・美容','家電・スマホ・カメラ','スポーツ・レジャー','ハンドメイド','チケット','自動車・オートバイ','その他']

parent_child_array=['レディース','トップス','ジャケット/アウター','パンツ','スカート','ワンピース','靴','ルームウェア/パジャマ','レッグウェア','帽子','バッグ','アクセサリー','ヘアアクセサリー','小物','時計','ウィッグ/エクステ','浴衣/水着','スーツ/フォーマル/ドレス','マタニティ','その他','メンズ','トップス','ジャケット/アウター','パンツ','靴','バッグ','スーツ','帽子','アクセサリー','小物','時計','水着','レッグウェア','アンダーウェア'、、、省略]

# parent_idに代入する親のidが代入されていくもの
parent_id_cnt = 0
child_array = []
parent_child_array.each do |parent_child|
# parent_childの名前が親カテゴリの名前であるか比較
if parent_child == parent_array[parent_id_cnt]
# 親カテゴリの名前がきた時、次に親カテゴリの名前が来るまで
# 子カテゴリはその親カテゴリを親とするため、親カテゴリのidをparent_idにする
parent_id_cnt += 1
else
# 親カテゴリの名前でなかった場合子カテゴリであるので
# child_arrayにparent_idと共に格納していく。
child_array << [parent_child,parent_id_cnt ]
end
end
# 最後にターミナルに完成した配列を出力。
p child_array


こんな感じのファイルを作ってあげて

$ ruby get_child.rb

というふうに実行してあげるとこんな実行結果になるはずです。

image.png

確認してみましょう。


孫カテゴリの作成

いよいよ最後に孫カテゴリのseedを作っていきます。

# 孫カテゴリの作成

grandchild_array [['Tシャツ',2],['パーカー',2]]
grandchild_array.each do |grandchild|
Category.Create(name: grandchild[0] , parent_id: grandchild[1])
end

こんな感じの配列を作ることができればあなたの勝ちです!

先ほど親カテゴリと子カテゴリでやったことを子カテゴリと孫カテゴリでやってあげれば実装出来そうです。

しかし、問題がいくつかあります。下記画像をご覧ください。

image.png

問題

①子カテゴリにある「その他」が孫カテゴリにも存在しておりややこしい

②孫カテゴリに「その他を持たない子カテゴリがありややこしい」

③孫カテゴリを持たない子カテゴリが存在する。(「すべて」は孫カテゴリではないと判断しました)

先ほどのeachメソッドでは名前を使って親カテゴリか子カテゴリかを判断していました。

そのため孫カテゴリと子カテゴリに同名のカテゴリがあるとアルゴリズムがうまく働いてくれません。

また問題③にあるような、孫カテゴリを持たない子カテゴリは想定できていません。

問題③は深刻な問題で、カテゴリを扱うあらゆる場面で孫カテゴリを持たない子カテゴリの場合を想定してハンドリングをしていかなければならず、障害になります。

そこですべての子カテゴリは孫カテゴリにその他を持っているという変更を加えることにしました。

9.5割程のカテゴリは孫カテゴリに「その他」を持っているためそこまで不自然じゃないはずです。

これのおかげで問題②と③が解消されました!

残りは①ですね、これは配列の生成の時に工夫をすることで除くことができます。

それではまずカテゴリ一覧のページから子カテゴリと孫カテゴリが混ざったテキストを生成しましょう

スクリーンショット 2019-06-16 14.08.17.png

今回は以下のコマンドをデベロッパーツールで使いました。

# 親カテゴリ名はh3なので、h3を消す

$('h3').empty()
# 「すべて」を内容にもつaタグ(孫カテゴリ部分)を消す
$('a:contains("すべて")').empty()
# 「その他」を内容にもつaタグ(孫カテゴリ部分)を消す
$('a:contains("その他")').empty()

孫カテゴリの「その他」はここで消してしまいます、すべての子カテゴリにもれなく付いているという設定なので、配列を作る際にあとで付け足す形をとります。

さて、これでいい感じのテキストができたので

配列にしてrubyに整理してもらいましょう。


get_grandchild.rb

# 子カテゴリの配列

child_array=[["トップス", 1], ["ジャケット/アウター", 1], ["パンツ", 1], ["スカート", 1], ["ワンピース", 1], ["靴", 1], ["ルームウェア/パジャマ", 1], ["レッグウェア", 1], ["帽子", 1], ["バッグ", 1], ["アクセサリー", 1], ["ヘアアクセサリー", 1], ["小物", 1], ["時計", 1], ["ウィッグ/エクステ", 1], ["浴衣/水着", 1], ["スーツ/フォーマル/ドレス", 1], ["マタニティ", 1], ["その他", 1] 、、、略]
# 今回作った、子カテゴリと孫カテゴリの配列
child_grandchild_array=['トップス','Tシャツ/カットソー(半袖/袖なし)','Tシャツ/カットソー(七分/長袖)','シャツ/ブラウス(半袖/袖なし)','シャツ/ブラウス(七分/長袖)','ポロシャツ','キャミソール','タンクトップ','ホルターネック','ニット/セーター','チュニック','カーディガン/ボレロ','アンサンブル','ベスト/ジレ','パーカー','トレーナー/スウェット','ベアトップ/チューブトップ','ジャージ','ジャケット/アウター','テーラードジャケット','ノーカラージャケット','Gジャン/デニムジャケット','レザージャケット','ダウンジャケット','ライダースジャケット','ミリタリージャケット','ダウンベスト','ジャンパー/ブルゾン','ポンチョ','ロングコート','トレンチコート','ダッフルコート','ピーコート','チェスターコート','モッズコート','スタジャン','毛皮/ファーコート','スプリングコート','スカジャン','パンツ','デニム/ジーンズ','ショートパンツ','カジュアルパンツ','ハーフパンツ','チノパン','ワークパンツ/カーゴパンツ','クロップドパンツ'、、、、、略]

# parent_idに代入する親のidが代入されていくもの
parent_id_cnt=0
grandchild_array=[]
child_grandchild_array.each do |child_grandchild|
# parent_childの名前が親カテゴリの名前であるか比較
if child_grandchild == child_array[parent_id_cnt][0]
# 新しい子カテゴリに変わる時はすなわち一つ前の子カテゴリの終わりなので、
# ここで孫カテゴリ「その他」を追加する操作をする。
# 本番のテーブルでは親カテゴリの数だけ子カテゴリーの数がずれるので13を足す。
grandchild_array << ['その他',parent_id_cnt + 13]
# 子カテゴリの名前がきた時、次に子カテゴリの名前が来るまで
# 孫カテゴリはこの子カテゴリを親とするため、子カテゴリのidをparent_idにする。
parent_id_cnt += 1
else
# 子カテゴリの名前でなかった場合孫カテゴリであるので
# grandchild_arrayにparent_idと共に格納していく。
grandchild_array << [child_grandchild,parent_id_cnt + 13]
end
end
# 最後に出力。
p grandchild_array


こんな配列ができたら成功です!

image.png

アルゴリズムの都合上、最初に['その他',13]が余分にできてしまいます。

逆に、最後に生成されるはずの['その他',158]が生成されないので、修正したら完成です。

結構parent_idが狂ってしまったりしていることがあるので、簡単に自分の目で確認してみてください。

最後に、1200行程度のseedができたわけですが実行する際SQL文が1200行実行されるとなると時間がかかってしまうので、bulk insertをしましょう。

(参考:https://qiita.com/makicamel/items/b6d4f3d2661fc66103ed)

以上でカテゴリのseedファイルの作業は終了となります、お疲れ様でした。