Charming Python: Fácil Coleta de Dados da Web com Mechanize e Beautiful Soup

As ferramentas Python facilitam a extração e organização de dados de Web site

Para coletar dados de páginas da Web, a biblioteca mechanize automatiza a análise e a interação com Web sites. O mechanize permite preencher formulários e configurar e salvar cookies e oferece outras ferramentas variadas para fazer um script Python se parecer com um navegador da Web genuíno para um Web site interativo. Uma ferramenta associada frequentemente usada chamada Beautiful Soup ajuda um programa Python a entender o confuso "quase HTML" que Web sites tendem a conter.

David Mertz, Freelance writer, Gnosis Software, Inc.

David Mertz 的照片Para David Mertz, o mundo todo é um teste; ele devota sua carreira a fornecer instruções de testes marginais. Para saber mais sobre sua carreira, consulte sua página pessoal na Web. Ele escreve as colunas Charming Python e XML Matters do developerWorks desde 2000. Consulte seu livro, Text Processing in Python. É possível entrar em contato com David em mertz@gnosis.cx.



07/Dez/2009

Entre em contato com David

David é um de nossos mais populares e criativos autores. Navegue por todos os arquivos e tutoriais de David no developerWorks. Consulte o perfil de David e conecte-se com ele, outros autores e colegas leitores no My developerWorks.

Escrever scripts para interagir com Web sites é possível com os módulos básicos do Python, mas prefira não fazer isso se não for preciso. Os módulos urllib e urllib2 no Python 2.x, junto com os subpacotes urllib.* em Python 3.0, fazem uma tarefa transitável de busca de recursos nas extremidades de URLs. Porém, quando você deseja fazer qualquer tipo de interação moderadamente sofisticada com o conteúdo que localiza em uma página da Web, você realmente precisa da biblioteca mechanize (consulte Recursos para um link de download).

Uma das grandes dificuldades com a automação de análise da Web ou outras simulações de interação do usuário com Web sites é o uso do servidor de cookies para controlar o progresso da sessão. Obviamente, os cookies fazem parte de cabeçalhos HTTP e são inerentemente visíveis quando urllib abre recursos. Além disso, os módulos padrão Cookie (http.cookie em Python 3) e cookielib (http.cookiejar em Python 3) ajudam na manipulação desses cabeçalhos em um nível mais alto do que o processamento de texto bruto. Mesmo assim, fazer esta manipulação neste nível é mais incômodo do que necessário. A biblioteca mechanize considera esta manipulação em um nível mais alto de abstração e permite que o seu script—ou seu shell Python interativo—aja de maneira muito parecida com um navegador da Web real.

O mechanize do Python é inspirado pelo WWW:Mechanize do Perl, que tem uma gama de recursos parecida. É claro que, como um Pythonista de longa data, eu acho o mechanize mais robusto, o que parece seguir o padrão geral das duas linguagens.

Um amigo íntimo do mechanize é a igualmente excelente biblioteca Beautiful Soup (consulte Recursos para um link de download). Ela é um ótimo "parser medíocre" para códigos HTML não estritamente válidos que você frequentemente encontra em páginas da Web reais. Você não precisa usar o Beautiful Soup com o mechanize, nem vice-versa, mas, mais frequentemente do que o contrário, você desejará usar as duas ferramentas juntas à medida que interagir com a "Web existente de verdade".

Um exemplo da vida real

Eu uso o mechanize em vários projetos de programação. O mais recente foi um projeto para reunir uma lista de nomes correspondentes a alguns critérios de um Web site popular. Este site vem com alguns recursos de busca, mas não vem com nenhuma API oficial para executar tais buscas. Ainda que os leitores possam conseguir imaginar mais especificamente o que eu estava fazendo, eu mudarei aspectos específicos do código que vou apresentar para evitar fornecer informações em excesso sobre o site analisado ou sobre meu cliente. De forma geral, código muito parecido com o que eu apresento será comum para tarefas similares.


Ferramentas para iniciar

No processo de desenvolvimento real de código de análise da Web, eu acho inestimável poder efetuar peek, poke e prod no conteúdo das páginas da Web de uma maneira interativa para descobrir o que realmente ocorre em páginas da Web relacionadas. Geralmente, esses são conjuntos de páginas dentro de um site, que são gerados dinamicamente a partir de consultas (mas, por isso, tendo padrões consistentes) ou são pré-gerados seguindo modelos razoavelmente rígidos.

Uma maneira valioso de fazer esta experimentação interativa é usar o próprio mechanize dentro de um shell Python, especificamente dentro de um shell aprimorado como o IPython (consulte Recursos para obter um link). Ao fazer a exploração desta maneira, é possível solicitar vários recursos vinculados, enviar formulários, manter ou manipular cookies de site e assim por diante, antes de gravar seu script final que executa a interação que você deseja em produção.

