5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【顔認識+機械学習でカワイイは作れる】~某サロン検索サイトをスクレイピング・クローリング~

Posted at

きっかけ

あれはイタリアン・バルに飲みに行ったときのこと。
私はカウンターの中のスタッフともみちゃん が超美人だと気づいた。

私:「ねぇ、ともみちゃん、すごい美人じゃない!?」
男:「あーよく見ると確かに。でも俺的にはゆかちゃん(別スタッフ)の方がいいな。だってともみちゃんの短すぎじゃん?少年じゃないんだから…」
   
確かに、ともみちゃんは刈り上げベリーショト、一方で他スタッフのゆかちゃんは、ゆるふわミディアム(顔はどこにでもいそうなフツーの子だが…)。

ここで私は気づいたのだ。
男は顔の良し悪しなんてよく見ちゃいない、髪型から作り出される雰囲気を見てるんじゃないか…?
つまりだ、
ヘアスタイルさえそれっぽい状態を作り出せば、美人に勝てる可能性があるんじゃないか!?

**カワイイは作れるという言葉が脳裏をよぎった私、
技術を使って
カワイイを作る**ことを決意した。`

できたこと

先に、何が出来るようになったか説明する。

① カワイイを作りたい人の顔写真を入力する(既にカワイイけど)
加工0.jpg

② 顔検出+機械学習で、その人に最適なヘアスタイルをレコメンドする
加工1.jpg

③ その人+最適なヘアスタイルのイメージ画像を出力する
加工2.jpg
(画像処理してるから分かりにくいかもだけど、レコメンドした画像に元画像の顔部分を重ねてるよ)

⇒簡単に**カワイイを作る**ことができる!
(あとは現実に実行するのみ!!)

上記を行うために今回行ったのは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勉強しようぜ!!

5
7
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?