ノート/テキストマイニング

訪問者数 1538      最終更新 2012-03-01 (木) 11:19:41

PythonのUnicode/UTF-8文字列の扱い --- 2012/02/29

UnicodeとUTF-8

Unicodeは、 WikipediaのUnicode によると

世界中の多くのコンピュータ上の文字列を一貫した方法で符号化し、表現し、
扱うためのコンピュータ業界の標準

で、

NT系のMicrosoft Windows (Windows)、Mac OS X、LinuxやJavaなどで利用されて

おり、

元々16ビットの文字集合で全ての文字の網羅を目指して開発されたが、
符号位置が圧倒的に足りなくなり、Unicode 2.0以降では、
21ビットの文字集合として規定されることとなった。

である。

Unicodeの文字を表現する場合、"U+"にその文字の符号位置を表す16進数の値を続ける。

また、

日本の文字については当初より JIS X 0201、JIS X 0208 と JIS X 0212 を、
Unicode 3.1 では JIS X 0213 の内容を収録している。

具体的に我々が使いそうなのは、

U+0000-007F  ラテン文字(ASCII文字)
と
U+3000-303F  漢字上の記号・句読点
U+3040-309F  ひらがな
U+30A0-30FF  かたかな
U+4E00-9FFF  漢字(正確にはCJK統合漢字、CJKは中国・日本・韓国)

と思われる。あと、CJK互換文字とかCJK統合漢字拡張B,C,Dとかあるが、無視してよいのか?

UTF-8は、上記のUnicodeを「符号化」する方法の1つである。WikipediaのUTF-8によると、

ASCII文字と互換性を持たせるために、ASCIIと同じ部分は1バイト、その他の部分
を2〜6バイトで符号化する。4バイトのシーケンスでは21bit(0x1FFFFF)まで
表現することができるが、(後略)

であり、符号化の規則は、

バイトの先頭が0 ⇒ 1バイトコード(ASCII対応のU+00-7Fの部分)
バイトの先頭が10 ⇒ 2〜6バイト時の2〜6バイト目(非1バイト目)を表す
バイトの先頭が110 ⇒ 2バイトコードの1バイト目
  2バイトコードはU+0080-07FFを収容する。7FF=11ビットをyyyyxxxxxxxとすると
   1バイト目 110yyyyx、 2バイト目 10xxxxxx として収める
バイトの先頭が1110 ⇒ 3バイトコードの1バイト目
  3バイトコードはU+08000-FFFFを収容する。FFFF=16ビットをyyyyyxxxxxxxxxxxとすると
   1バイト目 1110yyyy、 2バイト目 10yxxxxx、 3バイト目 10xxxxxx として収める。

Pythonでの、Unicode文字列とUTF-8文字列の違い

ここは、かなり微妙な話で、わかりにくかったが、このページを見て納得した。

要するに、Python上では
Unicode文字列 = 次項に述べるように、「シーケンス型」のうちの1つで、propertyとして「unicode」がついているもの。だから、

>>> u1 = u"これはユニコード型文字列です"
>>> type(u1)
<type 'unicode'>

UTF-8の文字列 = シーケンス型のうちの「str型」であり、バイト列と思えばよいらしく、その中に入っている文字のエンコードは(pythonとしては)関わらない。たまたまシステムがUTF-8を仮定しているのでUTF-8のバイトコードが入っているだけ。

>>> s1 = "これはふつうの文字列です"
>>> type(s1)
<type 'str'> 

Pythonのシステムに対して、「扱う文字コードはUTF-8」としておけば(=プログラム上に書く文字列も、入出力もUTF-8としておけば)、str型の中に入っている文字列はUTF-8になっている。でも、ユニコード型とは、同じものではない。また、ユニコード型の文字列が内部的にUTF-8で表現されている保証もない(実際どうなのか知らない)。

目に見える違いとしては、たとえば

# EUC-JPなソースコードでは
>>> len("あ")   〜〜〜 EUC-JPやShift-JISでは2バイトで保持される
2
# UTF-8なソースコードでは
>>> len("あ")   〜〜〜 UTF-8では3バイトで保持される
3
# unicode型なら
>>> len(u"あ")  〜〜〜 ユニコード文字としては1文字(内部表現のバイトではなくて)
1 

ということになる。

で、基本は、プログラム内部ではなるべくunicode型で持つべし、ということになる。変換が必要になることがある。

Pythonでのunicode文字列とその分解

まず、Pythonでのunicode文字列は、「シーケンス型」である。(5.6節 シーケンス型

シーケンス型への操作は、この5.6節の表に載っている通りで、

x in ss のある要素 x と等しい場合 True , そうでない場合 False
x not in ss のある要素が x と等しい場合 False, そうでない場合 True
s + ts および t の結合
s * n, n * ss の浅いコピー n 個からなる結合
s[i]s の 0 から数えて i 番目の要素
s[i:j]s の i 番目から j 番目までのスライス
s[i:j:k]s の i 番目から j 番目まで、 k 毎のスライス
len(s)s の長さ
min(s)s の最小の要素
max(s)s の最大の要素
s.index(i)s 中で最初に i が現れる場所のインデクス
s.count(i)s 中に i が現れる数

だから、s[i]を使って文字列を先頭からスキャンして判定することになる。

Pythonでの、文字列がUnicodeであるかどうかの判定は、実は簡単で、 1文字ずつスキャンする必要はない。シーケンス型の変数xがUnicodeであるかどうかは、  isinstance(x, unicode) によって判定できる。

しかし、これは今の役には立たない。なぜなら、Twitter APIで得られたtext本文は、 すべて(ASCII文字も含めて)Unicodeで書かれているからである。たとえば、 u'abc' など。従って、ASCII文字であるか漢字(2バイトコード)であるかの区別は、 1文字ずつ判定をする必要がある。

実際に欲しいのは、文字列全体の中に漢字(2バイトコード)が含まれているか否か、であるから、1文字でも漢字が出てくればそこでTrueを返せばよい。全部の文字を確認する必要はない。つまり

Unicode文字列であることを確認したうえで、
先頭から1文字ずつ見て行って、
   もしord(文字)が30xxや4Exx-9xxxならば、それは漢字なので「漢字入り文字列」。
   もし最後のバイトまでそうでなければ、「ASCII文字列」

である。

# -*- coding: utf-8 -*-
def hasKanji(s):
  #Returns True if string s contains a Kanji, False if not.
  kanji = False
  for c in s:
   if (((ord(c)&0xff00) == 0x3000) or (0x4e00 <= ((ord(c)&0xff00) <=0x9f00))):
     kanji = True
     break
  return kanji

s = u"abc" とか u"abcあ" とか
print hasKanji(s)

Twitter APIとhasKanjiを組み合わせる

# -*- coding: utf-8 -*-
import twitter
def hasKanji(s):
  #Returns True if string s contains a Kanji, False if not.
  kanji = False
  for c in s:
   if (((ord(c)&0xff00) == 0x3000) or (0x4e00 <= ((ord(c)&0xff00) <=0x9f00))):
     kanji = True
     break
  return kanji

api = twitter.Api()
timeline = api.GetPublicTimeline()
for s in timeline:
  t = s.text
  if hasKanji(t):
    print "Kanji> ", t
  else:
    print "Ascii> ", t

実際やってみると、Kanjiのツイートはそれ程多くないものだ。時間帯によるのか?

次のステップは、選び出した漢字のツイートを日本語形態素解析に掛けることになる ⇒ ノート/テキストマイニング/twitter-2


トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2012-03-01 (木) 11:19:41 (2093d)