我、日本人顔暗記開始了

（念の為：タイトルは偽中国語で、意味は「私は日本人の顔を暗記し始めた」）

## 出来上がったやつ



**Anki用Deckはこちらで https://github.com/masakichi/japanese_celebrity/releases 入手できる。（japanese_celebrity.apkg)**

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88197/212cf8c5-f708-aada-3eae-98b5066046c7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/88197/87d1d1c2-7432-1563-b9dc-65b9a65dfe8e.png)

> 顔を認識し、個人を識別することは、コンピュータでもつい最近になって実現した技術であり、大変な計算容量が必要な作業だが、人間はこれを子供のうちにマスタしてしまう。もっと も、年齢が上がってくると、若いアイドルの見分けがつかない、という人が増えてくるが、これはそのとおり、思考力が衰えているからだ。 ――森博嗣


## 動機

前日、ネットフリックスで「メモリーゲーム」というドキュメンタリーを観ました。その中にいろんな記憶の達人が登場して、彼らは毎年記憶の世界大会を参加するようです。その大会で皆いくつかの試験を受けなければならない、そのいくつかの試験の一つは「顔認識」です。規則はごく簡単で、選手たちは何十人の顔と名前を提供され、定められた時間の中に暗記し、その後指定された顔の名前を答える。私は選手たちの出来事に驚いた。なぜあんな僅かな時間で沢山の人の顔を暗記できるのかなと。

そのドキュメンタリーによると彼らは生まれた驚異の記憶力を持つわけではない。彼らは自分自身でなんとなくいろんな訓練を経験し、自分なりの記憶方法を身につけたんだ。そして、そのドキュメンタリーに一瞬だけ、あるアプリを気がついた、これはAnkiじゃん、私が日本語単語を暗記するアプリじゃん。そうか、私はそういうこともできるかもしれない、外国語単語を暗記するように私は人の顔を暗記する決意をした。

## 問題

### まずはどんな人たちの顔を暗記するつもり？
私は日本の芸能人の顔を暗記することをした。理由は概ねに２つがある
1. 入手できる可能性が高い。なぜなら芸能人の顔はきっとネットで溢れていると想像できるでしょ。
2. 名前の読み方の自由度が高い。音読みと訓読み両方ある日本語漢字は中国人の私にとっては難しい、だから勉強必要がある。日本人の名前を暗記するのはいろんな漢字の読み方を学べることができると想定する。

### 芸能人たちの資料どう入手するのかい？
実は簡単で、グーグルで「芸能人　データベース」というキーワードを入力し、検索結果の一番目は結構です。残るのはどう采集するのだ。

### Ankiにどう導入するのかい？
これも簡単。前にAnkiへ複数の単語を一括登録することはあった。csvファイルを用意してAnki内蔵された導入機能を使えばいいのだ。詳しくは http://rs.luminousspice.com/bulk-import-media-files-into-anki/ この文章を参照しては大丈夫と思います。

## 実装

### 環境

- Arch Linux 
- Python 3.7.3
- Scrapy 1.6.0
- Go go1.12.7 linux/amd64

詳しく手順は https://github.com/masakichi/japanese_celebrity こちらへ

### Scrapy

```python:pasonica.py
# -*- coding: utf-8 -*-
import scrapy


class PasonicaSpider(scrapy.Spider):

    name = 'pasonica'
    allowed_domains = ['www.pasonica.com']
    download_delay = 1

    def start_requests(self):
        for year in range(2000, 1910, -10):
            for gender in ('女', '男'):
                url = 'https://www.pasonica.com/{}性芸能人-{}年代誕生/'.format(gender, year)
                yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        for div in response.css('.thumb_sq2'):
            for link in div.css('a'):
                profile_url = link.attrib.get('href')
                yield response.follow(url=profile_url, callback=self.parse_celebrity)

    def parse_celebrity(self, response):
        name = response.css('h2::text').get()
        image = response.css('.j-blog-post--header + div img').attrib.get('src', '')
        intro = ''.join(response.css('.j-module.n.j-text p::text').getall())
        self.log('GET {}'.format(name))
        return dict(name=name, image=image, intro=intro)
```

