収集したTwitterの対話データの前処理をする。

以前書いた記事でTwitterの対話データを集める方法を紹介しました。

www.pytry3g.com

紹介した方法を使えば膨大な数の対話データが簡単に手に入るというメリットがありますが、一方でTwitter特有の単語が多く含まれていてデータとして使えないなどのデメリットもあります。

今回はその収集したデータに前処理をかけて、データとして使えるものとそうでないものに分けたいと思います。

 

関連リンク

6.2. re — 正規表現操作 — Python 3.6.5 ドキュメント

正規表現を使う

pythonに標準で入っているreを使って対話データのフィルタリングをします。

まずはインポートします。

import re

URL

手に入れたツイートにはかなりの確率でURLが含まれています。URLを含んでいるツイートはそのURLに関することについての会話がされていると考えられるので、対話データとしては扱いません。

正規表現を使ってフィルタリングします。

# 正規表現パターンを正規表現オブジェクトにコンパイル
url_regexp = re.compile(r"https?://")

正規表現パターンが文字列を正しくフィルタリングしているか確認します。

フィルタリングにはreモジュールのsearch()を使います。

search()は文字列を走査し正規表現がマッチする最初の場所を探して、対応するmatchオブジェクトを返します。文字列が正規表現にマッチしない場合はNoneを返します。

URLを含まないテキスト

sample = "わしはこれもはじめてみたよ"
print(url_regexp.search(sample))
# None

正規表現にマッチする文字列がないのでNoneを返しています。

URL(https)を含むテキスト

sample = "わしはこれもはじめてみたよ https://t.co/lhlr3851cT"
print(url_regexp.search(sample))
# <_sre.SRE_Match object; span=(14, 22), match='https://'>

正規表現にマッチしたのでmatchオブジェクトが返ってきました。

URL(http)を含むテキスト

sample = "わしはこれもはじめてみたよ http://t.co/lhlr3851cT"
print(url_regexp.search(sample))
# <_sre.SRE_Match object; span=(14, 21), match='http://'>

httpsから始まるURLだけでなくhttpから始まるURLにもマッチしています。

使用例

使用例として下のような感じ。

def filtering(string):
    # マッチするものがあればFalse、なければTrueを返す
    if url_regexp.search(string):
        return False
    return True

for sentence in corpus:
    if filtering(sentence):
        # 何かの処理

おまけ

ちなみに以下の方法①と方法②は等価です。

sample = "わしはこれもはじめてみたよ https://t.co/lhlr3851cT"
# 方法①
url_regexp = re.compile(r"https?://")
result = url_regexp.search(sample)

# 方法②
result = re.search(r"https?://", sample)

ハッシュタグ

ハッシュタグは普段我々が会話をしているときには含まれることはないので、フィルタリングします。

# 正規表現パターンを正規表現オブジェクトにコンパイル
tag_regexp = re.compile(r"#(\w+)")

URLのフィルタリングと同じようにsearch()を使います。

サンプル1

sample = "#RTした人に絵のアドバイスする 画像をリプで……送っ(′ω'((⊂(`ω´∩)アチョイ"
print(tag_regexp.search(sample))
# <_sre.SRE_Match object; span=(0, 16), match='#RTした人に絵のアドバイスする'>

ハッシュタグを見つけています。

サンプル2

sample = "【報告】結婚します💍 #いやそんなまさか #企業研究です #頑張る #インスタではお馴染みの最近の私 #いかにも幸せそうな女の子っぽくレジに向かいました #虚しい #ひじがだざん https://t.co/7abyBJn6i0"
print(tag_regexp.search(sample))
# <_sre.SRE_Match object; span=(11, 20), match='#いやそんなまさか'>

ハッシュタグを見つけています。

サンプル3 - 顔文字

sample = "おは(#^.^#)"
print(tag_regexp.search(sample))
# None

この顔文字は正規表現にマッチしませんでした。

ユーザ名

Twitterで返信(リプライ)するときはツイートの先頭にユーザ名を含んでいます。このユーザ名を正規表現を使って空文字列に変換します。

# 正規表現パターンを正規表現オブジェクトにコンパイル
name_regexp = re.compile(r"@([A-Za-z0-9_]+) ")

正規表現にマッチした文字列を変換するにはsub()を使います。

re.sub(pattern, repl, string, count=0, flags=0)

