Python json.loads falla con `ValueError: Carácter de control no válido en: línea 1 columna 33 (carácter 33)`

Tengo una cuerda como esta:

s = u"""{"desc": "\u73cd\u54c1\u7f51-\u5168\u7403\u6f6e\u6d41\u5962\u54c1\u7f51\u7edc\u96f6\u552e\u5546 
\r\nhttp:\/\/www.zhenpin.com\/
\r\n
\r\n200\u591a\u4e2a\u56fd\u9645\u4e00\u7ebf\u54c1\u724c\uff0c\u9876\u7ea7\u4e70\u624b\u5168\u7403\u91c7\u8d2d\uff0c100%\u6b63\u54c1\u4fdd\u969c\uff0c7\u5929\u65e0\u6761\u2026"}"""

json.loads(s) devuelve un mensaje de error como este:

 ValueError: Invalid control character at: line 1 column 33 (char 33) 

¿Por qué se produce este error? ¿Como puedó resolver esté problema?

El problema es que su cadena Unicode contiene retornos de carro ( \r ) y líneas nuevas ( \n ) dentro de un literal de cadena en los datos JSON. Si estaban destinados a formar parte de la cadena, deberían escaparse de manera apropiada. Si no estaban destinados a ser parte de la cadena, tampoco deberían estar en su JSON.

Si no puede arreglar dónde obtuvo esta cadena JSON para producir JSON válido, puede eliminar los caracteres ofensivos:

 >>> json.loads(s.replace('\r\n', '')) 

o escapar de ellos manualmente:

 >>> json.loads(s.replace('\r\n', '\\r\\n')) 

Otra opción, tal vez, es usar el argumento strict=False

Según http://docs.python.org/2/library/json.html

“Si estricto es Falso (Verdadero es el valor predeterminado), entonces los caracteres de control se permitirán dentro de las cadenas. Los caracteres de control en este contexto son aquellos con códigos de caracteres en el rango 0-31, incluyendo ‘\ t’ (tab), ‘\ n ‘,’ \ r ‘y’ \ 0 ‘”.

Por ejemplo:

 json.loads(json_str, strict=False) 

El problema es que el carácter en el índice 33 es un carácter de control de retorno de carro.

 >>> s[33] u'\r' 

Según la especificación JSON, los caracteres válidos son:

  • Cualquier carácter Unicode excepto: " , \ y caracteres de control ( ord(char) < 32 ).

  • Se permiten las siguientes secuencias de caracteres: \" , \\ , \/ , \b (retroceso), \f (avance de página), \n (avance de línea / nueva línea), \r (retorno de carro), \t (tab), o \u seguido de cuatro dígitos hexadecimales.

Sin embargo, en Python tendrás que duplicar los caracteres de control de escape (a menos que la cadena esté sin formato) porque Python también interpreta esos caracteres de control.

 >>> s = ur"""{"desc": "\u73cd\u54c1\u7f51-\u5168\u7403\u6f6e\u6d41\u5962\u54c1\u7f51\u7edc\u96f6\u552e\u5546 
\r\nhttp:\/\/www.zhenpin.com\/
\r\n
\r\n200\u591a\u4e2a\u56fd\u9645\u4e00\u7ebf\u54c1\u724c\uff0c\u9876\u7ea7\u4e70\u624b\u5168\u7403\u91c7\u8d2d\uff0c100%\u6b63\u54c1\u4fdd\u969c\uff0c7\u5929\u65e0\u6761\u2026"}""" >>> json.loads(s) {u'desc': u'\u73cd\u54c1\u7f51-\u5168\u7403\u6f6e\u6d41\u5962\u54c1\u7f51\u7edc\u96f6\u552e\u5546
\r\nhttp://www.zhenpin.com/
\r\n
\r\n200\u591a\u4e2a\u56fd\u9645\u4e00\u7ebf\u54c1\u724c\uff0c\u9876\u7ea7\u4e70\u624b\u5168\u7403\u91c7\u8d2d\uff0c100%\u6b63\u54c1\u4fdd\u969c\uff0c7\u5929\u65e0\u6761\u2026'}

Referencias:

Intenta escapar de tu \n \r :

 s = s.replace('\r', '\\r').replace('\n', '\\n') json.loads(s) >>> {u'desc': u'\u73cd\u54c1\u7f51-\u5168\u7403\u6f6e\u6d41\u5962\u54c1\u7f51\u7edc\u96f6\u552e\u5546 
\r\nhttp://www.zhenpin.com/
\r\n
\r\n200\u591a\u4e2a\u56fd\u9645\u4e00\u7ebf\u54c1\u724c\uff0c\u9876\u7ea7\u4e70\u624b\u5168\u7403\u91c7\u8d2d\uff0c100%\u6b63\u54c1\u4fdd\u969c\uff0c7\u5929\u65e0\u6761\u2026'}

En algunos casos, este error se producirá cuando el archivo realmente contenga una cadena con un espacio en blanco. Eliminar el espacio en blanco resolverá el problema.