Voltar ao blogue
Guias
Mihai MaximLast updated on Mar 31, 202611 min read

Web scraping com o Scrapy: a maneira fácil

Web scraping com o Scrapy: a maneira fácil

Web scraping com o Scrapy

O Scrapy é uma poderosa biblioteca Python para extrair dados de sites. É rápido, eficiente e fácil de usar – acredite em mim, já passei por isso. Quer seja um cientista de dados, um programador ou alguém que adora brincar com dados, o Scrapy tem algo para lhe oferecer. E o melhor de tudo é que é gratuito e de código aberto.

Os projetos Scrapy vêm com uma estrutura de ficheiros que o ajuda a organizar o seu código e os seus dados. Facilita a criação e a manutenção de web scrapers, por isso vale definitivamente a pena considerar se estiver a planear fazer qualquer trabalho sério de web scraping. Web scraping com o Scrapy É como ter um assistente útil (embora virtual) ao seu lado enquanto embarca na sua jornada de extração de dados.

O que vamos construir

Quando se está a aprender uma nova competência, uma coisa é ler sobre ela e outra é realmente pô-la em prática. É por isso que decidimos construir um scraper juntos à medida que avançamos neste guia. É a melhor forma de obter uma compreensão prática de como funciona o web scraping com o Scrapy.

Então, o que vamos construir, exatamente? Vamos construir um scraper que extrai definições de palavras do site Urban Dictionary. É um alvo divertido para o scraping e ajudará a tornar o processo de aprendizagem mais agradável. O nosso scraper será simples – irá devolver as definições de várias palavras encontradas no site Urban Dictionary. Iremos utilizar o suporte integrado do Scrapy para selecionar e extrair dados de documentos HTML, de modo a obter as definições de que precisamos.

Então, vamos começar! Na próxima secção, vamos rever os pré-requisitos de que precisará para acompanhar este tutorial. Até lá!

Pré-requisitos

Antes de nos dedicarmos à construção do nosso scraper, há algumas coisas que terá de configurar. Nesta secção, abordaremos como instalar o Scrapy e configurar um ambiente virtual para o nosso projeto. A documentação do Scrapy sugere a instalação do Scrapy num ambiente virtual dedicado. Ao fazê-lo, evitará quaisquer conflitos com os pacotes do seu sistema.

Estou a executar o Scrapy no Ubuntu 22.04.1 WSL (Subsistema Windows para Linux), por isso vou configurar um ambiente virtual para a minha máquina.

Recomendo que leia o capítulo «Compreender a Estrutura de Pastas» para compreender totalmente as ferramentas com que estamos a trabalhar. Além disso, dê uma vista de olhos ao capítulo «Scrapy Shell», pois tornará a sua experiência de desenvolvimento muito mais fácil.

Configurar um ambiente virtual Python

Para configurar um ambiente virtual para Python no Ubuntu, pode usar o comando mkvirtualenv. Primeiro, certifique-se de que tem o virtualenv e o virtualenvwrapper instalados:

$ sudo apt-get install virtualenv virtualenvwrapper

Adicione estas linhas no final do seu ficheiro .bashrc:

export WORKON_HOME=$HOME/.virtualenvs

export PROJECT_HOME=$HOME/Deve

export VIRTUALENVWRAPPER_PYTHON='/usr/bin/python3'

source /usr/local/bin/virtualenvwrapper.sh

Usei o Vim para editar o ficheiro, mas pode escolher o editor que preferir:

vim ~/.bashrc

// Use ctr + i to enter insert mode, use the down arrow to scroll to the bottom of the file.

// Paste the lines at the end of the file.

// Hit escape to exit insert mode, type wq and hit enter to save the changes and exit Vim.

Em seguida, crie um novo ambiente virtual com o mkvirtualenv:

$ mkvirtualenv scrapy_env

Agora deverá ver um (scrapy_env) acrescentado no início da linha do seu terminal.

Para sair do ambiente virtual, digite $ deactivate

Para voltar ao ambiente virtual scrapy_env, digite $ workon scrapy_env

Instalar o Scrapy

Pode instalar o Scrapy com o gestor de pacotes pip:

$ pip install scrapy

Isto irá instalar a versão mais recente do Scrapy.

Pode criar um novo projeto com o comando scrapy startproject:

$ scrapy startproject myproject

Isto irá inicializar um novo projeto Scrapy chamado "myproject". Deverá conter a estrutura padrão do projeto.

Compreender a estrutura de pastas

Esta é a estrutura padrão do projeto:

myproject

├── myproject

│ ├── __init__.py

│ ├── items.py

│ ├── middlewares.py

│ ├── pipelines.py

│ ├── settings.py

│ └── spiders

│ └── __init__.py

└── scrapy.cfg

items.py

items.py é um modelo para os dados extraídos. Este modelo será utilizado para armazenar os dados que extrair do site.

Exemplo:

import scrapy

class Product(scrapy.Item):

    name = scrapy.Field()

    price = scrapy.Field()

    description = scrapy.Field()

Aqui definimos um Item chamado Product. Pode ser utilizado por um Spider (ver /spiders) para armazenar informações sobre o nome, preço e descrição de um produto.

/spiders

/spiders é uma pasta que contém classes de Spider. No Scrapy, os Spiders são classes que definem como um site deve ser rastreado.

Exemplo:

import scrapy

from myproject.items import Product

class MySpider(scrapy.Spider):

    name = 'myspider'

    start_urls = ['<example_website_url>']

    

    def parse(self, response):

        # Extract the data for each product

        for product_div in response.css('div.product'):

            product = Product()

            product['name'] = product_div.css('h3.name::text').get()

            product['price'] = product_div.css('span.price::text').get()

            product['description'] = product_div.css('p.description::text').get()

            yield product

O “spider” percorre os start_urls, extrai o nome, o preço e a descrição de todos os produtos encontrados nas páginas (usando seletores CSS) e armazena os dados no Item Product (ver items.py). Em seguida, ele “yields” esses itens, o que faz com que o Scrapy os passe para o próximo componente no pipeline (ver pipelines.py).

Yield é uma palavra-chave em Python que permite que uma função retorne um valor sem terminar a função. Em vez disso, produz o valor e suspende a execução da função até que o próximo valor seja solicitado.

Por exemplo:

def count_up_to(max):

    count = 1

    while count <= max:

        yield count

        count += 1

for number in count_up_to(5):

    print(number)

// returns 1 2 3 4 5 (each on a new line)  

pipelines.py

Os pipelines são responsáveis por processar os itens (ver items.py e /spiders) que são extraídos pelos spiders. Pode usá-los para limpar o HTML, validar os dados e exportá-los para um formato personalizado ou guardá-los numa base de dados.

Exemplo:

import pymongo

class MongoPipeline(object):

    def __init__(self):

        self.conn = pymongo.MongoClient('localhost', 27017)

        self.db = self.conn['mydatabase']

        self.product_collection = self.db['products']

        self.other_collection = self.db['other']

        

    def process_item(self, item, spider):

        if spider.name == 'product_spider':

            //insert item in the product_collection

        elif spider.name == 'other_spider':

            //insert item in the other_collection

        return item

Criámos um Pipeline chamado MongoPipeline. Este liga-se a duas coleções MongoDB (product_collection e other_collection). O pipeline recebe itens (ver items.py) dos spiders (ver /spiders) e processa-os na função process_item. Neste exemplo, a função process_item adiciona os itens às suas coleções designadas.

settings.py

O settings.py armazena uma variedade de configurações que controlam o comportamento do projeto Scrapy, tais como os pipelines, middlewares e extensões que devem ser utilizados, bem como configurações relacionadas com a forma como o projeto deve lidar com pedidos e respostas.

Por exemplo, pode utilizá-lo para definir a ordem de execução dos pipelines (ver pipelines.py):

ITEM_PIPELINES = {

    'myproject.pipelines.MongoPipeline': 300,

    'myproject.pipelines.JsonPipeline': 302,

}

// MongoPipeline will execute before JsonPipeline, because it has a lower order number(300)

Ou definir um contentor de exportação:

FEEDS = {

    'items': {'uri': 'file:///tmp/items.json', 'format': 'json'},

}

// this will save the scraped data to a items.json

scrappy.cfg

O scrapy.cfg é o ficheiro de configuração para as principais definições do projeto.

[settings]

default = [name of the project].settings

[deploy]

#url = http://localhost:6800/

project = [name of the project]

middlewares.py

