読者です 読者をやめる 読者になる 読者になる

Web就活日記

愛と夢と人生について書きます

アダルトフィルタ実装に向けたA○女優リストの自動抽出 + α

programming

アダルトフィルタ実装に向けて

エロデータサイエンティストの@yutakikucです。
今日はSystemで使うアダルトフィルタの辞書データ作成を目的としていた事が、予想外な方向に突き進んでしまった事をお話します。


アダルトフィルタの良くある活用例としてはユーザーが投稿する内容に卑猥単語が含まれている場合は登録を弾くことです。アダルトフィルタの辞書データを作成/運用することは実は結構大変だったりします。なぜならば人目で1語ずつ卑猥か否かを判断したり、自分で判断できない際どい単語はGoogle先生に聞きながらだったり..当然新語が増えればアダルトフィルタ辞書の運用コストも膨れるからです。


そこで作成/運用のコストを大幅に下げるために辞書の更新を自動化したいと考えます。例えば新しいA○女優がデビューした場合、名前をアダルトフィルタの辞書に自動登録するようなものです。今日はそれをPythonで実装し、最終的にはLTSV形式のデータで出力します。


Labeled Tab-separated Values (LTSV) はてなブックマーク - Labeled Tab-separated Values (LTSV)

サンプルコード

A○女優一覧 - Wikipedia はてなブックマーク - A○女優一覧 - Wikipedia
データソースはWikipediaに乗っている日本のA○女優一覧になります。上のURL以下をスクレイピングする事で名前のリストを抽出します。今回のアダルトフィルタに必要となるデータ項目は名前ですが、折角なのでVital Statistics(Three Size)、Bra Sizeもついでのついでのついでに取得します。保存するLTSVのフォーマットは以下のように定義します。

Name:<Value> <Tab> Bust:<Value> <Tab> Waist:<Value> <Tab> Hip:<Value> <Tab> Bra:<Value>


以下がスクレイピングを行うPythonコードです。更新データが直ぐに欲しい場合はCron等で定期的に実行してください。※時間が無くて10分ぐらいで書いたコードなので汚いです。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# アダルトフィルタのデータ抽出

import sys,re,urllib,urllib2
f = open( 'av.txt', 'w' )
opener = urllib2.build_opener()
ua = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/534.51.22 (KHTML, like Gecko) Version/5.1.1 Safari/    534.51.22'
referer = 'http://www.yahoo.co.jp/'
opener.addheaders = [( 'User-Agent', ua ),( 'Referer', referer )]
urls = []
baseurls = [ "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%81%82%E8%A1%8C",
            "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%81%8B%E8%A1%8C",
            "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%81%95%E8%A1%8C",
            "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%81%9F%E8%A1%8C",
            "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%81%AA%E8%A1%8C",
            "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%81%AF%E8%A1%8C",
            "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%81%BE%E8%A1%8C",
            "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%82%84%E8%A1%8C",
            "http://ja.wikipedia.org/wiki/AV%E5%A5%B3%E5%84%AA%E4%B8%80%E8%A6%A7_%E3%82%89%E3%83%BB%E3%82%8F%E8%A1%8C" ]
for burl in baseurls:
   try:
      content = opener.open( burl ).read()
      if re.compile( r'<li><a href="(/wiki/.*?)">.*?</li>', re.M ).search( content ) is not None:
         tmp_urls = re.compile( r'<li><a href="(/wiki/.*?)".*?</li>', re.M ).findall( content )
         urls = urls + tmp_urls
   except Exception, e:
      print e
      continue

#dedupe
urls = sorted(set(urls), key=urls.index)

