2015年2月21日土曜日

PythonのUnicodeEncodeErrorを知る

Pythonにはじめて触って、いつのまにか1年が過ぎた のですが、一番はまったのは、やっぱりunicodeの扱いだったと思います。

特に、
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-12: ordinal not in range(128)  
のようなエラーにはさんざん悩まされました。ここがたとえばrubyなど他の言語と比べてわかりにくいために、Pythonが取っつきにくい 言語になっているのではないか、と個人的には思います。

そこで、このエラーに関係するはまりどころとTipsをいくつか列挙してみました。これからPythonに触れられる方の参考になればと思い ます。


unicode型について

u1 = u"これはユニコード型文字列です"
のように、"の前にuをつけて宣言すると、u1は「Unicode型」になります。
>>> u1 = u"これはユニコード型文字列です"  >>> type(u1)  <type 'unicode'>  >>> s1 = "これはふつうの文字列です"  >>> type(s1)  <type 'str'>   
u1はunicode型で、s1はstr型です。s1にどのような文字コードが格納されているかは、前節の「エンコード宣言」で何を宣言した かによって決定されます。u1には、エンコード宣言にかかわらず「Unicode文字列を表現可能な、とある内部形式」で情報が格納されてい ます。

混乱しやすい(私が混乱した)のは、「unicode型」と、たとえば「UTF-8文字コード列」等の「Unicodeの文字列」は「別物」 ということです。

str型は、代入された時点で、(ソースコードのエンコーディングがUTF-8の場合)UTF-8の文字コード列であるため、s1は、 UTF-8の文字コード列としてそのまま出力することが可能です(pythonから見ると、特に意味の無いバイト列になります)。

一方、「unicode型」とは、「Unicodeを表現可能な、とある内部形式」であって、上記のu1は、どの文字コード列(エンコーディ ング)でもありません。どの文字コードで出力するかは、実際にu1を出力する際に決定し、出力する前に内部形式から文字コード列に変換する必 要があります(これは明示的に行われることもありますし、暗黙のうちに行われることもあります)。



unicode型を使うメリット


C言語等の他の言語と同じように、日本語を単なるバイト列として扱うのであれば、unicode型を使わずに、str型に特定の文字コードの バイト列を格納してプログラムを書くことももちろん可能です。しかし、その場合、str型の変数に、どの文字コード列が格納されているのかを 常に意識し、その特徴に従う必要があります。