Existem dois tipos de middlewares no Scrapy: middlewares de downloader e middlewares de spider.

Os middlewares de downloader são componentes que podem ser utilizados para modificar pedidos e respostas, tratar erros e implementar lógica de download personalizada. Estão situados entre o spider e o downloader do Scrapy.

Os middlewares de spider são componentes que podem ser utilizados para implementar lógica de processamento personalizada. Estão situados entre o motor e o spider.

O Scrapy Shell

Antes de embarcarmos na emocionante jornada de implementar o nosso scraper do Urban Dictionary, devemos primeiro familiarizar-nos com o Scrapy Shell. Esta consola interativa permite-nos testar a nossa lógica de scraping e ver os resultados em tempo real. É como uma caixa de areia virtual onde podemos brincar e afinar a nossa abordagem antes de soltar o nosso Spider na web. Acredite em mim, isso vai poupar-lhe muito tempo e dores de cabeça a longo prazo. Então, vamos divertir-nos um pouco e conhecer o Scrapy Shell.

Abrir o shell

Para abrir o Scrapy Shell, primeiro terá de navegar até ao diretório do seu projeto Scrapy no seu terminal. Depois, basta executar o seguinte comando:

scrapy shell

Isto irá abrir o Scrapy Shell e ser-lhe-á apresentado um prompt onde pode introduzir e executar comandos do Scrapy. Também pode passar um URL como argumento para o comando do shell para extrair diretamente uma página web, assim:

scrapy shell <url>

Por exemplo:

scrapy shell https://www.urbandictionary.com/define.php?term=YOLO

Irá devolver (no objeto de resposta) o HTML da página web que contém as definições da palavra YOLO (no Urban Dictionary).

Em alternativa, assim que entrar no shell, pode utilizar o comando fetch para obter uma página web.

fetch('https://www.urbandictionary.com/define.php?term=YOLO')

Também pode iniciar o shell com o parâmetro nolog para que não exiba registos:

scrapy shell --nolog

Trabalhar com o shell

Neste exemplo, recuperei o URL “https://www.urbandictionary.com/define.php?term=YOLO” e guardei o HTML no ficheiro test_output.html.

(scrapy_env) mihai@DESKTOP-0RN92KH:~/myproject$ scrapy shell --nolog

[s] Available Scrapy objects:

[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)

[s] crawler <scrapy.crawler.Crawler object at 0x7f1eef80f6a0>

[s] item {}

[s] settings <scrapy.settings.Settings object at 0x7f1eef80f4c0>

[s] Useful shortcuts:

[s] fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)

[s] fetch(req) Fetch a scrapy.Request and update local objects

[s] shelp() Shell help (print this help)

[s] view(response) View response in a browser

>>> response // response is empty

>>> fetch('https://www.urbandictionary.com/define.php?term=YOLO')

>>> response

<200 https://www.urbandictionary.com/define.php?term=Yolo>

>>> with open('test_output.html', 'w') as f:

... f.write(response.text)

...

118260

Agora vamos inspecionar o test_output.html e identificar os seletores de que precisamos para extrair os dados para o nosso scraper do Urban Dictionary.

Podemos observar que:

  • Cada contêiner de definição de palavra possui a classe “definition”.
  • O significado da palavra encontra-se dentro do div com a classe «meaning».
  • Os exemplos da palavra encontram-se dentro do div com a classe «example».
  • As informações sobre o autor e a data da publicação encontram-se dentro do div com a classe “contributor”.

Agora vamos testar alguns seletores no Scrapy Shell:

Para obter referências a todos os contentores de definição, podemos usar seletores CSS ou XPath:

Pode saber mais sobre seletores XPath aqui: https://www.webscrapingapi.com/the-ultimate-xpath-cheat-sheet

definitions = response.css('div.definition')

definitions = response.xpath('//div[contains(@class,"definition")]')

Devemos extrair o significado, o exemplo e as informações da publicação de cada contentor de definição. Vamos testar alguns seletores com o primeiro contentor:

>>> first_def = definitions[0]

>>> meaning = first_def.css('div.meaning').xpath(".//text()").extract()

>>> meaning

['Yolo ', 'means', ', '', 'You Only Live Once', ''.']

>>> meaning = "".join(meaning)

>>> meaning

'Yolo means, 'You Only Live Once'.'

