youtube-dlのextractor書き方講座 4/4

# 遅くなってごめんなさい
卒論やら何やらで忙しく、書こう書こうと思ってたらもう2月も半分を過ぎました。ウソだろお前

# 前回の続き
また見てない方は[前回はここから](https://qiita.com/nao20010128nao/items/43d82cdd0278ae25f725)どうぞ。
前回は最低限以上の情報の取得を書きました。
今回は、youtube-dlのコーディング規則を簡単にやりたいと思います。

<!-- ## 最終目標
[これを作ります。](https://github.com/nao20010128nao/ytdl-patched/blob/8648116f743d8ea9f58db7c2daf726903232ccda/youtube_dl/extractor/whowatch.py) -->

# コーディング規則って何があるの?
具体的には、[README.mdのyoutube-dl coding conventionsのセクション](https://github.com/ytdl-org/youtube-dl#youtube-dl-coding-conventions)に載っています。

## Mandatory and optional metafields
`_real_extract`で返す情報には次のキーと、対応する情報が必要だよ。という意味です。

- `id` (動画ID)
- `title` (動画タイトル)
- `url` または `formats` (今回のシリーズは後者を使用)

逆に、それ以外は*全て*任意です。ダウンロード自体はできます。

## Provide fallbacks
ウェブサイトには仕様変更が付きものです。これに備えるためにも、いくつかの方法で同時に情報を取得し、仕様変更にある程度耐えられるようにしなければなりません。
例えば、OpenGraphやTwitter Card向けの情報から取り出すのを最終手段として用意しておくのが良いと思います。

```python
# JSONをダウンロード
meta = self._download_json(url, video_id)

# これはNG ('meta'キーがJSONから消えたらKeyErrorになる)
title = meta['title']

# これはOK ('meta'キーがJSONから消えてもOpenGraphのog:titleから取得できる)
title = meta.get('title') or self._og_search_title(webpage)

# Twitter Card向けのmetaタグを先に検索し、出来なければOpenGraphのog:titleを探す
title = self._html_search_meta(
        ('twitter:title', 'og:title'),
        webpage)
```

## Regular expressions
これは[第1巻](https://qiita.com/nao20010128nao/items/2dea7f263d0fd5ca29b9#%E3%82%B9%E3%83%86%E3%83%83%E3%83%974-youtube-dl%E3%81%AB%E5%AE%9F%E8%A3%85%E3%81%99%E3%82%8B)で簡単に扱いました。

まず、大前提として、不要なグループはキャプチャしないようにします。
ここでは、XMLのattributeという属性を取り出す正規表現を考えます。

```python
# NG ("attribute"はどう見ても定数なのでキャプチャする意味はない)
r'(attribute)="(.+?)"'

# 場合によってはOK
# 不特定多数のXMLやHTMLに対して使うのであれば、問題無い
r'attribute=([\'"]?)(.+?)\1'

# OK
# ウェブサイトが引用符や二重引用符でしか囲まないと分かっているのであれば、そのグループは削除するべき
r'attribute="(.+?)"'
r"attribute='(.+?)'"
```

また、正規表現は出来る限り柔軟にするべきです。
ここで、[FC2動画のテストケースにあったこのURL](https://github.com/ytdl-org/youtube-dl/blob/master/youtube_dl/extractor/fc2.py#L25)を考えます。

FC2動画では、言語や設定などを考慮すると、次のようなURLが理論上存在し得ます。

```bash
# 最短
http://video.fc2.com/content/20121103kUan1KHs

# アダルト
http://video.fc2.com/a/content/20121103kUan1KHs

# 言語付き
http://video.fc2.com/en/content/20121103kUan1KHs
http://video.fc2.com/ja/content/20121103kUan1KHs

# 言語付き+アダルト
http://video.fc2.com/en/a/content/20121103kUan1KHs
http://video.fc2.com/ja/a/content/20121103kUan1KHs
```

さて、このURLに全てマッチし、同時にIDを正しく取り出せる正規表現はどうなるでしょうか?

愚直に考えれば、次のような正規表現になるでしょう。

```python
r'^https?://video\.fc2\.com/(?:a/)?(?:(?:en|ja)/)?content/(?P<id>[^/]+)'
```

でも、例えば上記`言語付き+アダルト`のURLの一つ http://video.fc2.com/ja/a/content/20121103kUan1KHs の`ja`と`a`を入れ替えても問題なく動画を見ることが出来ます。

では、こうでしょうか?

```python
r'^https?://video\.fc2\.com/(?:a/)?(?:(?:en|ja)/)?(?:a/)?content/(?P<id>[^/]+)'
```

すごく複雑になってしまいました。

ここで、何が本当に必要とされているかを考えてみましょう。

最初の`https://video.fc2.com/`は必須として、`/content/`以降のIDパートも必要です。それだけを取り出そうとすると、次のような正規表現になります。

```python
r'^https?://video\.fc2\.com/(?:[^/]+/)*content/(?P<id>[^/]+)'
```
これであれば、先程の様々なパターンのURLに対応することができます。

このように、正規表現は可能な限り柔軟に作ることが重要です。

## Long lines policy
1行当たりの長さはできるだけ80文字以内に収める、というものです。
ただし、強制ではないので、長い文字列を分割してまでやる必要はありません。

## Inline values
一回しか使わない定数は、特に理由がない限り[^longstr]はコード内に入れるようにします。
頻繁に使うものだけ定数として取り出しておくのが最も良いです。

## Collapse fallbacks
`Provide fallbacks`で扱った次のコードを考えます。

```python
# OK: 綺麗なコード
title = self._html_search_meta(
        ('twitter:title', 'og:title'), # このtupleを変えるだけ
        webpage)

# NG: 同じようなコードを複数行書く
title = (
    self._html_search_meta('twitter:title', webpage) or 
    self._og_search_description('title', webpage))
    # ...行を増やす
```

上記のコードは実質的に同じですが、取り出すmetaタグの数が増減するに従って、OKのコードはどのmetaタグを取り出すか一目で分かりますが、NGの方ではあまりに多くなると見づらくなることが予想されます。
よって、行数が比較的少なくなるOKのコードにするべきです。

## Trailing parentheses
閉じ括弧だけの行が(文字列リテラルの一部である場合を除いて)無いようにし、前の行の末尾にまとめます。

```python
# OK: 閉じ括弧だけの行が無い
title = (
    self._html_search_meta('twitter:title', webpage) or 
    self._og_search_description('title', webpage))

# NG: 閉じ括弧だけの行がある
title = (
    self._html_search_meta('twitter:title', webpage) or 
    self._og_search_description('title', webpage)
)
```

## Use convenience conversion and parsing functions
`youtube_dl/utils.py`には便利なメソッドがたくさんあります。extractorからなら`from ..utils import hogehoge`でimportできます。

# おわりに
今回は、youtube-dlのコーディング規則を簡単にやりました。
これで、多分最低限はやったと思うので、extractorを書いてみたい方は参考にしてくれれば幸いです。
もし何かあったら追加があるかもしれないですが。

最後までご覧いただきありがとうございました。


[^longstr]: 例えば、`'''aa'''`を用いて複数行かつ長い文字列を作成するなど、やむを得ない場合は除く