Em requests, como ler corretamente o encoding ISO-8859-1?

Em Python3, com beautifulsoup4 e requests, quero extrair algumas informações de um site que possui encoding ‘ISO-8859-1’. Tentei essa estratégia para mostrar corretamente o texto:

import requests
from bs4 import BeautifulSoup

req = requests.get('https://sisgvarmazenamento.blob.core.windows.net/prd/PublicacaoPortal/Arquivos/201901.htm')
req.encoding

encoding = req.encoding
text = req.content

decoded_text = text.decode(encoding)

sopa = BeautifulSoup(decoded_text, "lxml")

sopa.find("h1")

E o resultado que aparece é:

<h1>
                        CÃMARA MUNICIPAL DE SÃO PAULO<br/></h1>

Quando copio e colo nesta tela aparece correto, mas no meu computador toda a acentuação está errada

Estou em uma máquina com Ubuntu

Por favor, alguém sabe uma forma correta de ler o encoding?

1 Curtida

São Paulo leu certo e Câmara leu errado? É só esse registro, Reinaldo? Parece até mais um erro de acentuação do que de encoding. (respondendo tb pra aproveitar e testar aqui o fórum) :wink:

Então Nati, essa seria um tipo de pergunta complexa, que apenas parece simples. Um outro programador me explicou no começo do ano. Seria uma questão que poderia gerar muita discussão.

Quando você recebe uma resposta, o Requests faz uma suposição sobre a codificação a ser usada para decodificar a resposta quando você acessa o atributo Response.text. As solicitações primeiro verificarão uma codificação no cabeçalho HTTP e, se nenhuma estiver presente, usarão o chardet para tentar adivinhar a codificação.

Mas a única vez que Requests não fará isso é se nenhum charset explícito estiver presente nos HTTP headers e o Content-Type header contiver texto. Nessa situação, o RFC 2616 especifica que o conjunto de caracteres padrão deve ser ISO-8859-1. Requests segue a especificação neste caso. Se você precisar de uma codificação diferente, poderá definir manualmente a propriedade Response.encoding ou usar o Response.content bruto.

Que é o caso desse site da Câmara de São Paulo, ele não está formatado com excelência - muitos sites públicos devem ter esse problema. Ao inspecionar os request headers mostra que, de fato, “nenhum charset explícito está presente nos HTTP headers e o Content-Type header contém texto”

req.headers['content-type']
'text/html'

Se você quiser posso postar a solução inteira

1 Curtida

Oi Reinaldo, acho que vale postar a solução inteira, sim. Assim, quem tiver esse problema no futuro, já vai saber como proceder…

Quando você recebe uma resposta, o Requests faz uma suposição sobre a codificação a ser usada para decodificar a resposta quando você acessa o atributo Response.text. Requests primeiro verifica uma codificação no cabeçalho HTTP e, se nenhuma estiver presente, usa o chardet para tentar adivinhar a codificação.

Mas a única vez que Requests não fará isso é se nenhum charset explícito estiver presente nos HTTP headers e o Content-Type header contiver texto. Nessa situação, o RFC 2616 especifica que o conjunto de caracteres padrão deve ser encoding ISO-8859-1. Requests segue a especificação neste caso. Se você precisar de uma codificação diferente, poderá definir manualmente a propriedade Response.encoding ou usar o Response.content bruto.

Que é o caso desse site da Câmara de São Paulo, ele não está formatado com excelência - muitos sites públicos devem ter esse problema. Ao inspecionar os request headers mostra que, de fato, nenhum charset explícito está presente nos HTTP headers e o Content-Type header contém texto.

req.headers['content-type']
'text/html'

Portanto, o requests segue fielmente o padrão e são decodificadas como ISO-8859-1 (latin-1).
No conteúdo da resposta, um conjunto de caracteres é especificado:

<META http-equiv="Content-Type" content="text/html; charset=utf-16">

No entanto, isso está errado: a decodificação como UTF-16 produz mojibake.

chardet identifica corretamente a codificação como UTF-8.

Então, para resumir:

 + Não há uma maneira geral de determinar a codificação de texto com precisão total
 + Nesse caso específico, a codificação correta é UTF-8.

Código final para resolver:

req.encoding = 'UTF-8'
soup = BeautifulSoup(req.text,'lxml')
soup.find('h1').text
'\r\n                        CÂMARA MUNICIPAL DE SÃO PAULO'

(Com a ajuda de snakecharmerb )

Se você trocar req.encoding no código por req.apparent_encoding provavelmente vai funcionar! Seria assim:

import requests
from bs4 import BeautifulSoup

url = "https://sisgvarmazenamento.blob.core.windows.net/prd/PublicacaoPortal/Arquivos/201901.htm"
response = requests.get(url)
html = response.content.decode(response.apparent_encoding)
sopa = BeautifulSoup(html, "lxml")
print(sopa.find("h1"))

Nota: response.content é o conteúdo devolvido pelo site em sua forma de bytes (sem decodificação), response.text é o conteúdo devolvido pelo site com a decodificação que a requests identificou (vinda do servidor) e response.apparent_encoding é a codificação que a chardet detectou baseada no response.content. Em geral response.content.decode(response.apparent_encoding) vai te dar um resultado mais correto que response.text.

2 Curtidas

Ah, e sobre detecção da codificação: a biblioteca requests usa a biblioteca chardet para fazer detecção, mas eu acho ela bem ruim. Caso precise fazer essa detecção de forma mais robusta (para casos em que o código que citei acima não funciona), você pode usar a biblioteca file-magic (eu a coloquei no Python Package Index). Eu criei um blog post (que contém um vídeo-tutorial) sobre como utilizar a file-magic.

2 Curtidas