きっかけ
あれはイタリアン・バルに飲みに行ったときのこと。
私はカウンターの中のスタッフともみちゃん が超美人だと気づいた。
私:「ねぇ、ともみちゃん、すごい美人じゃない!?」
男:「あーよく見ると確かに。でも俺的にはゆかちゃん(別スタッフ)の方がいいな。だってともみちゃんの髪短すぎじゃん?少年じゃないんだから…」
確かに、ともみちゃんは刈り上げベリーショト、一方で他スタッフのゆかちゃんは、ゆるふわミディアム(顔はどこにでもいそうなフツーの子だが…)。
ここで私は気づいたのだ。
男は顔の良し悪しなんてよく見ちゃいない、髪型から作り出される雰囲気を見てるんじゃないか…?
つまりだ、
ヘアスタイルさえそれっぽい状態を作り出せば、美人に勝てる可能性があるんじゃないか!?
**カワイイは作れる
という言葉が脳裏をよぎった私、
技術を使ってカワイイを作る
**ことを決意した。`
できたこと
先に、何が出来るようになったか説明する。
① カワイイを作りたい人の顔写真を入力する(既にカワイイけど)
② 顔検出+機械学習で、その人に最適なヘアスタイルをレコメンドする
③ その人+最適なヘアスタイルのイメージ画像を出力する
(画像処理してるから分かりにくいかもだけど、レコメンドした画像に元画像の顔部分を重ねてるよ)
⇒簡単に**カワイイを作る
**ことができる!
(あとは現実に実行するのみ!!)
上記を行うために今回行ったのはstepは以下です。
step1☆ ヘアスタイルの画像を大量収集して、データ蓄積する。(⇒スクレイピング・クローリング)
stap2☆ 顔の特徴量を作成し、モデリングする。(⇒顔認識)
step3☆ 最適な画像を提案する。(⇒機械学習)
step1☆ 某サロン検索サイトをスクレイピング・クローリング
まず、カワイイを作るためにはカワイイデータを拾ってくる必要がある。
カワイイヘアスタイルが大量に集まる場所はどこか ⇒ 美容院
美容院のデータがたくさん集まる場所はどこか ⇒ 某サロン検索サイト
ということで、今回はそこから情報を集めることに決定した。
※なお、スクレイピング・クローリングに関しては決まりや規定があるので、しっかり確認しましょう。
以下、今回参考にさせていただいたサイト・ページです。
[【Pythonクローラー入門】クローリング スクレイピング方法 総まとめ]
(https://www.sejuku.net/blog/69383#Beautiful_Soup)
[PythonでWebスクレイピングする時の知見をまとめておく]
(https://vaaaaaanquish.hatenablog.com/entry/2017/06/25/202924)
[[Python3]スクレイピングで、弊社の情報を取得してみた]
(https://qiita.com/hidetoshi_n_cograph/items/fca19b490fd0210e3633)
コード
全部は書ききれないので、一部ご紹介します。
※今回このテーマで進行するにあたり偉大な先輩の力を借りました。以下先輩の書いたコードのご紹介となります。…というか先輩がいなければ、カワイイは作れなかった!ありがとうセンパイ!
ヘアサロン(=美容院)一覧を取得
def text_to_soup(html_text):
try:
soup = BeautifulSoup(html_text, "lxml")
except:
soup = BeautifulSoup(html_text, "html5lib")
return soup
def is_pr_parent(salon):
"""PRサロンが含まれるので、その対応
"""
return 'class' in salon.parent.attrs and 'isPR' in salon.parent.attrs['class']
def scrape_salons(soup):
salon_list = {
'url': [],
'name': [],
'id': [],
}
salons = soup.select('#mainContents > ul > li.searchListCassette')
for salon in salons:
if is_pr_parent(salon):
continue
a = salon.find('a')
salon_url = a.attrs['href']
salon_name = a.text
salon_id = salon_url.replace(base_url, '').split('/')[1]
salon_list['url'].append(salon_url)
salon_list['name'].append(salon_name)
salon_list['id'].append(salon_id)
return salon_list
salon_df_list = []
for area_id in yamanote["area_id"]: ##山手線沿いに絞って取得したため
salon_list_url = f'{kanto_url}/{area_id}/salon'
res = requests.get(salon_list_url, headers={'user-agent': user_agent})
soup = text_to_soup(res.text)
page_regex = re.compile('\d+/(\d+)ページ')
page_text = soup.select_one('#mainContents > div.mT20.bgWhite > div.preListHead > div > p.pa.bottom0.right0').text
match = page_regex.search(page_text)
max_page = int(match.group(1))
salons = scrape_salons(res.text) ##設定したdefを実行
salon_df = pd.DataFrame(salons)
salon_df['area_id'] = area_id
salon_df_list.append(salon_df)
time.sleep(1)
for page in range(2, max_page + 1):
salon_page_url = f'{kanto_url}/{area_id}/salon/PN{page}.html?searchGender=ALL'
res = requests.get(salon_page_url, headers=ua_headers)
soup = text_to_soup(res.text)
salons = scrape_salons(soup) ##設定したdefを実行
salon_df = pd.DataFrame(salons)
salon_df['area_id'] = area_id
salon_df_list.append(salon_df)
time.sleep(1)
salon_df_all = pd.concat(salon_df_list, ignore_index=True)
ヘアスタイル一覧を取得
style_df_list = []
params = {
'typeSearch': 'submit',
'lengthCds': [f'HL{i:02d}' for i in [1,2,3,4,5,7,8]],
'condition': '絞り込む',
}
for salon_id in salon_df_all['id']: ##上で取得したヘアサロン一覧
wemens_style_url = f'{base_url}/{salon_id}/style'
res = requests.get(wemens_style_url, params=params, headers=ua_headers)
soup = text_to_soup(res.text)
page_regex = re.compile('\d+/(\d+)ページ')
page_elment = soup.select_one('#mainContents > div.mT20 > div.pH10.mT25.pr > p.pa.bottom0.right0')
if page_elment is None:
# メンズのみ、JavaScriptでは取れるがPythonではNoneになる
# どちらにしろメンズのみ対応は必要だったので、これで対処する
continue
page_text = page_elment.text
match = page_regex.search(page_text)
max_page = int(match.group(1))
styles = scrape_styles(soup)
style_df = pd.DataFrame(styles)
style_df['salon_id'] = salon_id
style_df_list.append(style_df)
time.sleep(1)
for page in range(2, max_page + 1):
style_page_url = f'{base_url}/{salon_id}/style/PN{page}.html'
res = requests.get(style_page_url, headers={'user-agent': user_agent})
soup = text_to_soup(res.text)
styles = scrape_styles(soup)
style_df = pd.DataFrame(styles)
style_df['salon_id'] = salon_id
style_df_list.append(style_df)
time.sleep(1)
学んだこと
- 2つめのコード内コメントアウトにもあるように、今回の主旨にそぐわないメンズのヘアスタイルは、ループの中でcontinueを使用しスキップさせた。目的に対して不要な不具合は、無視するのが得策。
- 適時処理を一時停止(time.sleep())することで、サーバーへの負荷を配慮。またこの[一時停止の時間]×[取得したいデータ数]を先に計算することで、スクレイピングにかかる処理時間を概算できる(結構時間はかかる)。
- 空のリストを作ってappend()でどんどん追加していくのは、python初級者でも使える技。先輩曰く、「別に難しいことをやろうとしなくていい、自分のできる基礎を組み合わせればそれなりのことはできる」。
まとめ
**カワイイは技術で作れる
**時代になったヨ。
髪型で悩んでる女子たち!今こそPython勉強しようぜ!!