文字列(string)を走査し正規表現(pattern)にマッチすると、マッチした文字列をreplに置き換えます。

サンプル1

sample = "いっぱいついーとします"
print(name_regexp.sub('', sample))
# いっぱいついーとします

マッチした文字列はないので変化なし。

サンプル2

sample = "@Yamada2ndSeason おいおいまだ俺とマッチングしてないぞ"
print(name_regexp.sub('', sample))
# おいおいまだ俺とマッチングしてないぞ

正規表現がユーザ名とマッチしたので空文字に変換してます。

サンプル3 - 顔文字

sample = "おは(@-.-@)"
print(name_regexp.sub("", sample))
# おは(@-.-@)

正規表現にマッチせず。

サンプル4 - 顔文字

sample = "おは(@_@)"
print(name_regexp.sub("", sample))
# おは(@_@)

正規表現にマッチせず。

サンプル5 - 顔文字

sample = "おは@(・●・)@"
print(name_regexp.sub("", sample))
# おは@(・●・)@

正規表現にマッチせず。

replaceを使う。

手に入れたツイートにはHTMLで使われる特殊文字が含まれています。主に顔文字など。pythonに組み込まれているreplaceを使って文字列を痴漢置換します。

sample = "おは(&gt;v&lt;)&amp;"
print(sample.replace("&gt;", ">").replace("&lt;", "<").replace("&amp;", "&"))
# おは(>v<)&

ソースコード

textify.pyをこの記事で紹介した前処理などを加えて以下のように書き換えてみた。

import MeCab
import argparse
import re
import sqlite3
import sys


# class to dump conversation as text from db.
class Dumper:
    def __init__(self, db):
        self.db = db
        self.url_regexp = re.compile(r"https?://")
        self.name_regexp = re.compile(r"@([A-Za-z0-9_]+) ")
        self.tag_regexp = re.compile(r"#(\w+)") #ハッシュタグを取り除く
        self.tagger = MeCab.Tagger("-Owakati") #分かち書き

    def dump(self):
        conn = sqlite3.connect(self.db)
        cursor = conn.cursor()

        cursor.execute(
            "select sid1, sid2, sid3 from conversation")
        for row in cursor:
            sid1, sid2, sid3 = row
            text1 = self.tweet_text(conn, sid1)
            text2 = self.tweet_text(conn, sid2)
            text3 = self.tweet_text(conn, sid3)
            if self.is_valid_conversation(text1, text2, text3):
                text1, text2, text3 = self.mapper(text1, text2, text3)
                print("{}\n{}\n{}".format(text1, text2, text3))
        conn.close()

    def is_valid_conversation(self, text1, text2, text3):
        if text1 is None or text2 is None or text3 is None:
            return False
        # URLを含むツイートは取り除く
        for text in [text1, text2, text3]:
            if re.search(self.url_regexp, text):
                return False
        # ハッシュタグを含むツイートは取り除く
        for text in [text1, text2, text3]:
            if re.search(self.tag_regexp, text):
                return False

        return True

    def mapper(self, text1, text2, text3):
        text1 = text1.replace("&gt;", ">").replace("&lt;", "<").replace("&amp;", "&")
        text2 = text2.replace("&gt;", ">").replace("&lt;", "<").replace("&amp;", "&")
        text3 = text3.replace("&gt;", ">").replace("&lt;", "<").replace("&amp;", "&")
        text1 = re.sub(self.name_regexp, '', text1)
        text2 = re.sub(self.name_regexp, '', text2)
        text3 = re.sub(self.name_regexp, '', text3)
        return text1, text2, text3

    @staticmethod
    def tweet_text(conn, sid):
        text = None
        cursor = conn.cursor()
        cursor.execute("select text from status where id = ?", [sid])
        for row in cursor:
            text = row[0]
        return text

    def tokenizer(self, text):
        return self.tagger.parse(text).strip()


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--db', type=str, required=True,
                        help='path to sqlite3 db')
    args = parser.parse_args()

    dumper = Dumper(args.db)
    dumper.dump()


if __name__ == '__main__':
    sys.exit(main())

おわりに

今回行った前処理だけではまだ不十分で使えない対話データはまだ残っています。(例えば、ユーザとボットとの会話とか)今後データをさらに考察し新しい前処理の方法を考えていきたいと考えています。