はじめに
ハッシュタグは、SNS などで広く利用される、話題やカテゴリを示すキーワードです。
本記事では、Unicode Standard で規定されているハッシュタグの仕様 を元に、ハッシュタグ機能の実装します。この仕様はハッシュタグの共通的な処理を促進する一方で、実装の自由度も一定程度確保しています。
ハッシュタグの仕様
ハッシュタグの仕様について簡単に説明します。
<Hashtag-Identifier> := <Start> <Continue>* (<Medial> <Continue>+)*
`:=` : 右辺が左辺の構文を定義
`<>` : 定義された構文
`*` : 直前の構文が 0 回以上繰り返されることを示す
`+` : 直前の構文が 1 回以上繰り返されることを示す
`()` : グループ化を表す
Start : ハッシュタグの開始文字
Continue : 続く文字
Medial : オプションで使用できる文字
ハッシュタグの仕様には、さまざまなバリエーションが書かれていますが、今回は以下のようにいします。
-
Start
文字は#
-
Continue
文字にはXID_Continue
プロパティにExtended_Pictographic
プロパティ、Emoji_Component
プロパティ、_
、-
、+
を加えたものを含め、#
を除外 -
Medial
は空
ハッシュタグを解析する際には、Start
文字の前に Continue
文字がない場合にのみハッシュタグとして認識することが推奨されます。例えば、foo#bar
ではハッシュタグは認識されませんが、foo #bar
や foo.#bar
ではハッシュタグとして認識されます。
また、比較やマッチングをする際には NFKC_CF
形式へ変換した後に行う必要があります。例えば、#MötleyCrüe
は #MÖTLEYCRÜE
と一致する必要があります。
ハッシュタグの仕様について詳しくは以下の記事で説明しています。
ハッシュタグ機能の実装
Post と Hashtag の 2 つのエンティティが多対多の関係であることを前提に実装します。
# Post
- ID: ユニークな識別子
- Content: 投稿のテキスト内容
- Timestamp: 投稿が作成された日時
# Hashtag
- ID: ユニークな識別子
- Tag: ハッシュタグテキスト(`#` から始まる)
# PostHashtag
- PostID: 関連する投稿のID (外部キー)
- HashtagID: 関連するハッシュタグのID (外部キー)
# Relationships
- Post は複数の Hashtag を持つことができる。
- Hashtag は複数の Post に関連付けられることができる。
+-----------+ +-----------+ +-------+
| Post | |PostHashtag| |Hashtag|
+-----------+ +-----------+ +-------+
| ID |<-------|| PostID ||------>| ID |
|-----------| | HashtagID | |-------|
| Content | +-----------+ | Tag |
| Timestamp | +-------+
+-----------+
POST の Content から Hashtag の Tag になる部分を抽出し、NFKC_CF 形式に変換する関数を実装します。Tag を NFKC_CF 形式に統一することで検索機能を強化することができます。
フロントエンド側の実装は以下の記事で行なっています。
必要なライブラリのインストール
Python の正規表現 regex
ライブラリをインストールします。標準ライブラリの re
は Unicode プロパティ(\p{XID_Continue}
など)を使うことができないためです。
pip3 install regex
ハッシュタグ抽出と正規化の実装
以下のコードは、POST の Content から Hashtag を抽出し、NFKC_CF 形式に変換する関数を実装します。
-
hashtag.py
ファイルを作成 -
NFKC_CF
関数: 文字列を NFKC 形式に正規化し、casefold する。 -
extract_hashtags
関数: テキストをトークンに分割し、ハッシュタグ部分を NFKC_CF 形式に正規化する。
# 以下の仕様で実装
# - <Hashtag-Identifier> := <Start> <Continue>*
# - オプションの (<Medial> <Continue>+)* の部分は実装しない
# - Start の前に Continue がない場合にのみハッシュタグとして認識
# - ハッシュタグは NFKC_CF 形式に正規化
import regex as re
import unicodedata
def NFKC_CF(text):
return unicodedata.normalize('NFKC', text).casefold()
def extract_hashtags(text):
tokens = []
current_token = ''
in_hashtag = False
def add_token(value):
tokens.append(value)
for char in text:
if re.match(r'#', char):
if in_hashtag:
add_token(NFKC_CF(current_token))
in_hashtag = False
current_token = char
else:
in_hashtag = True
current_token = char
elif in_hashtag:
if re.match(r'\p{XID_Continue}|\p{Extended_Pictographic}|\p{Emoji_Component}|[_+-]', char):
current_token += char
else:
add_token(NFKC_CF(current_token))
in_hashtag = False
# 最後の部分を処理
if current_token:
add_token(NFKC_CF(current_token))
return tokens
# 動作確認
content = "#MötleyCrüe #MÖTLEYCRÜE foo #bar#foo #b🦰ar foo.#bar"
hashtags = extract_hashtags(content)
for hashtag in hashtags:
print(hashtag)
動作確認
python3 hashtag.py
#mötleycrüe
#mötleycrüe
#bar
#foo
#b🦰ar
#bar
おわりに
本記事では、Unicode Standard で規定されているハッシュタグの仕様を元に、ハッシュタグ機能を実装しました。
フロントエンドからタグ検索を行う場合は、上記で実装した NFKC_CF
関数で検索ワードを正規化した上で、データベースに問い合わせすることで検索機能を強化することができます。