Edited at

python3で日本語を含むURL(日本語URL)にurllibでアクセスする際、htmlで勝手にencodeされてエラーするので回避策をメモ

More than 3 years have passed since last update.


内容


  • 文字列の中の日本語に対して、処理をして置換する方法

    "http://ほげふが/qiita.com" -> 「ほげふが」を処理した結果で置き換える


  • urllibのurlonenで日本語含むURLを使いたい時のちゃんとした(?)対応

    追記:ご指摘頂いた方法が今回の対応として正しいと思われたため最後に追記しました。



背景


  • urllibを使ってスクレイピングして「エヴァンゲリオン」画像を集めようとしたらはまった

  • はまったのは、「UnicodeEncodeError」。python苦手。。。


つまづいた内容

response = urllib.request.urlopen(url)

普通です。urlにアクセスしてオブジェクトくださいやってるだけです。

ただしこのurlに日本語が含まれていたので悲劇が起こりました。

url = 'http://image.search.yahoo.co.jp/search?p=エヴァンゲリオン'

みたいな感じです。

速攻でpythonの闇に引きずり込まれます。

エラー内容追記しました。

Traceback (most recent call last):

・・・
response = urllib.request.urlopen(link)
File "/Users/mix/.pyenv/versions/3.5.0/lib/python3.5/urllib/request.py", line 162, in urlopen
return opener.open(url, data, timeout)
File "/Users/mix/.pyenv/versions/3.5.0/lib/python3.5/urllib/request.py", line 465, in open
response = self._open(req, data)
File "/Users/mix/.pyenv/versions/3.5.0/lib/python3.5/urllib/request.py", line 483, in _open
'_open', req)
File "/Users/mix/.pyenv/versions/3.5.0/lib/python3.5/urllib/request.py", line 443, in _call_chain
result = func(*args)
File "/Users/mix/.pyenv/versions/3.5.0/lib/python3.5/urllib/request.py", line 1268, in http_open
return self.do_open(http.client.HTTPConnection, req)
File "/Users/mix/.pyenv/versions/3.5.0/lib/python3.5/urllib/request.py", line 1240, in do_open
h.request(req.get_method(), req.selector, req.data, headers)
File "/Users/mix/.pyenv/versions/3.5.0/lib/python3.5/http/client.py", line 1083, in request
self._send_request(method, url, body, headers)
File "/Users/mix/.pyenv/versions/3.5.0/lib/python3.5/http/client.py", line 1118, in _send_request
self.putrequest(method, url, **skips)
File "/Users/mix/.pyenv/versions/3.5.0/lib/python3.5/http/client.py", line 960, in putrequest
self._output(request.encode('ascii'))
UnicodeEncodeError: 'ascii' codec can't encode characters in position 14-21: ordinal not in range(128)

エラーを見る限り、urllibがasciiに変換しようとしてるだけですよね???

追記:httpがURLをascii変換しようとしていました!!

回避策どこやー!と探したところ、

日本語の部分はパースすればいいよ、とのこと。

追記:URLエンコード(パーセントエンコーディング)すればいいよ、とのことです。

urllib.parse.quote_plus('エヴァンゲリオン', encoding='utf-8')

みたいな感じですか?

これだと問題が。。。

url = 'http://image.search.yahoo.co.jp/search?p=' + urllib.parse.quote_plus('エヴァンゲリオン', encoding='utf-8')

愚直にやるとこんななっちゃう。。。

と思って調べたら対象外にする文字列も指定できるとのこと!

第二引数で渡せばいいみたい。

urllib.parse.quote_plus(url, "/:?=&")

みたいな感じですか?なんか対象外の文字に漏れあるかも。。。

一応これで動いたのですが、ちょっと気になったので別の方法も。

逆に(?)日本語全部置換したらいいんでは!ということをやってみました。


やったこと


  • 正規表現で日本語取得 -> findallして結果のリストを使ってreplaceする


まどろっこしいです!

ただ、この方法では、正規表現にマッチした言葉を

「関数に渡してその結果で」置き換えることができます。

書き方をなんとかしたかったですが、頭が硬いのか思い浮かばず。。。

pythonの事知らなすぎてぱっと見までよくなく。。。

lambdaも副作用無理っぽいし。他になんかあれば教えてください。イテレータとか?

regex = r'[ぁ-ヶ亜-熙]'

matchedList = re.findall(regex,url)
for m in matchedList:
url = url.replace(m, urllib.parse.quote_plus(m, encoding="utf-8"))

日本語を全部となると

[ぁ-ン]とか書いてる記事が多いのですが、

文字コード表見ると、まさかの「ヶ」!

そう!!

全然馴染みないpythonちゃんで汚いコード晒してでも

最後のこの驚きを共有したいがために書きました。


追記:正規表現の正しい指定方法

@KeisukeKudo さんに改善策頂きましたのでここでも紹介致します!

私の表記は厳密には漏れがあるので、もし使う場合は下記でよろしくお願いします。


regex = r'[ぁ-ヶ亜-熙]'

#上記を以下のように変更
regex = r'[^\x00-\x7F]'

としてみては如何でしょうか

[\x00-\x7F]

これは、ascii文字にマッチする正規表現です。

上記の否定形を使うことで、日本語にマッチする文字を取得できます。

http://rubular.com/r/2dnoBUlKe9



追記:今回の対応でもっとも正しい方法

@komeda-shinji さんに改善策を頂いたのでここでも紹介致します!

やりたいことを具体的に考えると、URLのクエリにascii変換できない文字があるときに、

先にURLエンコードしておく、ということなので下記の方がベターです。

URLの構成要素で分解してクエリだけURLエンコードして再構築しています。


from urllib.parse import urlparse

import urllib.request

url = 'http://image.search.yahoo.co.jp/search?p=エヴァンゲリオン'
p = urlparse(url)
query = urllib.parse.quote_plus(p.query, safe='=&')
url = '{}://{}{}{}{}{}{}{}{}'.format(
p.scheme, p.netloc, p.path,
';' if p.params else '', p.params,
'?' if p.query else '', query,
'#' if p.fragment else '', p.fragment)
response = urllib.request.urlopen(url)