Edited at

Scrapyでログに出力されるItem内のUnicode文字列を読めるようにする

More than 3 years have passed since last update.

2016-05-25追記:2016年5月にリリースされたScrapy 1.1はPython 3に対応したので、Python 3を使えばこのページの問題はそもそも発生しません。何もしなくてもItem内の日本語が読めます。ぜひPython 3とScrapy 1.1以降を使いましょう!


問題

Scrapyでは、SpiderからyieldしたItemがログに出力されますが、日本語など非ASCIIの文字列を含むフィールドはUnicodeエスケープされた状態で出力されるので、パット見で読めず、もどかしい思いをします。


(雑な) 対処法

Itemクラスに以下のような__unicode__()メソッドを定義すると読みやすくなります。

class MyItem(scrapy.Item):

# ...
def __unicode__(self):
return repr(self).decode('unicode_escape')


実行例

以下のSpiderを実行します。これは12factor.netというWebサイトで翻訳のある言語の一覧を取得するSpiderです。


twelve_factor_spider.py

# coding: utf-8


import scrapy

class MyItem(scrapy.Item):
languages = scrapy.Field()

def __unicode__(self):
return repr(self).decode('unicode_escape')

class TwelveFactorSpider(scrapy.Spider):
name = '12factor'
start_urls = ['http://12factor.net/']

def parse(self, response):
yield MyItem(languages=response.css('#locales a::text, #locales span::text').extract())



Before (__unicode__なし)

Unicode文字列がエスケープされています。

$ scrapy runspider twelve_factor_spider.py

2016-01-13 17:56:46 [scrapy] INFO: Scrapy 1.0.4 started (bot: scrapybot)
2016-01-13 17:56:46 [scrapy] INFO: Optional features available: ssl, http11
2016-01-13 17:56:46 [scrapy] INFO: Overridden settings: {}
...
2016-01-13 17:56:47 [scrapy] DEBUG: Scraped from <200 http://12factor.net/>
{'languages': [u'Espa\xf1ol (es)',
u'Fran\xe7ais (fr)',
u'Deutsch (de)',
u'\u0420\u0443\u0441\u0441\u043a\u0438\u0439 (ru)',
u'English (en)',
u'\u65e5\u672c\u8a9e (ja)',
u'Italiano (it)',
u'Brazilian Portuguese (pt_br)',
u'\u7b80\u4f53\u4e2d\u6587 (zh_cn)',
u'\ud55c\uad6d\uc5b4 (ko)']}
...


After (__unicode__あり)

Unicode文字列がエスケープされておらず、読みやすいです。

$ scrapy runspider twelve_factor_spider.py

2016-01-13 17:57:25 [scrapy] INFO: Scrapy 1.0.4 started (bot: scrapybot)
2016-01-13 17:57:25 [scrapy] INFO: Optional features available: ssl, http11
2016-01-13 17:57:25 [scrapy] INFO: Overridden settings: {}
...
2016-01-13 17:57:25 [scrapy] DEBUG: Scraped from <200 http://12factor.net/>
{'languages': [u'Español (es)',
u'Français (fr)',
u'Deutsch (de)',
u'Русский (ru)',
u'English (en)',
u'日本語 (ja)',
u'Italiano (it)',
u'Brazilian Portuguese (pt_br)',
u'简体中文 (zh_cn)',
u'한국어 (ko)']}
...

ちなみに、あくまでログへの出力内容を変えるだけで、-o/--outputでファイルに保存した場合の内容などには影響しません。


注意点

decode('unicode_escape')を通すと、空白文字 (\n, \r, \t, \u3000など) や制御文字のUnicodeエスケープもアンエスケープされてしまいます。このため、空白文字やバイト列が含まれるItemでは逆に中身がわかりづらくなる可能性が大いにあります。使い方には気をつけてください。

ScrapyがPython 3に対応したらこんな面倒なことしなくて良くなるはずです。

おまけ:もう少し真面目に表示可能な文字のみをアンエスケープする関数を書いてみました。 → http://ideone.com/J77aaN


まとめ


  • Itemの__unicode__()を定義するとログへの出力内容を変更できる。

  • Python 3が便利。