for url in urls:
   url = "http://ja.wikipedia.org" + url
   nodes = []
   vs = []
   p = re.compile(r'<.*?>')
   try: 
      content = opener.open( url ).read()
      if re.compile( r'<caption.*?>(.|\n)*?<big><b>(.*?)</b></big></caption>', re.M ).search( content ) is not None:
         tmp = re.compile( r'<caption.*?>(.|\n)*?<big><b>(.*?)</b></big></caption>', re.M ).search( content ).group(2)
         nodes.append( "Name:" + tmp.replace( ' ', '' ) )
      if re.compile( r'<th valign="top" style="font-weight:normal" nowrap="nowrap">.*?スリーサイズ.*?</th>', re.M ).search( content ) is not None:
         tmp  = re.compile( r'<th valign="top" style="font-weight:normal" nowrap="nowrap">.*?スリーサイズ.*?</th>(.|\n)*?<td nowrap="nowrap">(.*?)</td>', re.M ).search( content ).group(2)
         tmp = tmp.replace( ' ', '' )
         tmp = tmp.replace( 'cm', '' )
         vs = tmp.split( '-' )
         nodes.append( "Bust:" + p.sub( '', vs[0] ) )
         nodes.append( "Waist:" + p.sub( '', vs[1] ) )
         nodes.append( "Hip:" + p.sub( '', vs[2] ) )
      if re.compile( r'<th valign="top" style="font-weight:normal">.*?ブラのサイズ.*?</th>', re.M ).search( content ) is not None:
         tmp  = re.compile( r'<th valign="top" style="font-weight:normal">.*?ブラのサイズ.*?</th>(.|\n)*?<td>(.*?)</td>', re.M ).search( content ).group(2)
         nodes.append( "Bra:" + p.sub( '', tmp ) )
      if len(nodes) == 0:
         continue
      res = "\t".join( nodes ) + "\n"
      f.write( res )
   except Exception, e:
      print e
      continue
f.close()

抽出データ

上のPythonコードを実行すると以下のようなエロTSVデータ、もといLTSVデータを抽出する事ができます。抽出して気づいたのですが、元データが統一フォーマットで書かれていないので、後で抽出コードを書き換えて奇麗なデータを生成するようにします。やってはいけない事だと思いますが、githubにも抽出したデータを上げておきます。※データの利用は全て自己責任でお願いします。
それにしても皆さん凄いサイズですね...。どこが?って..、いや、アレがですよ、アレが。

Name:瑠川リナ	Bust:81	Waist:57	Hip:83	Bra:D65
Name:☆LUNA☆	Bust:83	Waist:57	Hip:82	Bra:D-65
Name:RUMIKA	Bust:82	Waist:60	Hip:85	Bra:C
Name:麗花	Bust:88	Waist:58	Hip:87	Bra:E
Name:Reo.	Bust:92	Waist:60	Hip:87	Bra:G
Name:麗奈	Bust:88	Waist:60	Hip:85	Bra:D
Name:れのん	Bust:85	Waist:56	Hip:82	Bra:E
Name:若瀬千夏	Bust:87	Waist:53	Hip:80
Name:若瀬七海	Bust:85	Waist:58	Hip:85	Bra:D-70
Name:若槻朱音	Bust:83	Waist:58	Hip:83	Bra:C-70
Name:若月樹里	Bust:86	Waist:58	Hip:83	Bra:E
Name:若槻尚美	Bust:88	Waist:60	Hip:86	Bra:F-65
Name:若菜	Bust:89	Waist:60	Hip:88	Bra:D-75
Name:若菜亜衣	Bust:85	Waist:57	Hip:86
Name:若菜すず	Bust:83	Waist:58	Hip:90	Bra:B-65
Name:若菜瀬奈	Bust:84	Waist:58	Hip:83
Name:若葉かおり	Bust:86	Waist:53	Hip:80	Bra:E
Name:若林樹里	Bust:83	Waist:58	Hip:83	Bra:D
Name:若林美保	Bust:90	Waist:58	Hip:88	Bra:F
Name:若宮莉那	Bust:92	Waist:59	Hip:86	Bra:I
Name:和久井由菜	Bust:84	Waist:56	Hip:85	Bra:C
Name:わさび	Bust:83	Waist:58	Hip:83	Bra:C
Name:渡瀬晶	Bust:88	Waist:60	Hip:86	Bra:D70
Name:渡瀬安奈	Bust:70	Waist:56	Hip:73	Bra:B-65
Name:渡瀬澪	Bust:100	Waist:60	Hip:87	Bra:G
Name:渡瀬ミク	Bust:80	Waist:55	Hip:80
Name:渡瀬りえ	Bust:87	Waist:63	Hip:86	Bra:D-75
Name:渡辺なつき	Bust:86	Waist:56	Hip:84	Bra:E
Name:渡辺美名子	Bust:80	Waist:60	Hip:85	Bra:C
Name:渡辺弓絵	Bust:90	Waist:62	Hip:92	Bra:Fカップ

githubのURLは以下です。
Data/ero.tsv at master · yutakikuchi/Data はてなブックマーク - Data/ero.tsv at master · yutakikuchi/Data

優秀なデータサイエンティストの皆さんへ

最初はアダルトフィルタ実装に向けたデータ準備を目的としていたのですが、途中から話が可笑しくなって何かの傾向分析ができそうなデータ抽出に成功しました。このデータから何か面白い方向性の研究に話を膨らませてくれる事を多いに期待します。

スポンサーリンク