たとえば、len関数のような、長さを返す関数は、「文字」ではなく「バイト」の長さを返すことになるので、文字コードによって長さが異なり ます。
# EUC-JPなソースコードでは  >>> len("あ")  2  # UTF-8なソースコードでは  >>> len("あ")  3  # unicode型なら  >>> len(u"あ")  1   
正規表現もすんなりとはいきません。
# Shift-JISなソースコードでは  >>> re.match("すも+", "すもももももももももももももももももも")  →"すも"にマッチする    # unicode型なら  >>> re.match(u"すも+", "すもももももももももももももももももも")  →"すもももももももももももももももももも"にマッチする   
さらに、Shift JISにおける0x5c問題(「表」等の文字に"\"が含まれており、エスケープコードとして扱われてしまう問題)など、気を遣わなければならない問題が たくさんあります。

このため、pythonで国際化されているライブラリやアプリケーション等では、unicode型での入出力を前提にしているものがほとんど です。パフォーマンス上の理由など、妥当な理由が存在しない限りは、pythonで日本語を扱う場合においてはunicode型を利用するの が無難です。



encode、decode、unicode


では、実際に出力する場合に、どのようにして「unicode型」を文字コード列に変換するのでしょうか。

これには、unicode型のインスタンスメソッドであるencodeを使います。その逆には、decodeを使います。

  • encodeは、「Unicode型を特定の文 字コードのバイト列(のstr型)にエンコードする」ためのメソッドです。
  • decodeは、「特定の文字コードのバイト列 (のstr型)をデコードしてUnicode型にする」ためのメソッドです。



どっちがどっちだっけ?とごっちゃになったり、頭の中で逆転しやすいので、気をつけましょう。

たとえば上述のu1をUTF-8で出力するには、下記のようにします。
print u1.encode('utf_8')   
(ええー!python面倒くさくない?と思うなかれ。あとで、詳しく説明します)

なお、u1.encode('utf_8')は、UTF-8文字コード列(のstr型)なので、s1と同一になります。
>>> s1 == u1.encode('utf_8')  True   
では、s1とu1を直接比較するとどうなるのでしょうか?
>>> s1 == u1  Traceback (most recent call last):    File "<stdin>", line 1, in ?  UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 0: ordinal not in range(128)   
標準的な環境では、こんなエラーが出ると思います。これは、(unicode型である)u1と比較するために、(str型である)s1を文字 コード'ascii'でデコードしてunicode型にしようとしたのですが、できませんでした、というエラーです。

なぜ、できなかったのか?それはどの文字コードでエンコードするかが指定されていないからです。これまでのencode, decodeの例では、'utf_8'が指定されていましたが、この例では特に何も明示していません。そのため、とりあえずpythonのデフォルトの設 定である'ascii'つまりASCIIコードだと想定してデコードしようとしたのですが、s1の先頭バイト(position 0)にASCIIには無い文字が出現したので、エラーとなったわけです。

pythonで日本語を使おうとすると、UnicodeEncodeError, UnicodeDecodeErrorあたりでくじけそうになります。というか私はくじけそうになりました。が、わかってしまえばたいしたことは無いの で、もう少しがんばりましょう。

ほか に、pythonの組み込み関数unicodeも存在します。これは、主に「str型をデコードして、unicode型にする」ため の組み込み関数で、(str型に対して使う限りにおいては)decodeと同じ機能を持った関数と考えられます。
u2 = unicode(s1, 'utf_8')  
のように使います。



UnicodeDecodeErrorの原因を知る


たとえば、
u1.encode()  s1.decode()  unicode(s1)   
のように、文字コードを省略したような場合など、「文字コードが指定されていないケースでエンコード」しなければならない局面では、 encodeやstrはsys.getdefaultencoding()で取得できる、「デフォルトエンコーディング」の値を使用します。

このsys.getdefaultencoding()は通常の環境では、'ascii'になっています。
>>> import sys  >>> sys.getdefaultencoding()  'ascii'   
そのため、
u1.encode('ascii')  s1.decode('ascii')  unicode(s1, 'ascii')   
を呼び出したときと同じ挙動をするわけです。

s1.decode()と打つと、先述のu1 == s1とまったく同じエラーが出るはずです。
>>> s1.decode()  Traceback (most recent call last):    File "<stdin>", line 1, in ?  UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 0: ordinal not in range(128)   
(ASCIIコードだと想定してデコードしようとしたのですが、s1の先頭バイト(position 0)にASCIIには無い文字が出現したので、エラーとなった)

では、「デフォルトエンコーディング」を変えてしまえば良いのでは?getdefaultencodingがあるんだから、 setdefaultencodingもあるでしょ、と思われた方は、良い勘をしていると思うんですが、 setdefaultencodingは、残念ながら、ありません(*1)。

解決法としては、入力された文字列はなるべく早い段階でunicode型に変換し、その文字列は出力されるぎりぎりまでunicode型で保 持するようにこころがけ、上記のような暗黙に変換されるような処理を書かないよう工夫するのがベストだと私は思います。

そのためには、どういう場所で暗黙に変換されるのか、主な「はまりどころ」を理解しておく必要があります。上記の、「str型と unicode型の比較」は「はまりどころ」の1つです。

*1 正確には、python起動時に読み込まれるスクリプトで消されてしまいます。一つの解法は、setdefaultencoding が消される前に実行されるスクリプト、たとえばsitecustomize.pyで、 sys.setdefaultencoding('utf_8')と書いてしまうことです。この方法については、大抵のケースを解決 するものの、様々な理由からこのドキュメントでは推奨していません。興味のある方はsitecustomize.py setdefaultencodingなどのキーワードで検索してみてください。




はまりどころ:printステートメント


先ほどは、日本語を確実に出力するために、
print u1.encode('utf_8')   
のようにしましたが、実は、単に
print u1   
ように直接unicode型を渡しても、unicode型からstr型へのエンコードが行われます。たいていは正しく日本語を出力できます。

この場合には、先ほどのsys.getdefaultencoding()で得られる文字コード(エンコーディング)ではなく、環境変数 LANG等のロケールで設定された文字コードを使ってエンコードされます。

このとき使用されるエンコーディングは、sys.stdout.encodingから参照することができます。
#!/usr/bin/env python  # -*- coding: utf-8 -*-  import sys  print sys.stdout.encoding  print u"日本語"   
上記のようなファイルを、nihongo.pyという名前で保存して、下記のように実行してみましょう。
$ LANG=ja_JP.UTF-8 ./nihongo.py  UTF-8  日本語   
LANGが日本語のときには、想定された動きをします(LANGには端末で出力できる文字コードを設定してください)。
$ LANG=C ./nihongo.py  ANSI_X3.4-1968  Traceback (most recent call last):    File "./nihongo.py", line 5, in ?      print u"日本語"  UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)   
LANGが正しく設定されていないと、上記のようにエラーが発生します。



はまりどころ:パイプ

パイプや、atやcronから起動されたプロセスなど、端末にひも付いていないプロセスからpythonを実行する場合、printステート メントにunicode型を渡すと、str型への展開にあたって、LANG等の設定は無視され、またまた sys.setdefaultencodingの値が使用されます。

