Passando dados da web para Beautiful Soup - Lista vazia
-
26-12-2019 - |
Pergunta
Verifiquei novamente meu código e observei operações comparáveis ao abrir uma URL para passar dados da web para o Beautiful Soup. Por algum motivo, meu código simplesmente não retorna nada, embora esteja na forma correta:
>>> from bs4 import BeautifulSoup
>>> from urllib3 import poolmanager
>>> connectBuilder = poolmanager.PoolManager()
>>> content = connectBuilder.urlopen('GET', 'http://www.crummy.com/software/BeautifulSoup/')
>>> content
<urllib3.response.HTTPResponse object at 0x00000000032EC390>
>>> soup = BeautifulSoup(content)
>>> soup.title
>>> soup.title.name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'name'
>>> soup.p
>>> soup.get_text()
''
>>> content.data
a stream of data follows...
Como mostrado, está claro que urlopen() retorna uma resposta HTTP que é capturada pela variável content, faz sentido que ele possa ler o status da resposta, mas depois de passada para o Beautiful Soup, os dados da web não são convertidos em um objeto Beautiful Soup (sopa variável).Você pode ver que tentei ler algumas tags e texto, o get_text() retorna uma lista vazia, isso é estranho.
Estranhamente, quando acesso os dados da web via content.data, os dados aparecem, mas não são úteis, pois não posso usar o Beautiful Soup para analisá-los.Qual é o meu problema?Obrigado.
Solução
Se você quiser apenas raspar a página, requests
obterá o conteúdo que você precisa:
from bs4 import BeautifulSoup
import requests
r = requests.get('http://www.crummy.com/software/BeautifulSoup/')
soup = BeautifulSoup(r.content)
In [59]: soup.title
Out[59]: <title>Beautiful Soup: We called him Tortoise because he taught us.</title>
In [60]: soup.title.name
Out[60]: 'title'
Outras dicas
urllib3 retorna um objeto Response, que contém o .data
que tem a carga útil do corpo pré-carregada.
De acordo com o início rápido principal exemplo de uso aqui, eu faria algo assim:
import urllib3
http = urllib3.PoolManager()
response = http.request('GET', 'http://www.crummy.com/software/BeautifulSoup/')
from bs4 import BeautifulSoup
soup = BeautifulSoup(response.data) # Note the use of the .data property
...
O resto deve funcionar conforme planejado.
--
Um pouco sobre o que deu errado no seu código original:
Você passou por todo response
objeto em vez da carga útil do corpo.Isso normalmente deveria estar bem porque o response
object é um objeto semelhante a um arquivo, exceto neste caso, o urllib3 já consome toda a resposta e a analisa para você, para que não haja mais nada a ser feito .read()
.É como passar um ponteiro de arquivo que já foi lido. .data
por outro lado, acessará os dados já lidos.
Se quiser usar objetos de resposta urllib3 como objetos semelhantes a arquivos, você precisará desabilitar o pré-carregamento de conteúdo, assim:
response = http.request('GET', 'http://www.crummy.com/software/BeautifulSoup/', preload_content=False)
soup = BeautifulSoup(response) # We can pass the original `response` object now.
Agora deve funcionar como você esperava.
Entendo que este comportamento não é muito óbvio e, como autor do urllib3, peço desculpas.:) Planejamos fazer preload_content=False
o padrão algum dia.Talvez algum dia em breve (Abri um issue aqui).
--
Uma nota rápida sobre .urlopen
contra .request
:
.urlopen
assume que você cuidará da codificação de quaisquer parâmetros passados para a solicitação.Neste caso, não há problema em usar .urlopen
porque você não está passando nenhum parâmetro para a solicitação, mas em geral .request
fará todo o trabalho extra para você, então é mais conveniente.
Se alguém quiser melhorar nossa documentação nesse sentido, ficaria muito grato.:) Por favor, envie um PR para https://github.com/shazow/urllib3 e adicione-se como colaborador!
Como mostrado, está claro que urlopen() retorna uma resposta HTTP que é capturada pela variável content…
O que você chamou content
não é o conteúdo, mas um objeto semelhante a um arquivo do qual você pode ler o conteúdo.BeautifulSoup fica perfeitamente feliz em aceitar tal coisa, mas não é muito útil imprimi-la para fins de depuração.Então, vamos ler o conteúdo dele para facilitar a depuração:
>>> response = connectBuilder.urlopen('GET', 'http://www.crummy.com/software/BeautifulSoup/')
>>> response
<urllib3.response.HTTPResponse object at 0x00000000032EC390>
>>> content = response.read()
>>> content
b''
Isso deve deixar bem claro que BeautifulSoup
não é o problema aqui.Mas continuando:
… mas depois de passado para o Beautiful Soup, os dados da web não são convertidos em um objeto Beautiful Soup (sopa variável).
Sim.O fato de que soup.title
deu-te None
em vez de levantar um AttributeError
é uma evidência muito boa, mas você pode testá-la diretamente:
>>> type(soup)
bs4.BeautifulSoup
Isso é definitivamente um BeautifulSoup
objeto.
Quando você passa BeautifulSoup
uma string vazia, exatamente o que você receberá dependerá de qual analisador está usando nos bastidores;se estiver contando com o stdlib Python 3.x, o que você obterá é um html
nó com um vazio head
, e vazio body
, e nada mais.Então, quando você procura um title
nó, não há um, e você obtém None
.
Então, como você conserta isso?
Como a documentação diz, você está usando "a chamada de nível mais baixo para fazer uma solicitação, então você precisará especificar todos os detalhes brutos". Quais são esses detalhes brutos?Honestamente, se você ainda não sabe, não deveria usar este método. Ensinando como lidar com os detalhes ocultos do urllib3
antes mesmo de você saber o básico, não estaria lhe prestando um serviço.
Na verdade, você realmente não precisa urllib3
aqui de jeito nenhum.Basta usar os módulos que acompanham o Python:
>>> # on Python 2.x, instead do: from urllib2 import urlopen
>>> from urllib.request import urlopen
>>> r = urlopen('http://www.crummy.com/software/BeautifulSoup/')
>>> soup = BeautifulSoup(r)
>>> soup.title.text
'Beautiful Soup: We called him Tortoise because he taught us.'
Meu lindo código de sopa estava funcionando em um ambiente (minha máquina local) e retornando uma lista vazia em outro (servidor Ubuntu 14).
Resolvi meu problema alterando a instalação.detalhes em outro tópico: