Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A good way to get the charset/encoding of an HTTP response in Python

Looking for an easy way to get the charset/encoding information of an HTTP response using Python urllib2, or any other Python library.

>>> url = 'http://some.url.value' >>> request = urllib2.Request(url) >>> conn = urllib2.urlopen(request) >>> response_encoding = ? 

I know that it is sometimes present in the 'Content-Type' header, but that header has other information, and it's embedded in a string that I would need to parse. For example, the Content-Type header returned by Google is

>>> conn.headers.getheader('content-type') 'text/html; charset=utf-8' 

I could work with that, but I'm not sure how consistent the format will be. I'm pretty sure it's possible for charset to be missing entirely, so I'd have to handle that edge case. Some kind of string split operation to get the 'utf-8' out of it seems like it has to be the wrong way to do this kind of thing.

>>> content_type_header = conn.headers.getheader('content-type') >>> if '=' in content_type_header: >>>  charset = content_type_header.split('=')[1] 

That's the kind of code that feels like it's doing too much work. I'm also not sure if it will work in every case. Does anyone have a better way to do this?

like image 249
Clay Wardell Avatar asked Jan 29 '13 21:01

Clay Wardell


People also ask

What character encoding is HTTP?

The charset parameter It is very important to always label Web documents explicitly. HTTP 1.1 says that the default charset is ISO-8859-1.

Can HTTP headers be UTF 8?

As per RFC5987 also, the character set and language encoding in HTTP headers must be ISO-8859-1 and UTF-8 character sets, both of which are basically 8-bit single byte encoded characters.


2 Answers

To parse http header you could use cgi.parse_header():

_, params = cgi.parse_header('text/html; charset=utf-8') print params['charset'] # -> utf-8 

Or using the response object:

response = urllib2.urlopen('http://example.com') response_encoding = response.headers.getparam('charset') # or in Python 3: response.headers.get_content_charset(default) 

In general the server may lie about the encoding or do not report it at all (the default depends on content-type) or the encoding might be specified inside the response body e.g., <meta> element in html documents or in xml declaration for xml documents. As a last resort the encoding could be guessed from the content itself.

You could use requests to get Unicode text:

import requests # pip install requests  r = requests.get(url) unicode_str = r.text # may use `chardet` to auto-detect encoding 

Or BeautifulSoup to parse html (and convert to Unicode as a side-effect):

from bs4 import BeautifulSoup # pip install beautifulsoup4  soup = BeautifulSoup(urllib2.urlopen(url)) # may use `cchardet` for speed # ... 

Or bs4.UnicodeDammit directly for arbitrary content (not necessarily an html):

from bs4 import UnicodeDammit  dammit = UnicodeDammit(b"Sacr\xc3\xa9 bleu!") print(dammit.unicode_markup) # -> Sacré bleu! print(dammit.original_encoding) # -> utf-8 
like image 111
jfs Avatar answered Sep 21 '22 08:09

jfs


If you happen to be familiar with the Flask/Werkzeug web development stack, you will be happy to know the Werkzeug library has an answer for exactly this kind of HTTP header parsing, and accounts for the case that the content-type is not specified at all, like you had wanted.

 >>> from werkzeug.http import parse_options_header  >>> import requests  >>> url = 'http://some.url.value'  >>> resp = requests.get(url)  >>> if resp.status_code is requests.codes.ok:  ...     content_type_header = resp.headers.get('content_type')  ...     print content_type_header  'text/html; charset=utf-8'  >>> parse_options_header(content_type_header)   ('text/html', {'charset': 'utf-8'}) 

So then you can do:

 >>> content_type_header[1].get('charset')  'utf-8' 

Note that if charset is not supplied, this will produce instead:

 >>> parse_options_header('text/html')  ('text/html', {}) 

It even works if you don't supply anything but an empty string or dict:

 >>> parse_options_header({})  ('', {})  >>> parse_options_header('')  ('', {}) 

Thus it seems to be EXACTLY what you were looking for! If you look at the source code, you will see they had your purpose in mind: https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/http.py#L320-329

def parse_options_header(value):     """Parse a ``Content-Type`` like header into a tuple with the content     type and the options:     >>> parse_options_header('text/html; charset=utf8')     ('text/html', {'charset': 'utf8'})     This should not be used to parse ``Cache-Control`` like headers that use     a slightly different format.  For these headers use the     :func:`parse_dict_header` function.     ... 

Hope this helps someone some day! :)

like image 45
Brian Peterson Avatar answered Sep 23 '22 08:09

Brian Peterson