>>> example = first_def.css('div.example').xpath(".//text()").extract()

>>> example = "".join(example)

>>> example

'"Put your seatbelt on." Jessica said.\r"HAH, YOLO!" Replies Anna.\r(They then proceed to have a car crash. Long story short...Wear a seatbelt.)'

>>> post_data = first_def.css('div.contributor').xpath(".//text()").extract()

>>> post_data

['by ', 'Soy ugly', ' April 24, 2019']

Ao utilizar o Scrapy Shell, conseguimos encontrar rapidamente um seletor geral que se adequa às nossas necessidades.

definition.css('div.<meaning|example|contributor>').xpath(".//text()").extract()

// returns an array with all the text found inside the <meaning|example|contributor>

ex: ['Yolo ', 'means', ', '', 'You Only Live Once', ''.']

Para saber mais sobre os seletores do Scrapy, consulte a documentação. https://docs.scrapy.org/en/latest/topics/selectors.html

Implementação do scraper do Urban Dictionary

Excelente trabalho! Agora que já apanhou o jeito de usar o Scrapy Shell e compreende o funcionamento interno de um projeto Scrapy, está na hora de mergulhar na implementação do nosso scraper do Urban Dictionary. A esta altura, já deve sentir-se confiante e pronto para assumir a tarefa de extrair todas aquelas definições de palavras hilariantes (e, por vezes, questionáveis) da web. Portanto, sem mais delongas, vamos começar a construir o nosso scraper!

Definir um Item

Primeiro, vamos implementar um Item: (ver items.py)

class UrbanDictionaryItem(scrapy.Item):

    meaning = scrapy.Field()

    author = scrapy.Field()

    date = scrapy.Field()

    example = scrapy.Field()

Esta estrutura irá armazenar os dados extraídos pelo Spider.

Definir um Spider

É assim que vamos definir o nosso Spider (ver /spiders):

import scrapy

from ..items import UrbanDictionaryItem

class UrbanDictionarySpider(scrapy.Spider):

name = 'urban_dictionary'

start_urls = ['https://www.urbandictionary.com/define.php?term=Yolo']

def parse(self, response):

definitions = response.css('div.definition')

para cada definição em definições:

item = UrbanDictionaryItem()

item['significado'] = definição.css('div.significado').xpath(".//text()").extract()

item['exemplo'] = definição.css('div.exemplo').xpath(".//text()").extract()

autor = definição.css('div.contributor').xpath(".//text()").extract()

item['date'] = author[2]

item['autor'] = autor[1]

yield item

Para executar o spider do urban_dictionary, utilize o seguinte comando:

scrapy crawl urban_dictionary

// the results should appear in the console (most probably at the top of the logs)

Criação de um Pipeline

Neste momento, os dados não estão sanitizados.

{'author': 'Soy ugly',

 'date': ' April 24, 2019',

 'example': ['“Put your ',

             'seatbelt',

             ' on.” Jessica said.\n',

             '“HAH, YOLO!” Replies Anna.\n',

             '(They then proceed to have a ',

             'car crash',

             '. ',

             'Long story short',

             '...Wear a seatbelt.)'],

 'meaning': ['Yolo ', 'means', ', ‘', 'You Only Live Once', '’.']}

Queremos modificar os campos “example” e “meaning” para que contenham cadeias de caracteres, em vez de matrizes. Para tal, vamos escrever um Pipeline (ver pipelines.py) que transformará as matrizes em cadeias de caracteres, concatenando as palavras.

class SanitizePipeline:

    def process_item(self, item, spider):

        # Sanitize the 'meaning' field

        item['meaning'] = "".join(item['meaning'])

        # Sanitize the 'example' field

        item['example'] = "".join(item['example'])

       

        # Sanitize the 'date' field

        item['date'] = item['date'].strip() 

        return item

 //ex: ['Yolo ', 'means', ', ‘', 'You Only Live Once', '’.'] turns to 

       'Yolo means, ‘You Only Live Once’.'

Ativar o Pipeline

Depois de definirmos o Pipeline, precisamos de o ativar. Se não o fizermos, os nossos objetos UrbanDictionaryItem não serão sanitizados. Para isso, adicione-o ao seu ficheiro settings.py (ver settings.py):

 ITEM_PIPELINES = {

     'myproject.pipelines.SanitizePipeline': 1,

 }