単体では動くのに、パイプでつないだら動かなくなった!
単体では動くのに、cronから呼び出したら動かなくなった!
など、大いにくじける原因になりますので、注意しましょう。
$ #例1)上記のnihongo.pyを実行し、パイプに出力する  $ ./nihongo.py | cat  Traceback (most recent call last):    File "./nihongo.py", line 5, in ?      print u"日本語"  UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)  None    $ #例2)LANGは無視される  $ LANG=ja_JP.UTF-8 ./nihongo.py | cat  Traceback (most recent call last):    File "./nihongo.py", line 5, in ?      print u"日本語"  UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)  None   


はまりどころ:ファイルオブジェクトのwrite

ファイルオブジェクトのwriteは、端末にひも付いていようといまいと、ロケールを全く気にしてくれません。 sys.getdefaultencoding()の値が使用されるようです。

下記のスクリプトをnihongofile.pyとして保存します。
#!/usr/bin/env python  # -*- coding: utf-8 -*-  f = open('fairu', 'w')  f.write(u"日本語")   
実行します。
$ LANG=ja_JP.UTF-8 ./nihongofile.py  Traceback (most recent call last):    File "./nihongofile.py", line 4, in ?      f.write(u"日本語")  UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-2: ordinal not in range(128)   


回避方法:sys.stdoutやファイルオブジェクトを直接使わないようにする

上記の問題点を回避するためには、printやwriteにunicode型を渡さず、str型に変換してから渡す、というのが無難な方法だ と思うのですが、とはいえ、たとえばprintfデバッグをしたい場合など、unicode型を渡してしまいたいのが人情です。

安心してprintにunicode型を渡したい場合には、生のsys.stdoutではなく、streamwriterで
import sys, codecs  sys.stdout = codecs.EncodedFile(sys.stdout, 'utf_8')    
として、sys.stdoutをラップしてしまう方法が考えられます。
import sys, codecs  sys.stdout = codecs.EncodedFile(sys.stdout, 'utf_8')   print u"日本語"   
このほか、「Pythonクックブック」の1.22「標準出力にUnicodeキャラ クタを出力」には、
sys.stdout = codecs.lookup('utf_8')[-1](sys.stdout)  print u"日本語"   
という記法が載っていました。
([-1]がわかりにくい場合には、pydoc codecs.lookupして調べてみてください。戻り値のtupleの4番目の値、つまりStreamWriterを指しています)

sys.stdoutをファイルオブジェクトに差し替えれば、この方法は、ファイルオブジェクトでも使うことができます。
f = open('fairu', 'w')  f = codecs.lookup('utf_8')[-1](f)  f.write(u"日本語")   
なお、出力をUTF-8で決め打ちしたくない場合には、
import locale  enc = locale.getpreferredencoding()   
などとして、ロケールのエンコーディングを取得し、
import codecs  sys.stdout = codecs.EncodedFile(sys.stdout, enc)    
のようにすると、EUC-JPやCP932な環境でも文字化けしないプログラムが書けるかもしれません。



その他:%展開
>>> "名前は%s、%d才です。" % (u"HDE", 12)  Traceback (most recent call last):    File "<stdin>", line 1, in ?  UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 2: ordinal not in range(128)   
右辺にunicode型があるため、左側のstr型がunicode型にデコードされようとするが、デフォルトエンコーディングである 'ascii'でデコードされようとしてしまうため、エラーになります。
>>> "名前は%s、%d才です。" % ("HDE", 12)  '\xe5\x90\x8d\xe5\x89\x8d\xe3\x81\xafHDE\xe3\x80\x8112\xe6\x89\x8d\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82'   
右辺も左辺もstr型であるため、矛盾は生じず、エラーになりません。

%展開する文字列にuを付け忘れると、後者の例では例外が出ずに動いてしまうため、ややこしく感じることがあります。



おまけ:「エンコード宣言」

pythonのソースコード中に日本語を書く場合には、「エンコード宣言」なるものをします。具体的には「1行目か2行目に」以下のように書 きます。
# -*- coding: utf_8 -*-    
または
# vim:fileencoding=utf_8    
この「エンコード宣言」は、「ソースコードの文字コード」を表します。この指定は、「ソースコードの文字コード」であって、実行時の出力にも 入力にも影響しません。 

UnicodeDecodeErrorが出ると、この辺を疑いたくなるのですが、エディタのエンコード設定と、エンコード宣言をきちんと一致 させて、あとは忘れてしまいましょう。

2015年2月17日火曜日

Web_crawler Tools

http://en.wikipedia.org/wiki/Web_crawler