Porém, eu acredito que grande parte da minha interação experimental com Web sites é mais bem executada dentro de um real navegador da Web moderno. Visualizar uma página convenientemente renderizada fornece um gestalt muito mais rápido de o que está acontecendo com uma determinada página ou formulário. O problema é que renderizar uma página sozinha somente fornece metade da história, talvez menos que a metade. Ter a "origem de página" o leva um pouco mais adiante. Para realmente entender o que está por trás de uma determinada página da Web ou de uma sequência de interações com um servidor da Web, acho que é necessário mais.

Para chegar tão fundo, geralmente eu uso o Firebug (consulte Recursos para obter um link) ou plug-ins Web Developer para Firefox (ou o menu integrado opcional Develop nas versões recentes do Safari, mas isso é para um público diferente). Todas essas ferramentas permitem fazer coisas como revelar campos de formulário, mostrar senhas, examinar o DOM de uma página, efetuar peek em Javascript ou executar Javascript, observar tráfego Ajax e mais. Comparar os benefícios e peculiaridades dessas ferramentas é um outro artigo completo, mas familiarize-se com eles se você faz qualquer programação orientada para Web.

Seja qual for a ferramenta específica que você experimente com um Web site com o qual deseja automatizar a interação, provavelmente você gastará mais horas descobrindo o que, de fato, um site está fazendo do que escrevendo o código mechanize incrivelmente compacto necessário para executar sua tarefa.


A análise de resultado da procura

Para os propósitos do projeto que eu mencionei acima, eu dividi meu script de centenas de linhas em duas funções:

  • Recuperar todos os resultados que me interessam
  • Extrair as informações que me interessam a partir dessas páginas recuperadas

Eu organizei o script desta maneira como uma conveniência de desenvolvimento; quando eu iniciei a tarefa, sabia que precisaria descobrir como fazer cada uma dessas duas coisas. Eu tinha noção de que as informações que eu queria estavam em uma coleta geral de páginas, mas eu ainda não havia examinado o layout específico dessas páginas.

Ao recuperar primeiro um lote de páginas e somente salvá-las em disco, eu poderia voltar à tarefa de extrair as informações do meu interesse desses arquivos salvos. É claro que, se a sua tarefa envolver o uso dessas informações recuperadas para formular novas interações dentro da mesma sessão, você precisará usar uma sequência um pouco diferente de etapas de desenvolvimento.

Desta forma, primeiro, vamos dar uma olhada em minha função fetch() :

Lista 1. Buscando o conteúdo da página
import sys, time, os from mechanize
import Browser

LOGIN_URL = 'http://www.example.com/login'
USERNAME = 'DavidMertz'
PASSWORD = 'TheSpanishInquisition'
SEARCH_URL = 'http://www.example.com/search?'
FIXED_QUERY = 'food=spam&' 'utensil=spork&' 'date=the_future&'
VARIABLE_QUERY = ['actor=%s' % actor for actor in
        ('Graham Chapman',
         'John Cleese',
         'Terry Gilliam',
         'Eric Idle',
         'Terry Jones',
         'Michael Palin')]