Scrapyのおかげで30行程度のコードが全てのデータが入手できる。素晴らしいです。
実行方法： `scrapy crawl pasonica -o celebrity.json` 完成できれば `celebrity.json` が現れるはず。


### データの整理

```python:clean_data.py
# coding: utf-8

import json
import re
import csv

RE_YOMI = re.compile('■(?P<name>.*)（(?P<yomi>.*)）') 
RE_NAME = re.compile('■(?P<name>.*)') 

def clean_intro(i):
    start_flag = '■'
    end_flag = '関連項目'
    start_idx = i.find(start_flag)
    if start_idx:
        i = i[start_idx:]
        end_idx = i.find(end_flag)
        if end_idx:
            i = i[:end_idx]
    return i.strip()

def get_name(i):
    m = RE_NAME.match(i)
    return m.group('name') if m else ''

def get_yomi(i):
    m = RE_YOMI.match(i)
    return m.group('yomi') if m else ''

def text_to_html(i):
    return i.replace('\n\n', '\n').replace('\n', '</br>')

def main():
    result = []
    with open('celebrity.json') as f:
        celebrities = json.load(f)
        for c in celebrities:
            intro = clean_intro(c['intro'])
            name = c['name']
            if not c['name']:
                name = get_name(intro)
            yomi = get_yomi(intro)
            if yomi or name:
                print(name, yomi)
                result.append(dict(name=name, yomi=yomi, intro=intro, image=c['image']))

    with open('celebrity_clean.json', 'w') as f:
        json.dump(result, f)

    with open('celebrity.csv', 'w') as f:
        csv_writer = csv.writer(f)
        for c in result:
            name = c['name']
            yomi = c['yomi']
            intro = text_to_html(c['intro'])
            row = name, yomi, '<img src="%s_%s.jpg" />' % (name, yomi), intro
            csv_writer.writerow(row)

if __name__ == '__main__':
    main()
```

前の `celebrity.json` を整理し、`celebrity_clean.json`(顔写真取得用)と`celebrity.csv`（Anki導入用）を生成する。

### 顔写真の取得

```golang:downlaod_images.go
package main

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"path/filepath"
	"sync"
)

type Celebrity struct {
	Name  string `json:"name"`
	Yomi  string `json:"yomi"`
	Image string `json:"image"`
}

var downloadDestFolder = "images"

func download(url string, filename string, w *sync.WaitGroup) {
	defer w.Done()
	res, err := http.Get(url)
	if err != nil {
		log.Printf("http.Get -> %v", err)
		return
	}
	data, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Printf("ioutil.ReadAll -> %s", err.Error())
		return
	}
	defer res.Body.Close()
	if err = ioutil.WriteFile(downloadDestFolder+string(filepath.Separator)+filename, data, 0644); err != nil {
		log.Println("Error Saving:", filename, err)
	} else {
		log.Println("Saved:", filename)
	}
}

func main() {
	bytes, err := ioutil.ReadFile("celebrity_clean.json")
	if err != nil {
		log.Fatal(err)
	}
	var celebrities []Celebrity
	if err := json.Unmarshal(bytes, &celebrities); err != nil {
		log.Fatal(err)
	}
	var w sync.WaitGroup
	for idx, c := range celebrities {
		if idx&10 == 0 {
			w.Wait()
		}
		w.Add(1)
		go download(c.Image, c.Name+"_"+c.Yomi+".jpg", &w)
	}
	w.Wait()
}
```
Go Routineを利用して一分未満で2775枚顔写真をダウンロードできった！取れた写真をAnkiのミーディアムフォルダーに転送して、前に生成された`celebrity.csv`をAnkiに導入すればいい。


## 感想

Ankiに導入したあと、私は20枚写真と名前を暗記した。ずいぶん時間がかかる、単語よりはるかに難しい。毎枚平均五回繰り返し必要がある。まあ、慣れないかもしれない。一週間後また振り返って見ようかなー

初めて投稿で恐縮です。

以上です。