The following is a list of published crawler architectures for general-purpose crawlers (excluding focused web crawlers), with a brief description that includes the names given to the different components and outstanding features:
  • Bingbot is the name of Microsoft's Bing webcrawler. It replaced Msnbot.
  • FAST Crawler is a distributed crawler, used by Fast Search & Transfer, and a general description of its architecture is available.[citation needed]
  • Googlebot is described in some detail, but the reference is only about an early version of its architecture, which was based in C++ and Python. The crawler was integrated with the indexing process, because text parsing was done for full-text indexing and also for URL extraction. There is a URL server that sends lists of URLs to be fetched by several crawling processes. During parsing, the URLs found were passed to a URL server that checked if the URL have been previously seen. If not, the URL was added to the queue of the URL server.
  • GM Crawl is a crawler highly scalable usable in SaaS mode
  • PolyBot is a distributed crawler written in C++ and Python, which is composed of a "crawl manager", one or more "downloaders" and one or more "DNS resolvers". Collected URLs are added to a queue on disk, and processed later to search for seen URLs in batch mode. The politeness policy considers both third and second level domains (e.g.: www.example.com and www2.example.com are third level domains) because third level domains are usually hosted by the same Web server.
  • RBSE was the first published web crawler. It was based on two programs: the first program, "spider" maintains a queue in a relational database, and the second program "mite", is a modified www ASCII browser that downloads the pages from the Web.
  • Swiftbot is Swiftype's web crawler, designed specifically for indexing a single or small, defined group of web sites to create a highly customized search engine. It enables unique features such as real-time indexing that are unavailable to other enterprise search providers.
  • WebCrawler was used to build the first publicly available full-text index of a subset of the Web. It was based on lib-WWW to download pages, and another program to parse and order URLs for breadth-first exploration of the Web graph. It also included a real-time crawler that followed links based on the similarity of the anchor text with the provided query.
  • WebFountain is a distributed, modular crawler similar to Mercator but written in C++. It features a "controller" machine that coordinates a series of "ant" machines. After repeatedly downloading pages, a change rate is inferred for each page and a non-linear programming method must be used to solve the equation system for maximizing freshness. The authors recommend to use this crawling order in the early stages of the crawl, and then switch to a uniform crawling order, in which all pages are being visited with the same frequency.
  • WebRACE is a crawling and caching module implemented in Java, and used as a part of a more generic system called eRACE. The system receives requests from users for downloading web pages, so the crawler acts in part as a smart proxy server. The system also handles requests for "subscriptions" to Web pages that must be monitored: when the pages change, they must be downloaded by the crawler and the subscriber must be notified. The most outstanding feature of WebRACE is that, while most crawlers start with a set of "seed" URLs, WebRACE is continuously receiving new starting URLs to crawl from.
  • World Wide Web Worm was a crawler used to build a simple index of document titles and URLs. The index could be searched by using the grep Unix command.
  • Yahoo! Slurp was the name of the Yahoo! Search crawler until Yahoo! contracted with Microsoft to use Bingbot instead.

In addition to the specific crawler architectures listed above, there are general crawler architectures published by Cho and Chakrabarti.

Open-source crawlers

  • DataparkSearch is a crawler and search engine released under the GNU General Public License.
  • GNU Wget is a command-line-operated crawler written in C and released under the GPL. It is typically used to mirror Web and FTP sites.
  • GRUB is an open source distributed search crawler that Wikia Search used to crawl the web.
  • Heritrix is the Internet Archive's archival-quality crawler, designed for archiving periodic snapshots of a large portion of the Web. It was written in Java.
  • ht://Dig includes a Web crawler in its indexing engine.
  • HTTrack uses a Web crawler to create a mirror of a web site for off-line viewing. It is written in C and released under the GPL.
  • ICDL Crawler is a cross-platform web crawler written in C++ and intended to crawl Web sites based on Website Parse Templates using computer's free CPU resources only.
  • mnoGoSearch is a crawler, indexer and a search engine written in C and licensed under the GPL (*NIX machines only)
  • Norconex HTTP Collector is a web spider, or crawler, written in Java, that aims to make Enterprise Search integrators and developers's life easier (licensed under GPL).
  • Nutch is a crawler written in Java and released under an Apache License. It can be used in conjunction with the Lucene text-indexing package.
  • Open Search Server is a search engine and web crawler software release under the GPL.
  • PHP-Crawler is a simple PHP and MySQL based crawler released under the BSD License. Easy to install, it became popular for small MySQL-driven websites on shared hosting.[citation needed]
  • Scrapy, an open source webcrawler framework, written in python (licensed under BSD).
  • Seeks, a free distributed search engine (licensed under Affero General Public License).
  • tkWWW Robot, a crawler based on the tkWWW web browser (licensed under GPL).
  • YaCy, a free distributed search engine, built on principles of peer-to-peer networks (licensed under GPL).

See also