def fetch():
    result_no = 0                 # Number the output files
    br = Browser()                # Create a browser
    br.open(LOGIN_URL)            # Open the login page
    br.select_form(name="login")  # Find the login form
    br['username'] = USERNAME     # Set the form values
    br['password'] = PASSWORD
    resp = br.submit()            # Submit the form

    # Automatic redirect sometimes fails, follow manually when needed
    if 'Redirecting' in br.title():
        resp = br.follow_link(text_regex='click here')

    # Loop through the searches, keeping fixed query parameters
    for actor in in VARIABLE_QUERY:
        # I like to watch what's happening in the console
        print >> sys.stderr, '***', actor
        # Lets do the actual query now
        br.open(SEARCH_URL + FIXED_QUERY + actor)
        # The query actually gives us links to the content pages we like,
        # but there are some other links on the page that we ignore
        nice_links = [l for l in br.links()
                        if 'good_path' in l.url
                        and 'credential' in l.url]
        if not nice_links:        # Maybe the relevant results are empty
            break
        for link in nice_links:
            try:
                response = br.follow_link(link)
                # More console reporting on title of followed link page
                print >> sys.stderr, br.title()
                # Increment output filenames, open and write the file
                result_no += 1
                out = open(result_%04d' % result_no, 'w')
                print >> out, response.read()
                out.close()
            # Nothing ever goes perfectly, ignore if we do not get page
            except mechanize._response.httperror_seek_wrapper:
                print >> sys.stderr, "Response error (probably 404)"
            # Let's not hammer the site too much between fetches
            time.sleep(1)

Tendo feito minha exploração interativa deste site de interesse, eu descubro que as consultas que eu desejo executar possuem alguns elementos fixos e alguns elementos variáveis. Eu simplesmente as concateno juntas em um grande pedido GET e dou uma olhada na página "resultados". Em troca, essa lista de resultados contém links para os recursos que eu realmente desejo. Assim, eu os sigo (com alguns blocos try/except lançados para o caso de alguma coisa não funcionar ao longo do caminho) e salvo qualquer coisa que localizar nessas páginas de conteúdo.

Bem simples, não? O mechanize pode fazer mais que isso, mas este pequeno exemplo mostra uma ampla gama de seus recursos.


Processando os resultados

Neste ponto, terminamos com o mechanize; tudo o que falta é compreender esse monte de arquivos HTML que salvamos durante o loop fetch() . A natureza do processo em lote nos permite separá-los de forma limpa, mas, obviamente, em um programa diferente, fetch() e process() pode interagir mais intimamente. O Beautiful Soup torna o pós-processamento ainda mais fácil que a busca inicial.

Para esta tarefa em lote, desejamos produzir dados tabulares de valores separados por vírgula (CSV) a partir de algumas pequenas coisas que encontramos nessas várias páginas da Web que buscamos.

Lista 2. Criando dados ordenados a partir de um conjunto de dados randômicos com a Beautiful Soup
from glob import glob
from BeautifulSoup import BeautifulSoup

def process():
    print "!MOVIE,DIRECTOR,KEY_GRIP,THE_MOOSE"
    for fname in glob('result_*'):
        # Put that sloppy HTML into the soup
        soup = BeautifulSoup(open(fname))

        # Try to find the fields we want, but default to unknown values
        try:
            movie = soup.findAll('span', {'class':'movie_title'})[1].contents[0]
        except IndexError:
            fname = "UNKNOWN"

        try:
            director = soup.findAll('div', {'class':'director'})[1].contents[0]
        except IndexError:
            lname = "UNKNOWN"

        try:
            # Maybe multiple grips listed, key one should be in there
            grips = soup.findAll('p', {'id':'grip'})[0]
            grips = " ".join(grips.split())   # Normalize extra spaces
        except IndexError:
            title = "UNKNOWN"

        try:
            # Hide some stuff in the HTML <meta> tags
            moose = soup.findAll('meta', {'name':'shibboleth'})[0]['content']
        except IndexError:
            moose = "UNKNOWN"

        print '"%s","%s","%s","%s"' % (movie, director, grips, moose)

O código aqui em process() é uma primeira visualização impressionista do Beautiful Soup. Os leitores devem ler sua documentação para saber mais sobre detalhes do módulo, mas a sensação geral é bem representada neste fragmento. A maioria do código soup consiste em algumas chamadas .findAll() em uma página que pode ser apenas HTML aproximadamente bem formatado. Lançados aqui, estão alguns atributos .parent, nextSibling e previousSibling parecidos com DOM. Eles são semelhantes ao modo "quirks" de navegadores da Web. O que localizamos no soup não é bem uma árvore de análise; se parece mais com um saco cheio de vegetais que podem entrar na sopa (para forçar uma metáfora).


Conclusão

Pessoas antiquadas como eu, e mesmo alguns leitores mais jovens, se lembrarão do grande deleite do script com TCL Expect (ou com seus semelhantes escritos em Python e muitas outras linguagens). Automatizar a interação com shells, incluindo os remotos como telnet, ftp, ssh e parecidos, é relativamente direto desde que tudo seja exibido na sessão. A interação da Web é um pouco mais sutil quanto às informações que são divididas entre cabeçalhos e corpos, e vários recursos dependentes são frequentemente compactados juntos com links href , quadros, Ajax, e assim por diante. Em princípio, porém, você poderia somente usar uma ferramenta como wget para recuperar cada byte que um servidor da Web poderia fornecer e, então, executar o mesmo estilo dos scripts Expect como com outros protocolos de conexão.

Na prática, poucos programadores são tão comprometidos com abordagens antigas como a minha abordagem sugerida de wget + Expect. O mechanize ainda tem muito da mesma sensação familiar e confortante daqueles bons scripts Expect e é tão fácil de escrever quanto eles, se não for mais fácil. Os comando do objeto Browser() como .select_form(), .submit() e .follow_link() são, realmente, a maneira mais simples e mais óbvia de dizer "procure por isso e envie aquilo" enquanto compacta toda a beleza do estado sofisticado e da manipulação de sessão que gostaríamos em uma estrutura de automação da Web.

Recursos

Aprender

Obter produtos e tecnologias

  • Faça o download do mechanize e de sua documentação.
  • Faça o download do Beautiful Soup e de sua documentação.
  • IPython é uma versão maravilhosamente aprimorada do shell interativo nativo do Python que pode fazer coisas bem singulares como auxiliar em cálculos de paralelização; eu o uso principalmente por seus auxílios de interatividade como colorização de código, rechamada de linha de comandos melhorada, conclusão de guia, recursos de macro e ajuda interativa melhorada.
  • É possível instalar o Firebugque oferece uma abundância de ferramentas de edição, depuração e monitoramento de desenvolvimento na Web ao alcance de suas mãos enquanto você navega diretamente do menu Tools/Add-ons do Firefox 3.0+. É possível incluir a Web Developer extensionque inclui um menu e uma barra de ferramentas no navegador com várias ferramentas de desenvolvedor da Web da mesma forma.
  • Com o software de avaliação IBM, disponível para download diretamente do developerWorks, construa seu próximo projeto de desenvolvimento no Linux.

Discutir

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Linux, Software livre
ArticleID=453852
ArticleTitle=Charming Python: Fácil Coleta de Dados da Web com Mechanize e Beautiful Soup
publish-date=12072009