NGワード判定システム構築（ウーマンコミュニケーション）

# ウーマンコミュニケーションとは
ウーマンコミュニケーションとは日常生活に隠れた卑猥な言葉を見つけ出し、指摘する言葉狩りゲームです。私はゲーム実況を見て、このゲームを知ったのですが、ただのクソゲーに見えて意外に深い内容なので、ぜひやってみてください。

![u-mancomyu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3635555/adc7bb7b-5352-1207-5b46-de433232551b.png)
# プログラムについて
ウーマンコミュニケーションに因んで、今回はNGワードを判定するプログラムをPythonで作りたいと思います。実行環境はGoogle Colaboratoryです。雑な説明になりますが最後まで読んでくれると嬉しいです。
#### 必要ライブラリ等のインストール
```
!pip install requests beautifulsoup4
!pip install pykakasi
```
requests beautifulsoup4はWikipediaから隠語データをスクレイピングする用。pykakasiは漢字とかを変換する用。pykakasiはサービス終了するかもしれないので他の手段も考えた方が良いですね。
#### コード
```python
import re
import requests
from bs4 import BeautifulSoup
from pykakasi import kakasi

#　エラーを定義
class ContainNgWordError(Exception):
    def __init__(self, message):
        self.message = message

# NGワードシステム構築
class NgWordSystem:
    def __init__(self):
        self.processed_ng_words = self.process_ng_words()
        self.get_password()
 
    def get_ng_words(self):
        wikipedia_url = "https://ja.wikipedia.org/wiki/%E6%80%A7%E9%A2%A8%E4%BF%97%E7%94%A8%E8%AA%9E%E4%B8%80%E8%A6%A7" # このサイトからNGワードを抽出
        ng_words = self.get_wikipedia_terms(wikipedia_url)
        ng_words = ng_words[1:164] # ここまでのワードを使用

        return ng_words

    def process_ng_words(self):
        # NGワードをアルファベット小文字に変換
        self.kakasi = kakasi()
        self.kakasi.setMode('H', 'a')
        self.kakasi.setMode('K', 'a')
        self.kakasi.setMode('J', 'a')

        conv = self.kakasi.getConverter()
        new_ng_words = []
        for word in self.get_ng_words():
            new_ng_words.append(conv.do(word).lower()) 

        # 新しいリストを作成して、括弧で分割して格納
        processed_ng_words = []
        for word in new_ng_words:
            parts = list(filter(None, re.split('[（）、()]', word)))
            processed_ng_words.extend(parts)
        add_ng_list = ['bdsm', 'smsadizumu', '4545', "0721", '1919', '810']   # NGワードを追加
        processed_ng_words.extend(add_ng_list)
        processed_ng_words = list(set(processed_ng_words))

        return processed_ng_words

    def get_wikipedia_terms(self, url):
        response = requests.get(url)
        soup = BeautifulSoup(response.text, 'html.parser')

        terms = []
        # 用語一覧が含まれている部分の特定
        term_section = soup.find('div', {'id': 'mw-content-text'})
        # リストアイテムを取得して用語を抽出
        for li in term_section.find_all('li'):
            term = li.get_text(strip=True)
            terms.append(term)

        return terms    
            
    def get_password(self):
        print("8文字以下でパスワードを設定してください（アルファベット小文字と数字のみ使用可能）")
        self.password = input()
        self.validate_password()

    def validate_password(self):
        if not re.match("^[a-z0-9]+$", self.password):
            print("パスワードにはアルファベット小文字と数字以外の文字は使用できません。")
            self.get_password()  # バリデーションエラーがある場合は再度パスワード入力を要求
        elif len(self.password) > 8:
            print("パスワードは８文字以下でなければなりません。")
            self.get_password()  # バリデーションエラーがある場合は再度パスワード入力を要求
        else:
            for ng_word in self.processed_ng_words:
                if ng_word in self.password:
                    raise ContainNGwordError("使用できないワードが入っています。")
            
            print("パスワードが受け付けられました。")

    def detect(self, text):
        # NGワードがあるか探したいテキストをアルファベット小文字に変換
        self.identify() # 設定したパスワードの入力を要求
        self.text = text
        self.text = self.text.replace("、", "")
        self.text = self.text.replace("。", "")
        self.text = self.text.replace("！", "")
        self.text = self.text.replace("？", "")

        self.kakasi = kakasi()
        self.kakasi.setMode('H', 'a')
        self.kakasi.setMode('K', 'a')
        self.kakasi.setMode('J', 'a')

        conv = self.kakasi.getConverter()

        # NGワードを探す
        for i in self.processed_ng_words:
            if i in conv.do(self.text) and i != "":
                  print('\033[35m'+f'{i.upper()}'+'\033[0m')   

    def identify(self):
        # 最初に設定したパスワードと一致するか確認
        print("パスワードを入力してください。")
        self.identical_password = input()    
        if self.password == self.identical_password:
            print("パスワードが一致しました。---NGワード検知システム始動---")
        else:
            print("パスワードが間違っています。")
            self.identify()    
```
コードのダウンロードは[github](https://github.com/katsuki0303/projectA)からどうぞ。
# シミュレーション
では実際に実行してみます。
```python
# クラスのインスタンス生成時に入力を要求
NgWordSystem = NgWordSystem()
```
クラスを呼び出した時にパスワードを設定するようにプログラムしました。パスワードを正しく設定すると以下のようにパスワードが受け付けられます。（今回はパスワードを「ka」と設定しました。）
![shidou.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3635555/45a6af04-f55a-07d4-7a1c-8aa3794a5d92.png)

パスワードにも卑猥なワードを忍ばせる人もいるかもしれません。その場合、オリジナルに作成したエラーを表示するようにしました。以下では「ka69」と設定しましたが、「69」がNGワードに該当したらしいです。
![エラー例.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3635555/7ecf4283-b78d-ae55-78a4-4fbcc9217ede.png)

次のコードで調べたいテキストをNgWordSystemのメソッドdetectの引数に渡して実行すると、最初に設定したパスワードの入力が求められパスワードを入力すると、どのNGワードが検知されたか表示されます。
```python
NgWordSystem.detect("我慢、このカフェラテは勉強のご褒美にしよう。")
```
![実行結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3635555/e759b9f8-5dee-aecb-a662-34806905aeb4.png)
上手くいきましたね……
# コードの解説・補足
ここでは、簡単にコードの解説や補足をしていく。コードの詳細な処理は、適宜に参考サイトを参照してください。

まず、クラスインスタンスの生成時に次のコードが動く。
```python
def __init__(self):
    self.processed_ng_words = self.process_ng_words()
    self.get_password()
```
具体的に言うと、（１）NGワードのデータの取得から加工、（２）パスワードの設定の要求が行われる。
### (1)NGワードのデータの取得から加工
```python
def get_ng_words(self):
    wikipedia_url = "https://ja.wikipedia.org/wiki/%E6%80%A7%E9%A2%A8%E4%BF%97%E7%94%A8%E8%AA%9E%E4%B8%80%E8%A6%A7" # このサイトからNGワードを抽出
    ng_words = self.get_wikipedia_terms(wikipedia_url)
    ng_words = ng_words[1:164] # ここまでのワードを使用

    return ng_words

def process_ng_words(self):
    # NGワードをアルファベット小文字に変換
    self.kakasi = kakasi()
    self.kakasi.setMode('H', 'a')
    self.kakasi.setMode('K', 'a')
    self.kakasi.setMode('J', 'a')

    conv = self.kakasi.getConverter()
    new_ng_words = []
    for word in self.get_ng_words():
        new_ng_words.append(conv.do(word).lower()) 

    # 新しいリストを作成して、括弧で分割して格納
    processed_ng_words = []
    for word in new_ng_words:
        parts = list(filter(None, re.split('[（）、()]', word)))
        processed_ng_words.extend(parts)
    add_ng_list = ['bdsm', 'smsadizumu', '4545', "0721", '1919', '810']   # NGワードを追加
    processed_ng_words.extend(add_ng_list)
    processed_ng_words = list(set(processed_ng_words))

    return processed_ng_words

def get_wikipedia_terms(self, url):
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')

    terms = []
    # 用語一覧が含まれている部分の特定
    term_section = soup.find('div', {'id': 'mw-content-text'})
    # リストアイテムを取得して用語を抽出
    for li in term_section.find_all('li'):
        term = li.get_text(strip=True)
        terms.append(term)

    return terms
```
今回のプログラムを作る上で必要となるNGワードのデータについては[Wikipediaの性風俗用語一覧](https://ja.wikipedia.org/wiki/%E6%80%A7%E9%A2%A8%E4%BF%97%E7%94%A8%E8%AA%9E%E4%B8%80%E8%A6%A7)というページから取り出し、get_wikipedia_termsとget_ng_wordsというメソッドでリストに保管した。そして、そのリストの各要素（NGワード）をprocess_ng_wordsというメソッドでアルファベット小文字に変換した。アルファベット小文字に変換したのはNGワードが含まれているか探す際に同一の文字で比較するためである。コードの解読は参考サイトを参照されたい。
##### processed_ng_words(List)
```
 ['oppai','burusera','yariman','chinchin ','koumon',......., 'fera']
 ```
##### コードを書く際に参考にしたサイト
[・pykakashiについて](https://tomoblog.net/programing/python/jp_convert_rome/)
[・文字列を複数の基準で分割する](https://qiita.com/bassie/items/483579eca617fb466476)
### (2)パスワードの設定の要求
```python
def get_password(self):
    print("8文字以下でパスワードを設定してください（アルファベット小文字と数字のみ使用可能）")
    self.password = input()
    self.validate_password()

def validate_password(self):
    if not re.match("^[a-z0-9]+$", self.password):
        print("パスワードにはアルファベット小文字と数字以外の文字は使用できません。")
        self.get_password()  # バリデーションエラーがある場合は再度パスワード入力を要求
    elif len(self.password) > 8:
        print("パスワードは８文字以下でなければなりません。")
        self.get_password()  # バリデーションエラーがある場合は再度パスワード入力を要求
    else:
        for ng_word in self.processed_ng_words:
            if ng_word in self.password:
                raise ContainNGwordError("使用できないワードが入っています。")
        
        print("パスワードが受け付けられました。")
```
パスワードの受取、評価についてはコードの通り。
```python
#　エラーを定義
class ContainNgWordError(Exception):
    def __init__(self, message):
        self.message = message
```
エラーの定義に関しては、Exceptionクラス（親クラス）を継承して作ることができる。
##### コードを書く際に参考にしたサイト
[・クラスの書き方について](https://qiita.com/nozomiprogram/items/ac7d480be154564e980c#:~:text=%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%AE%E5%9F%BA%E6%9C%AC%E3%81%AE%E6%9B%B8%E3%81%8D%E6%96%B9%201%20%E4%BE%8B%20%E3%82%AF%E3%83%A9%E3%82%B9%E5%90%8D%E3%80%80%E5%8B%95%E7%89%A9%20...%202%20Python%E3%81%AE%E5%9E%8B%20%23%E3%82%AF%E3%83%A9%E3%82%B9%E5%AE%9A%E7%BE%A9,%E3%83%A1%E3%82%BE%E3%83%83%E3%83%88%28self%29%3A%23%E3%83%A1%E3%82%BE%E3%83%83%E3%83%88%E3%82%92%E5%AE%9A%E7%BE%A9%20print%28%27%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%27%29%20%23%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%92%E4%BD%BF%E3%81%86%20%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E5%90%8D%3D%E3%82%AF%E3%83%A9%E3%82%B9%E5%90%8D%28%29%23%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%82%92%E4%BD%9C%E3%82%8B%20%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E5%90%8D.%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%28%29%23%E3%83%A1%E3%82%BE%E3%83%83%E3%83%88%E3%82%92%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%99%203%20%E4%BE%8B%E3%82%92%E5%BD%93%E3%81%A6%E3%81%AF%E3%82%81%E3%82%8B%E3%81%A8%20)
[・文字列の検索（NGワードの発見）](https://www.sejuku.net/blog/52207)
### （３）テキストからNGワードの検知
```python
def detect(self, text):
    # NGワードがあるか探したいテキストをアルファベット小文字に変換
    self.identify() # 設定したパスワードの入力を要求
    self.text = text
    self.text = self.text.replace("、", "")
    self.text = self.text.replace("。", "")
    self.text = self.text.replace("！", "")
    self.text = self.text.replace("？", "")
    
    self.kakasi = kakasi()
    self.kakasi.setMode('H', 'a')
    self.kakasi.setMode('K', 'a')
    self.kakasi.setMode('J', 'a')

    conv = self.kakasi.getConverter()
    
    # NGワードを探す
    for i in self.processed_ng_words:
        if i in conv.do(self.text) and i != "":
              print('\033[35m'+f'{i.upper()}'+'\033[0m')  
```
シミュレーションの例でいうと、
"我慢、このカフェラテは勉強のご褒美にしよう。"
　　　　　　　　↓
"我慢このカフェラテは勉強のご褒美にしよう"
　　　　　　　　↓
"gamankonokaferatehabenkyounogohoubinishiyou"   
と加工され、この中にNGワード[manko, fera]が検知され、NGワードをアルファベット大文字にし、マゼンタ色でprint文で表示する。
##### コードを書く際に参考にしたサイト
[・print文に色を付ける](https://www.nomuramath.com/kv8wr0mp/)
# 最後に
まだまだ機能を追加できそうですが、コード書いていて恥ずかしいなと感じたのでやめました。最後までこんなくだらない記事を読んでくれたあなたが大好きです。それではまた。