読者です 読者をやめる 読者になる 読者になる

Log.i53

Themidaのアンパックを目指すブログ改め使い物になるえんじにゃを目指すブログ

requestsとlxmlによって生じるValueErrorについて

鯖江のオープンデータとPythonで遊ぼうと思い、以前利用したrequestsとlxmlを利用したところValueErrorという例外が発生しました。

>>> import requests
>>> from lxml import etree
>>> xml = requests.get('http://www3.city.sabae.fukui.jp/xml/test/test.xml').text
>>> root = etree.fromstring(xml)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "lxml.etree.pyx", line 2754, in lxml.etree.XML (src/lxml/lxml.etree.c:54631)
  File "parser.pxi", line 1569, in lxml.etree._parseMemoryDocument (src/lxml/lxml.etree.c:82659)
ValueError: Unicode strings with encoding declaration are not supported.

とりあえず「lxml ValueError」でググってlxml公式にある方法などを試すも動かず…そういえば、こういう場合の対処にはとりあえず中身が何なのか確認しなさいと教わったのを思い出しました。

>>> type(xml)
<type 'unicode'>

あ…

解決策①

import requests
from lxml import etree

xml = requests.get('http://www3.city.sabae.fukui.jp/xml/test/test.xml').text
# xml = bytes(bytearray(xml, encoding = 'utf-8'))
xml = xml.encode('utf-8')
root = etree.fromstring(xml)

print etree.tostring(root, encoding = 'unicode')

Unicode文字列をUTF-8の文字列にエンコードすることで問題なく動くようになりました :D

エラーの原因

Requestsは、サーバーからの内容を自動的にデコードします。 ほとんどのユニコード文字はシームレスにデコードされます。
 
リクエストを作成した時、RequestsはHTTPヘッダーに基づてレスポンスのエンコーディングについて推測しようとします。 Requestsによって推測されたテキストエンコーディングは、 r.text にアクセスした時に使われます。 Requestsで使われているエンコーディングは調べることができ、 r.encoding プロパティを使って調べたり、変更することができます。
 
クイックスタート — requests-docs-ja 1.0.4 documentation より

取得するWebサイトによってValueErrorが発生したりしなかったりしますが、その原因はどうやらrequestsのtextプロパティの仕様にあったようです。

>>> requests.get('http://www3.city.sabae.fukui.jp/xml/test/test.xml').encoding
'ISO-8859-1'

オープンデータのサイトのエンコーディングをrequestsで確認します。

requestsが今回利用したオープンデータのサイトのエンコーディングが'ISO-8859-1'であると推測して、自動的にレスポンスのバイナリをUnicode文字列に変換してくれているのです。そして、それはlxmlのfromstringメソッドと相性の悪いものだったのです。

解決策②

import requests
from lxml import etree

xml = requests.get('http://www3.city.sabae.fukui.jp/xml/test/test.xml').content
root = etree.fromstring(xml)

print etree.tostring(root, encoding = 'unicode')

textではなくcontentプロパティを利用することでエンコードしなおす必要がなくなります。