Obs.: ainda não conhece o Scrapy? Então leia este tutorial.
Vez por outra os sistemas que a gente usa não entregam as informações da forma que desejamos. Seja o sistema do seu cartão de crédito que não lhe dá uma visualização legal dos seus gastos, ou até mesmo seu app de táxi que não lhe deixa fazer uma análise mais aprofundada dos trajetos que você tem feito.
Ah se todos eles tivessem um botãozinho “exportar tudo em JSON”! 🙂
A realidade é dura e a maioria dos apps não nos dão essa opção. Uma alternativa nesses casos é raspar os dados que desejamos do site do tal app, e isso envolve fazer seu web spider se autenticar no sistema. Nesse post, vamos ver como fazer para que um spider Scrapy faça login em um sistema qualquer.
Como funciona a autenticação em um website?
Existem várias maneiras diferentes, mas em linhas gerais a autenticação em websites funciona assim:
- O usuário acessa URL que contém formulário de login. Exemplo: http://quotes.toscrape.com/login
- O usuário preenche campos do formulário com suas credenciais e clica no botão de login
- O navegador envia os dados preenchidos para o servidor, no corpo de uma requisição HTTP do tipo POST
- O servidor valida as credenciais do usuário e envia de volta uma resposta contendo uma página indicando que o usuário se autenticou com sucesso e, juntamente com ela, um cookie de sessão.
O cookie de sessão é uma informação que o browser irá armazenar localmente e irá enviar juntamente com as requisições subsequentes ao mesmo website, de forma que o último possa identificar quem é o usuário fazendo tais requisições.
Assim sendo, nosso spider deverá reproduzir os passos recém descritos para que consiga se autenticar em um website. Então vamos a ele!
Nosso spider
Vamos construir um spider que se autentique em http://quotes.toscrape.com e de lá extraia o link para a página no Goodreads de cada autor, informação que só é visível para usuários autenticados.
Dê uma olhada no esqueleto do nosso spider:
# -*- coding:utf-8 -*-
import scrapy
class LoginSpider(scrapy.Spider):
name = 'login-spider'
start_urls = ['http://quotes.toscrape.com/login']
def parse(self, response):
# depois a gente vai implementar aqui o preenchimento do formulário
# contido em response e fazer a requisição de login
self.log('visitei a página de login: {}'.format(response.url))
def parse_author_links(self, response):
# aqui a gente vai extrair os links para as páginas dos autores
# ou seja, quando chegar aqui, o spider já estará autenticado
pass
Quando rodarmos nosso spider, o Scrapy vai chamar automaticamente o método parse() assim que a resposta para a URL http://quotes.toscrape.com/login tiver sido baixada e a mensagem de log será impressa. Vá em frente e rode o spider acima só pra ver isso acontecendo:
$ scrapy runspider loginspider.py
Manipulando o formulário
O formulário de login do nosso site está representado assim:
<form action="/login" method="post" accept-charset="utf-8" >
<input type="hidden" name="csrf_token" value="fHQgXTCzxs...aOkolIudtjV"/>
<label for="username">Username</label>
<input type="text" class="form-control" id="username" name="username" />
<label for="username">Password</label>
<input type="password" class="form-control" id="password" name="password" />
<input type="submit" value="Login" class="btn btn-primary" />
</form>
Perceba que temos 4 inputs nesse formulário: username, password, o botão de login em si e um campo oculto chamado csrf_token. Não vou entrar em detalhes sobre proteção CSRF, mas o que você precisa saber é que a maioria dos sites por aí vai ter um campo desses no formulário de login deles e que você precisa enviar tal campo juntamente com a requisição de autenticação. Caso contrário, o servidor não irá validar sua requisição.
No Scrapy, requisições do tipo POST podem ser feitas com um tipo especial de Request chamado FormRequest. Nossa requisição de autenticação será:
req = scrapy.FormRequest(
url='http://quotes.toscrape.com/login',
formdata={
'username': 'john.doe',
'password': 'anything',
'csrf_token': token,
},
callback=self.parse_author_links,
}
Alguns detalhes importantes:
- O valor do campo
csrf_tokené gerado dinamicamente para cada requisição à página de login. Assim, teremos que extrair ele da página de login antes de fazer a requisição. - A URL passada ao objeto FormRequest deve ser aquela encontrada no atributo
actiondo formulário de login. - As chaves do dicionário passado ao parâmetro
formdatadevem usar como nome o valor do atributonamedo input correspondente no formulário.
Agora que a gente já sabe como fazer uma requisição do tipo POST, vamos ao spider:
# -*- coding:utf-8 -*-
import scrapy
from scrapy.exceptions import CloseSpider
class LoginSpider(scrapy.Spider):
name = 'login-spider'
start_urls = ['http://quotes.toscrape.com/login']
def parse(self, response):
self.log('visitei a página de login: {}'.format(response.url))
token = response.css('input[name="csrf_token"]::attr(value)').extract_first()
yield scrapy.FormRequest(
url='http://quotes.toscrape.com/login',
formdata={
'username': 'john.doe',
'password': 'anything',
'csrf_token': token,
},
callback=self.parse_author_links,
)
def parse_author_links(self, response):
has_logout_link = response.css('a[href="/logout"]').extract_first()
if not has_logout_link:
raise CloseSpider('falha de autenticação')
self.log('acabei de fazer login')
links = response.css('.quote a[href*="goodreads.com"]::attr(href)').extract()
for link in links:
yield {'link': link}
next_page = response.css('li.next a::attr(href)').extract_first()
if next_page:
yield scrapy.Request(
url=response.urljoin(next_page),
callback=self.parse_author_links,
)
Na linha 11 (método parse()), extraímos o token da página para enviar junto com a requisição de autenticação e, logo em seguida, o método parse() gera a requisição POST para a URL de login. Repare que registramos o método self.parse_author_links como o callback para manipular a página enviada como resposta pelo servidor.
No método parse_author_links, verificamos se o botão de logout apareceu na página (o que indica que estamos autenticados) e, caso não esteja, encerramos a execução do spider (linha 25).
Depois disso, basta extrair as URLs dos autores (linhas 28-30). Veja a versão completa do spider aqui.
E o cookie de sessão?
Ah, lembra que falei no começo do artigo que o navegador armazena um cookie de sessão e envia ele junto com as requisições subsequentes pro mesmo site? Pois é, o Scrapy faz a mesma coisa pra gente. Ou seja, não precisamos nos preocupar em manter a sessão. 🙂
Simplificando um pouco
É bem comum termos que submeter valores presentes em campos ocultos (hidden) juntamente com os dados que preenchemos no formulário. Para facilitar a nossa vida, o Scrapy fornece um método chamado FormRequest.from_response(), que carrega automaticamente os valores dos campos do formulário que possuem valores default. Nosso método parse() ficaria assim se utilizássemos o from_response():
def parse(self, response):
self.log('visitei a página de login: {}'.format(response.url))
yield scrapy.FormRequest.from_response(
response,
formdata={
'username': 'john.doe',
'password': 'anything',
},
callback=self.parse_author_links,
)
Como você pode ver, não precisamos extrair e passar o csrf token, nem passar a URL do formulário. O from_response já faz isso pra gente. 🙂
Por fim
Agora você já pode fazer um spider para extrair aqueles dados que você precisa para o seu relatório. Mas, antes de fazer seu spider, verifique se ele não está violando os termos de serviço do seu banco, ou seja lá qual sistema que você estiver acessando. 🙂