Já que está nisso, pode também especificar um ficheiro de saída para onde os dados extraídos serão colocados.

 FEEDS = {

     'items': {'uri': 'file:///tmp/items.json', 'format': 'json'},

 }

  // this will put the scraped data in an items.json file.

Opcional: Implementar um middleware proxy

O web scraping pode ser um pouco complicado. Um dos principais problemas com que nos deparamos frequentemente é que muitos sites exigem a renderização de JavaScript para exibir totalmente o seu conteúdo. Isto pode causar enormes problemas para nós, como web scrapers, uma vez que as nossas ferramentas muitas vezes não têm a capacidade de executar JavaScript como um navegador web normal. Isto pode levar à extração de dados incompletos ou, pior ainda, ao banimento do nosso IP do site por fazer demasiados pedidos num curto período de tempo.

A nossa solução para este problema é o WebScrapingApi. Com o nosso serviço, basta fazer pedidos à API e esta tratará de todo o trabalho pesado por si. Executará JavaScript, alternará proxies e até lidará com CAPTCHAs, garantindo que consegue extrair dados até dos sites mais difíceis com facilidade.

Um middleware de proxy encaminhará todas as solicitações de busca feitas pelo Scrapy para o servidor proxy. O servidor proxy fará então a solicitação por nós e retornará o resultado.

Primeiro, vamos definir uma classe ProxyMiddleware dentro do ficheiro middlewares.py.


import base64

class ProxyMiddleware:

    def process_request(self, request, spider):

        # Set the proxy for the request

        request.meta['proxy'] = "http://proxy.webscrapingapi.com:80"

        request.meta['verify'] = False

        # Set the proxy authentication for the request

        proxy_user_pass =  "webscrapingapi.proxy_type=residential.render_js=1:<API_KEY>"

        encoded_user_pass = base64.b64encode(proxy_user_pass.encode()).decode()

        request.headers['Proxy-Authorization'] = f'Basic {encoded_user_pass}'


Neste exemplo, utilizámos o servidor proxy WebScrapingApi.

webscrapingapi.proxy_type=residential.render_js=1 é o nome de utilizador de autenticação do proxy, <API_KEY> a palavra-passe.

Pode obter uma API_KEY gratuita criando uma nova conta em https://www.webscrapingapi.com/

Depois de definirmos o ProxyMiddleware, tudo o que precisamos de fazer é ativá-lo como DOWNLOAD_MIDDLEWARE no ficheiro settings.py.

DOWNLOADER_MIDDLEWARES = {

    'myproject.middlewares.ProxyMiddleware': 1,

}

E pronto. Como pode ver, o web scraping com o Scrapy pode simplificar muito o nosso trabalho.

Conclusão

Conseguimos! Criámos um scraper capaz de extrair definições do Urban Dictionary e aprendemos imenso sobre o Scrapy ao longo do caminho. Desde a criação de itens personalizados até à utilização de middlewares e pipelines, provámos o quão poderoso e versátil o web scraping com o Scrapy pode ser. E a melhor parte? Ainda há muito mais para descobrir.

Parabéns por terem chegado ao fim desta jornada comigo. Agora devem sentir-se confiantes na vossa capacidade de enfrentar qualquer projeto de web scraping que surgir. Lembre-se apenas de que o web scraping não tem de ser intimidante ou avassalador. Com as ferramentas e os conhecimentos certos, pode ser uma experiência divertida e gratificante. Se alguma vez precisar de uma ajuda, não hesite em contactar-nos aqui em https://www.webscrapingapi.com/. Sabemos tudo sobre web scraping e teremos todo o prazer em ajudá-lo de todas as formas que pudermos.

Sobre o autor
Mihai Maxim, Desenvolvedor Full Stack @ WebScrapingAPI
Mihai MaximDesenvolvedor Full Stack

Mihai Maxim é um programador Full Stack na WebScrapingAPI, contribuindo em todas as áreas do produto e ajudando a criar ferramentas e funcionalidades fiáveis para a plataforma.

Comece a construir

Pronto para expandir a sua recolha de dados?

Junte-se a mais de 2.000 empresas que utilizam a WebScrapingAPI para extrair dados da Web à escala empresarial, sem quaisquer custos de infraestrutura.