Esse projeto tem como intuito facilitar o entendimento dos processos de utilização
do framework Scrapy em conjunto com um deploy feito na nuvem usando Scrapy Cloud.
Creditos: video FreeCodeCamp
👣 Passo a passo:
Instalação e primeiro contato:
Instalação Scrapy
- Criar um novo projeto com ambiente virtual
- Abrir terminal e instalar o Scrapy com o comando “pip install Scrapy”
Criar um projeto de Spider
- Terminal: “scrapy startproject nomedoprojetoscraper”
Criar uma Spider
- Navegar até a pasta “spiders” do projeto Scrapy criado
- Terminal: “scrapy genspider spidername site.to.scrape.com”
Instalar ipython para facilitar os testes
- Terminal: “pip install ipython”
- No arquivo scrapy.cfg adicionar “SHELL= ipython” em baixo de default
Usando o terminal Ipython
-
Terminal: “scrapy shell” para ativar o terminal ipython
-
No terminal Ipython (chamaremos de ITerminal): “fetch('https://books.toscrape.com/')”
-
ITerminal: “response” ← <200 https://books.toscrape.com/>
-
Selecionando um elemento Iterminal: “response.css('article.product_pod')”
-
Sair do ITerminal usando: “exit”
-
Tipos de seleção Ipython:
- Salvar em uma variável: “books = response.css('article.product_pod')”
- Verificar tamanho da lista da variável: “len(books)”
- Obter um item da lista: “book = books[0]”
- Obter o texto de um elemento “a” dentro de um elemento “h3”: “book.css('h3 a::text').get()”
- Obter o texto de um elemento dentro de outro elemento pela Classe: “book.css('.product_price .price_color::text').get()”
- Obter o conteúdo de um atributo de um elemento: “book.css('h3 a').attrib['href']” ou “response.css('.next a::attr(href)').get()”
Alterando uma Spider nomedaspider.py (”bookspider.py”)
- Alterar o método parse que receberá a response igual ao ITerminal 1.1
Ativando e testando uma Spider pelo terminal
- Navegar até a pasta do projeto Scrapy, nesse caso bookscraper
- Terminal: “scrapy crawl bookspider” Nota: o item “'item_scraped_count': 20” referencia a quantidade de itens raspados.
Percorrendo próximas páginas
- Encontrar o elemento responsável pelo link da próxima página, nesse caso, “response.css('.next a::attr(href)').get()”
- Adicionar lógica para percorrer próximas páginas usando callback “yield response.follow(next_page_url, callback=self.parse)” 1.2
Intermediário:
Adicionando Coleta de páginas dentro da página principal
- Adicionar coleta de cada url de uma página do livro
- Adicionar método para coletar informações de uma página usando callback “yield response.follow(book_url, callback=self.parse_book_page)” 2
Exportar os dados manualmente para um arquivo
- Terminal: “scrapy crawl bookspider -O bookdata.csv” para criar em formato csv
- Terminal: “scrapy crawl bookspider -O bookdata.json” para criar em formato json
- Terminal: “scrapy crawl bookspider -o bookdata.json” para adicionar em formato json
Criando e populando um scrapy Item
Rodando uma pipeline para pré-processar um item
- Habilitar a spider no arquivo de configurações da spider “settings.py” descomentando o objeto ITEM_PIPELINES = {} Nota: Em “ITEM_PIPELINES = {"bookscraper.pipelines.BookscraperPipeline": 300, }” o número 300 representa a prioridade, onde, quanto menor o valor mais “cedo” a pipeline vai rodar.
- Alterar o método process_item do item no arquivo “pipelines.py” 4.
Salvando em um banco de dados
- Podemos adicionar no arquivo de configurações da spider “settings.py” um objeto FEEDS = {'booksdata.json': {'format': 'json'}} para definir um formato padrão de saída quando rodarmos o comando “scrapy crawl bookspider” que é equivalente ao comando “scrapy crawl bookspider -O cleandata.json”
- Podemos também sobrescrever uma configuração do arquivo settings.py dentro do arquivo bookspider.py (arquivo referente à spider), basta adicionar “custom_settings = { 'FEEDS': { 'booksdata.json': {'format': 'json'}, 'overwrite': True }}” ao código
- Baixar e instalar SQLITE.
- Adicionar uma classe no arquivo pipelines.py para lidar com o banco 5.
- Adicionar um item no objeto ITEM_PIPELINES no arquivo settings.py com: "bookscraper.pipelines.SaveToSQLitePipeline": 400,"
Evitando bloqueio de requisição de um site
Adicionando proxy às requisições
Rodando uma Spider na nuvem
Criando um projeto Scrapy Cloud 8
- Criar uma conta em “https://app.zyte.com”
- Ir até a aba Scrapy Cloud
- Criar um novo projeto clicando em “Start Project”
- Em Scrapy Cloud, navegar no projeto criado
- Ir na sessão “SPIDERS/Code & Deploys”
- Clicar no botão “Deploy My code”
Dando deploy do seu projeto
- No terminal do seu projeto instalar o “shub” com o comando “pip install shub”
- Terminal: “shub login"
- Fornecer a chave de API após “API key: SUA-CHAVE-API”
- Executar Deploy com o comando “shub deploy PROJECT-ID”
- Aguardar execução do deploy e verificar no Dashboard
Rodando o projeto
- No menu de navegação do site do Scrapy Cloud, ir em “JOBS/Dashboard”
- Clicar em “Run”
- Selecionar a Spider que deseja rodar
- Clicar em “Run”
Nota: Caso não seja importado automaticamente, adicionar ao arquivo “scrapinghub.yml” a linha de código “requirements_file: requirements.txt” para que seja mapeado o arquivo de requirements.txt do projeto. Ver tutorial
Mais detalhes:
Spider base criada a partir do comando “scrapy genspyder bookspider book.toscrape.com”
import scrapy
class BookspiderSpider(scrapy.Spider):
name = "bookspider"
allowed_domains = ["books.toscrape.com"]
start_urls = ["https://books.toscrape.com"]
def parse(self, response):
pass
1.1 Alterando o método parse para retornar um objeto com atributos vindos da response
import scrapy
class BookspiderSpider(scrapy.Spider):
name = "bookspider"
allowed_domains = ["books.toscrape.com"]
start_urls = ["https://books.toscrape.com"]
def parse(self, response):
pass
1.2 Adicionando coleta de páginas
import scrapy
class BookspiderSpider(scrapy.Spider):
name = "bookspider"
allowed_domains = ["books.toscrape.com"]
start_urls = ["https://books.toscrape.com"]
def parse(self, response):
books = response.css('article.product_pod')
for book in books:
yield{
'name': book.css('h3 a::text').get(),
'price': book.css('div p.price_color::text').get(),
'url': book.css('h3 a').attrib['href']
}
#------------------------------------------------------------------------------------------------
next_page = response.css('.next a::attr(href)').get()
if next_page is not None:
if 'catalogue/' in next_page:
next_page_url = 'https://books.toscrape.com/' + next_page
else:
next_page_url = 'https://books.toscrape.com/catalogue/' + next_page
yield response.follow(next_page_url, callback=self.parse)
2.Adicionando raspagem de cada página do livro
import scrapy
class BookspiderSpider(scrapy.Spider):
name = "bookspider"
allowed_domains = ["books.toscrape.com"]
start_urls = ["https://books.toscrape.com"]
def parse(self, response):
books = response.css('article.product_pod')
#---------------------------------------Alterado-------------------------------------------------
for book in books:
book_relative_url = book.css('h3 a::attr(href)').get()
if 'catalogue/' in book_relative_url:
book_url = 'https://books.toscrape.com/' + book_relative_url
else:
book_url = 'https://books.toscrape.com/catalogue/' + book_relative_url
yield response.follow(book_url, callback=self.parse_book_page)
#------------------------------------------------------------------------------------------------
next_page = response.css('.next a::attr(href)').get()
if next_page is not None:
if 'catalogue/' in next_page:
next_page_url = 'https://books.toscrape.com/' + next_page
else:
next_page_url = 'https://books.toscrape.com/catalogue/' + next_page
yield response.follow(next_page_url, callback=self.parse)
#----------------------------------------Adicionado----------------------------------------------
def parse_book_page(self, response):
table_rows = response.css("table tr")
yield {
'url': response.url,
'title': response.css('.product_main h1::text').get(),
'product_type': table_rows[1].css('td ::text').get(),
'price_excl_tax': table_rows[2].css('td ::text').get(),
'price_excl_tax': table_rows[2].css('td ::text').get(),
'tax': table_rows[4].css('td ::text').get(),
'availability': table_rows[5].css('td ::text').get(),
'num_reviews': table_rows[6].css('td ::text').get(),
'stars' : response.css('p.star-rating::attr(class)').get(),
'category': response.xpath("//ul[@class='breadcrumb']/li[@class='active']/preceding-sibling::li[1]/a/text()").get(),
'description': response.xpath("//div[@id='product_description']/following-sibling::p/text()").get(),
'price': response.css('p.price_color::text').get()
}
"""
Nota: Em category e description foi usado Xpath
no caso de category foi pego o elemento irmão (de mesmo nivel) anterior ao elemento li de classe 'active'
no caso da description foi pego o elemento irmão (de mesmo nivel) posterior ao elemento div de cladesse 'product_description'
"""
3.Adicionando um item
#items.py
class BookItem(scrapy.Item):
url = scrapy.Field()
title = scrapy.Field()
upc = scrapy.Field()
product_type = scrapy.Field()
price_excl_tax = scrapy.Field()
price_incl_tax = scrapy.Field()
tax = scrapy.Field()
availability = scrapy.Field()
num_reviews = scrapy.Field()
stars = scrapy.Field()
category = scrapy.Field()
description = scrapy.Field()
price = scrapy.Field()
Nota: “É possível serializar um campo ou vários campos criando uma função e passando ela como parametro serializer.”
def serialize_price(value):
return f'£ {str(value)}'
class BookItem(scrapy.Item):
url = scrapy.Field()
title = scrapy.Field()
upc = scrapy.Field()
product_type = scrapy.Field()
price_excl_tax = scrapy.Field()
price_incl_tax = scrapy.Field()
tax = scrapy.Field()
availability = scrapy.Field()
num_reviews = scrapy.Field()
stars = scrapy.Field()
category = scrapy.Field()
description = scrapy.Field()
price = scrapy.Field(serializer=serialize_price)
3.1 Populando a classe
#bookspider.py
import scrapy
from bookscraper.items import BookItem
class BookspiderSpider(scrapy.Spider):
name = "bookspider"
allowed_domains = ["books.toscrape.com"]
start_urls = ["https://books.toscrape.com"]
def parse(self, response):
books = response.css('article.product_pod')
for book in books:
book_relative_url = book.css('h3 a::attr(href)').get()
if 'catalogue/' in book_relative_url:
book_url = 'https://books.toscrape.com/' + book_relative_url
else:
book_url = 'https://books.toscrape.com/catalogue/' + book_relative_url
yield response.follow(book_url, callback=self.parse_book_page)
next_page = response.css('.next a::attr(href)').get()
if next_page is not None:
if 'catalogue/' in next_page:
next_page_url = 'https://books.toscrape.com/' + next_page
else:
next_page_url = 'https://books.toscrape.com/catalogue/' + next_page
yield response.follow(next_page_url, callback=self.parse)
#----------------------------Alterado---------------------------------------------
def parse_book_page(self, response):
table_rows = response.css("table tr")
book_item = BookItem()
book_item['url'] = response.url,
book_item['title'] = response.css('.product_main h1::text').get(),
book_item['upc'] = table_rows[0].css('td ::text').get(),
book_item['product_type'] = table_rows[1].css('td ::text').get(),
book_item['price_excl_tax'] = table_rows[2].css('td ::text').get(),
book_item['price_incl_tax'] = table_rows[3].css('td ::text').get(),
book_item['tax'] = table_rows[4].css('td ::text').get(),
book_item['availability'] = table_rows[5].css('td ::text').get(),
book_item['num_reviews'] = table_rows[6].css('td ::text').get(),
book_item['stars'] = response.css('p.star-rating::attr(class)').get(),
book_item['category'] = response.xpath("//ul[@class='breadcrumb']/li[@class='active']/preceding-sibling::li[1]/a/text()").get(),
book_item['description'] = response.xpath("//div[@id='product_description']/following-sibling::p/text()").get(),
book_item['price'] = response.css('p.price_color::text').get()
yield book_item
4 Criando uma pipeline
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html
# useful for handling different item types with a single interface
from itemadapter import ItemAdapter
class BookscraperPipeline:
def process_item(self, item, spider):adapter = ItemAdapter(item)
# Strip all whitespaces from strings
field_names = adapter.field_names()
for field_name in field_names:
if field_name != 'description':
value = adapter.get(field_name)
if isinstance(adapter[field_name], tuple):
adapter[field_name] = value[0].strip()
else:
adapter[field_name] = value.strip()
else:
value = adapter.get(field_name)
adapter[field_name] = str(value[0])
# Category & Product Type --> switch to lowercase
lowercase_keys = ['category', 'product_type']
for lowercase_key in lowercase_keys:
value = adapter.get(lowercase_key)
adapter[lowercase_key] = value.lower()
# Price --> convert to float
price_keys = ['price_excl_tax', 'price_incl_tax', 'tax', 'price']
for price_key in price_keys:
value = adapter.get(price_key)
value = value.replace('£', '')
adapter[price_key] = float(value)
# Availability --> extract number of books in stock (removing strings)
availability_string = adapter.get('availability')
split_string_array = availability_string.split('(')
if len(split_string_array) < 2:
adapter['availability'] = 0
else:
availability_string = split_string_array[1].split(' ')
adapter['availability'] = int(availability_string[0])
# Reviews --> convert string to int
num_reviews_string = adapter.get('num_reviews')
adapter['num_reviews'] = int(num_reviews_string)
# Stars --> covert text to number
stars_string = adapter.get('stars')
split_stars_array = stars_string.split(' ')
stars_text_value = split_stars_array[1].lower()
if stars_text_value == "zero":
adapter['stars'] = 0
elif stars_text_value == "one":
adapter['stars'] = 1
elif stars_text_value == "two":
adapter['stars'] = 2
elif stars_text_value == "three":
adapter['stars'] = 3
elif stars_text_value == "four":
adapter['stars'] = 4
elif stars_text_value == "five":
adapter['stars'] = 5
return item
5 Classe para lidar com o banco
Nota: Os metodos open_spider, close_spider e process_item serão encontrados pelo scrapy e são executados automaticamente no devido tempo de vida da spider.
#pipelines.py
class SaveToSQLitePipeline:
def open_spider(self, spider):
self.conn = sqlite3.connect('books.db')
self.cur = self.conn.cursor()
self.cur.execute("""
CREATE TABLE IF NOT EXISTS books (
id INTEGER PRIMARY KEY,
url TEXT,
title TEXT,
upc TEXT,
product_type TEXT,
price_excl_tax REAL,
price_incl_tax REAL,
tax REAL,
availability INTEGER,
num_reviews INTEGER,
stars REAL,
category TEXT,
description TEXT,
price REAL
);
""")
def close_spider(self, spider):
self.conn.close()
def process_item(self, item, spider):
query = """
INSERT INTO books (
url, title, upc, product_type, price_excl_tax, price_incl_tax,
tax, availability, num_reviews, stars, category, description, price
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"""
values = (
item['url'],
item['title'],
item['upc'],
item['product_type'],
item['price_excl_tax'],
item['price_incl_tax'],
item['tax'],
item['availability'],
item['num_reviews'],
item['stars'],
item['category'],
item['description'],
item['price']
)
self.cur.execute(query, values)
self.conn.commit()
return item
6 Criando cabeçalhos (headers)
#middlewares.py
class ScrapeOpsFakeBrowserHeaderAgentMiddleware:
@classmethod
def from_crawler(cls, crawler):
return cls(crawler.settings)
def __init__(self, settings):
self.scrapeops_api_key = settings.get('SCRAPEOPS_API_KEY')
self.scrapeops_endpoint = settings.get('SCRAPEOPS_FAKE_BROWSER_HEADER_ENDPOINT')
self.scrapeops_fake_browser_headers_active = settings.get('SCRAPEOPS_FAKE_BROWSER_HEADER_ENABLED')
self.scrapeops_num_results = settings.get('SCRAPEOPS_NUM_RESULTS')
self.headers_list = []
self._get_headers_list()
self._scrapeops_fake_browser_headers_enabled()
def _get_headers_list(self):
payload = {'api_key': self.scrapeops_api_key}
if self.scrapeops_num_results is not None:
payload['num_results'] = self.scrapeops_num_results
response = requests.get(self.scrapeops_endpoint, params=urlencode(payload))
json_response = response.json()
self.headers_list = json_response.get('result', [])
def _get_random_browser_header(self):
random_index = randint(0, len(self.headers_list) - 1)
return self.headers_list[random_index]
def _scrapeops_fake_browser_headers_enabled(self):
if self.scrapeops_api_key is None or self.scrapeops_api_key == '' or self.scrapeops_fake_browser_headers_active == False:
self.scrapeops_fake_browser_headers_active = False
else:
self.scrapeops_fake_browser_headers_active = True
def process_request(self, request, spider):
random_browser_header = self._get_random_browser_header()
request.headers['user-agent'] = random_browser_header['user-agent']
request.headers['accept'] = random_browser_header['accept']
request.headers['sec-ch-ua'] = random_browser_header['sec-ch-ua']
request.headers['sec-ch-ua-mobile'] = random_browser_header['sec-ch-ua-mobile']
request.headers['sec-ch-ua-platform'] = random_browser_header['sec-ch-ua-platform']
request.headers['sec-fetch-site'] = random_browser_header['sec-fetch-site']
request.headers['sec-fetch-mod'] = random_browser_header['sec-fetch-mod']
request.headers['sec-fetch-user'] = random_browser_header['sec-fetch-user']
request.headers['accept-encoding'] = random_browser_header['accept-encoding']
request.headers['accept-language'] = random_browser_header['accept-language']
print(random_browser_header)
6.1 Adicionando variáveis
#settings.py
SCRAPEOPS_API_KEY = 'a889d691-4e9a-4c25-a26b-a7faec67ebfe' #api-key-just-for-exampl;e
# SCRAPEOPS_FAKE_USER_AGENT_ENDPOINT = 'https://headers.scrapeops.io/v1/user-agents'
SCRAPEOPS_FAKE_BROWSER_HEADER_ENDPOINT = 'https://headers.scrapeops.io/v1/browser-headers'
# SCRAPEOPS_FAKE_USER_AGENT_ENABLED = True
SCRAPEOPS_FAKE_BROWSER_HEADER_ENABLED = True
SCRAPEOPS_NUM_RESULTS = 50
6.2 Adicionar a classe ao middlewares
#settings.py
DOWNLOADER_MIDDLEWARES = {
# "bookscraper_project.middlewares.BookscraperDownloaderMiddleware": 543,
# 'bookscraper.middlewares.ScrapeOpsFakeUserAgentMiddleware': 400,
'bookscraper.middlewares.ScrapeOpsFakeBrowserHeaderAgentMiddleware': 400,
}
7 Configurações de proxy
Terminal: “pip install scrapy-rotating-proxies”
Adicionar variáveis de ambiente
#settings.py
ROTATING_PROXY_LIST = [
'45.70.204.233:4145',
'45.7.177.224:39867',
'187.73.55.66:5678',
]
DOWNLOADER_MIDDLEWARES = {
# "bookscraper_project.middlewares.BookscraperDownloaderMiddleware": 543,
# 'bookscraper.middlewares.ScrapeOpsFakeUserAgentMiddleware': 400,
'bookscraper.middlewares.ScrapeOpsFakeBrowserHeaderAgentMiddleware': 400,
'rotating_proxies.middlewares.RotatingProxyMiddleware': 610,
'rotating_proxies.middlewares.BanDetectionMiddleware': 620,
}
7.1 Alternativa usando serviço pago smart proxy
Site: https://dashboard.smartproxy.com
Tutorial: freeCode
Depois de comprar um plano seguir os passos:
Criar um usuario
Adicionar configurações
Obter o endpoint
Criar uma classe para o proxy
Adicionar variáveis de ambiente
Adicionar middleware
7.2 Usando scrapeops proxy api
Terminal: “pip install scrapeops-scrapy-proxy-sdk”
Adicionar variável de ambiente
# settings.py
SCRAPEOPS_API_KEY = 'YOUR_API_KEY'
SCRAPEOPS_PROXY_ENABLED = True
SCRAPEOPS_PROXY_SETTINGS = {'country': 'us'}
DOWNLOADER_MIDDLEWARES = {
'scrapeops_scrapy_proxy_sdk.scrapeops_scrapy_proxy_sdk.ScrapeOpsScrapyProxySdk': 725,
}
Adicionar middleware para gerenciar as urls.
## middlewares.py
from urllib.parse import urlencode
from scrapy import Request
class ScrapeOpsProxyMiddleware:
@classmethod
def from_crawler(cls, crawler):
return cls(crawler.settings)
def __init__(self, settings):
self.scrapeops_api_key = settings.get('SCRAPEOPS_API_KEY')
self.scrapeops_endpoint = 'https://proxy.scrapeops.io/v1/?'
self.scrapeops_proxy_active = settings.get('SCRAPEOPS_PROXY_ENABLED', False)
self.scrapeops_proxy_settings = {}
self._clean_proxy_settings(settings.get('SCRAPEOPS_PROXY_SETTINGS'))
@staticmethod
def _replace_response_url(response):
real_url = response.headers.get(
'Sops-Final-Url', def_val=response.url)
return response.replace(
url=real_url.decode(response.headers.encoding))
def _clean_proxy_settings(self, proxy_settings):
if proxy_settings is not None:
for key, value in proxy_settings.items():
clean_key = key.replace('sops_', '')
self.scrapeops_proxy_settings[clean_key] = value
def _get_scrapeops_url(self, request):
payload = {'api_key': self.scrapeops_api_key, 'url': request.url}
## Global Request Settings
if self.scrapeops_proxy_settings is not None:
for key, value in self.scrapeops_proxy_settings.items():
payload[key] = value
## Request Level Settings
for key, value in request.meta.items():
if 'sops_' in key:
clean_key = key.replace('sops_', '')
payload[clean_key] = value
proxy_url = self.scrapeops_endpoint + urlencode(payload)
return proxy_url
def _scrapeops_proxy_enabled(self):
if self.scrapeops_api_key is None or self.scrapeops_api_key == '' or self.scrapeops_proxy_active == False:
return False
return True
def process_request(self, request, spider):
if self._scrapeops_proxy_enabled is False or self.scrapeops_endpoint in request.url:
return None
scrapeops_url = self._get_scrapeops_url(request)
new_request = request.replace(
cls=Request, url=scrapeops_url, meta=request.meta)
return new_request
def process_response(self, request, response, spider):
new_response = self._replace_response_url(response)
return new_response
8 Rodando projeto na nuvem usando Scrapy Cloud
Criar uma conta em “https://app.zyte.com/account/login/?next=/o/599648/scrapycloud/discover”
no menu lateral navegar até scrapy Cloud
Clicar em start project
Dar um nome ao projeto
Navegar a pagina de Code & Deploy 1.0
Navegar a pagina de Code & Deploy 1.1
Instalar shub no projeto (O projeto atual foi criado no Pycharm então foi usado o próprio console do Pycharm)
Logar no shub 1.0: digitar comando de login
Logar no shub 1.1: Inserir chave API
Executar Deploy do projeto para o shub
Aguardar o Deplyoy
Sucesso!
Inserir o nome da Spider e Clicar em Run
Navegar até o Dashboard do projeto e clicar em Run
Virificar conclusão na seção “Completed”
Acompanhar execução na seção “Running”
No caso atual ocorreram 4 erros, para investigar melhor clicaremos no numero logo abaixo da coluna “Errors”na tabela da seção “Completed”.
Podemos investigar os erros na seguinte página, podendo expandir cada erro para ver o log do erro em específico.
O erro é decorrido da falta do módulo “itemadapter” que utilizamos em uma classe dentro do arquivo pupelines.py para indicar um livro como um item
O erro em questão está ocorrendo pois no deploy do projeto não estamos indicando os requisitos que o projeto está utilizando. para isso criaremos um arquivo requirements.txt na raiz do nosso projeto e adicionaremos a versão do itemadapter que estamos utilizando
Em seguida no arquivo scrapinghub.yml adicionaremos a linha de código “requirements_file: requirements.txt” logo a baixo do id do projeto para indicar o caminho do nosso arquivo requirements.
Executaremos novamente o Deploy do projeto
No site de gerenciamento de deploy iremos navegar até a seção de deploy para verificar o andamento do nosso deploy
Aguardaremos o progesso do deploy. Note que temos um numero 2 que indica que esse é o segundo deploy desse projeto.
Indicação de sucesso do deploy.
Podemos clicar na seção que acabamos de implantar(Deploy) e navegar até a aba “Requirements” para verificar que nosso requirements.txt foi implementado com sucesso!
Seguiremos os passos de execução da spider e se tudo correr bem teremos uma execução em andamento da nossa spyder. caso contrario investigue os erros para resolver o problema.
Agora no Dashboard podemos ver que a spyder rodou com sucesso e tambem podemos ver a que falhou.
Clicando no numero do item podemos verificar os itens coletados.
Nessa parte também é possível baixar todos os itens em um formato desejado dentre os disponiveis.