Web scraping para extração de contatos — Parte 1: Emails

Rodrigo Nader
Ensina.AI
Published in
7 min readOct 6, 2018

--

Quando você começa a trabalhar com Ciência de Dados e Machine Learning, começa a perceber que há uma coisa que faz muita falta quase sempre na hora de resolver um problema: os dados!

Encontrar o tipo certo de dados para um problema específico não é fácil, e apesar da grande quantidade de datasets online coletados e até processados, muitas vezes somos forçados a extrair informações por nós mesmos no mundo caótico da Internet. É aí que entra a arte do web scraping.

Nas últimas semanas eu venho pesquisando sobre web scraping com Python e Scrapy, e decidi aplicá-los construindo um Coletor de Contatos, um bot que tem o objetivo de entrar em vários sites e coletar emails e outras informações de contato dada uma certa tag para procurar.

Existem muitas bibliotecas grátis de Python por aí focadas em web scraping, crawling e parsing, como Requests, Selenium e Beautiful Soup, então, caso você tenha interesse, tome um tempo para analisar cada uma delas e decidir qual se encaixa melhor para seus interesses. Esse artigo trás uma breve introdução sobre algumas dessas bibliotecas principais.

No meu caso, venho usando Beautiful Soup por algum tempo, principalmente para fazer o parse de HTML, e agora estou juntando ele com o ambiente do Scrapy, que é um poderoso framework para scraping e web crawling. Para aprender sobre suas características, sugiro que dê uma olhada em alguns tutoriais do youtube e leia sua documentação.

Apesar dessa série de artigos pretender trabalhar com números de telefones e talvez outras formas de informações de contato, nesse primeiro post vamos manter apenas a extração de emails para manter as coisas simples. O objetivo desse scraper é buscar no Google por vários sites relacionados com uma palavra-chave, procurar emails no código-fonte destes sites e armazená-los em um data frame.

Vamos supor que você deseja obter 2 mil emails que se relacionam com imobiliárias, você poderia usar algumas tags para a busca e obter esses emails armazenados em um arquivo CSV no seu computador facilmente. Isso seria ótimo para construir mailing lists de empresas que querer divulgar um produto ou fazer contato.

Esse problema será tratado em 5 passos:

1 — Buscar websites do google com googlesearch
2 — Criar uma expressão de regex para extrair emails
3 — Criar uma Spider do Scrapy para acessar os sites
4 — Salvar os emails em um arquivo CSV
5 — Consolidar esses passos em uma função

Esse artigo apresentará partes de códigos durante as explicações, mas tentarei mantê-lo o mais intuitivo possível. Vamos às etapas:

1 — Buscar websites do Google com googlesearch

Para extrair URLs com uma tag, usaremos o método search da biblioteca googlesearch, que, a partir de uma palavra-chave, um número de sites para procurar e o idioma, retorna links de uma busca no Google. Mas antes de chamar essa função, vamos importar alguns módulos:

import logging
import os
import pandas as pd
import re
import scrapy
from scrapy.crawler import CrawlerProcess
from scrapy.linkextractors.lxmlhtml import LxmlLinkExtractor
from googlesearch import search
logging.getLogger('scrapy').propagate = False

Essa última linha é usada para evitar muitos logs e avisos quando usamos o Scrapy dentro de um Jupyter Notebook.

Então vamos fazer uma função simples utilizando o search:

def get_urls(tag, n, language):
urls = [url for url in search(tag, stop=n, lang=language)][:n]
return urls

Esse script retorna uma lista de URL strings:

Esta lista de URLs (vamos chamá-la de google_urls) funcionará como o input para nossa Spider, que deve ler o código fonte de cada página e procurar emails.

2 — Criar uma expressão de regex para extrair emails

Se você se interessa em trabalhar com manipulação de texto, eu recomendo que se familiarize com as regular expressions (regex), pois são ótimas ferramentas para buscar, extrair e substituir partes de um texto e encontrar padrões em uma string, baseando-se em uma sequência de caracteres. Por exemplo, a extração de emails pode ser feita aplicando o método findall, como segue:

mail_list = re.findall(‘\w+@\w+\.{1}\w+’, html_text)

A expressão ‘\w+@\w+\.{1}\w+’ usada aqui poderia ser traduzida para algo como:

Procure por tudo que comece com uma ou mais letras, seguidos por um símbolo de arroba ('@'), seguido por uma ou mais letras com um ponto no final. Depois disso, deve haver uma ou mais letras de novo.

Caso você queira saber mais sobre regex, existem vários vídeos interessantes no youtube, incluindo essa introdução do Sentdex, e a documentação pode te ajudar a iniciar.

Claro que a expressão acima pode ser melhorada para evitar a seleção de emails indesejados ou erros no momento da extração, mas vamos tomá-la como boa o suficiente por enquanto.

3 — Criar uma Spider do Scrapy para acessar os sites

Uma Spider simples é composta de um nome, uma lista de URLs para fazer os requests e um ou mais métodos para fazer o parse do response. Nossa Spider completa ficou assim:

class MailSpider(scrapy.Spider):

name = 'email'

def parse(self, response):

links = LxmlLinkExtractor(allow=()).extract_links(response)
links = [str(link.url) for link in links]
links.append(str(response.url))

for link in links:
yield scrapy.Request(url=link, callback=self.parse_link)

def parse_link(self, response):

for word in self.reject:
if word in str(response.url):
return

html_text = str(response.text)
mail_list = re.findall('\w+@\w+\.{1}\w+', html_text)

dic = {'email': mail_list, 'link': str(response.url)}
df = pd.DataFrame(dic)

df.to_csv(self.path, mode='a', header=False)
df.to_csv(self.path, mode='a', header=False)

Para entender melhor, essa Spider pega a lista de URLs como entrada e lê seus códigos-fonte um por um. Você pode perceber aqui que além de extrair emails dos URLs, nós estamos buscando também por outros links dentro dos sites iniciais. Isto porque, na maioria dos sites, os emails relevantes não estão na página principal, mas sim em uma outra página (por exemplo, uma página de contato). Para isso, no primeiro método de parse estamos usando um objeto de extração de links (LxmlLinkExtractor) que procura por novos URLs dentro de um HTML. Esses URLs são passados para o método parse_link, onde nossa expressão regex será utilizada.

O script abaixo é responsável por enviar os links de um método de parse para o outro. Isso é feito através de um argumento chamado callback, que define para qual método o URL será enviado.

yield scrapy.Request(url=link, callback=self.parse_link)

Dentro de parse_link notamos um for loop usando a variável reject. Essa é uma lista de palavras a ser evitada quando procuramos os endereços. Por exemplo, se eu estou usando a tag="restaurantes no Rio de Janeiro", mas não quero entrar em sites do Facebook ou Twitter, posso incluir essas palavras como rejeições ao processar a Spider:

process = CrawlerProcess({'USER_AGENT': 'Mozilla/5.0'})
process.crawl(MailSpider, start_urls=google_urls, path=path, reject=reject)
process.start()

A lista google_urls é passada através de um argumento quando nós chamamos o método process para executar a Spider, path define onde salvar o arquivo CSV e reject funciona como foi explicado acima.

4 — Salvar os emails em um arquivo CSV

O Scrapy tem seus próprios métodos de armazenar e exportar os dados coletados, mas nesse caso estou aplicando os meus próprios métodos (provavelmente mais lentos), utilizando o pandas to_csv, função que exporta um data frame para um arquivo CSV. Para cada site explorado, criei um data frame com as colunas: [email, link], e anexo esse data frame a um CSV criado anteriormente.

Abaixo estou apenas definindo duas funções básicas para criar o arquivo CSV, e, caso esse arquivo já exista, checa se gostaríamos de substituí-lo.

def ask_user(question):
response = input(question + ' y/n' + '\n')
if response == 'y':
return True
else:
return False
def create_file(path):
response = False
if os.path.exists(path):
response = ask_user('File already exists, replace?')
if response == False: return

with open(path, 'wb') as file:
file.close()

5 — Consolidar esses passos em uma função

Finalmente vamos construir a função principal onde tudo será colocado conjuntamente. Primeiro essa função cria um data frame vazio e salva como CSV. Depois, coletamos a lista google_urls usando a função get_urls e começamos o processo executando nossa Spider.

def get_info(tag, n, language, path, reject=[]):

create_file(path)
df = pd.DataFrame(columns=['email', 'link'], index=[0])
df.to_csv(path, mode='w', header=True)

print('Collecting Google urls...')
google_urls = get_urls(tag, n, language)

print('Searching for emails...')
process = CrawlerProcess({'USER_AGENT': 'Mozilla/5.0'})
process.crawl(MailSpider, start_urls=google_urls, path=path, reject=reject)
process.start()

print('Cleaning emails...')
df = pd.read_csv(path, index_col=0)
df.columns = ['email', 'link']
df = df.drop_duplicates(subset='email')
df = df.reset_index(drop=True)
df.to_csv(path, mode='w', header=True)

return df

Ótimo, vamos testar os resultados:

ad_words = ['facebook', 'instagram', 'youtube', 'twitter', 'wiki']
df = get_info('mastering studio london', 300, 'pt', 'studios.csv', reject=bad_words)

Como eu permiti que um arquivo antigo fosse substituído, o arquivo studios.csv foi criado com os resultados. Além de salvar os dados neste CSV, nossa função final também retorna um data frame de pandas com a informação extraída.

df.head()

Aí está nosso bot que entra no Google e extrai emails automaticamente! Apesar de termos conseguido uma lista com milhares de emails, percebemos que muitos deles estão estranhos, alguns nem são emails, e provavelmente a maioria deles acabarão sendo inúteis para nosso propósito. Então o próximo passo será encontrar maneiras de filtrar uma grande porção de emails irrelevantes e buscar estratégias para manter apenas aqueles que poderiam ter utilidade. Talvez usando Machine Learning? Espero que sim.

Esse artigo foi escrito como o primeiro de uma série usando Scrapy para criar um Coletor de Contatos. Sinta-se a vontade para deixar comentários, idéias ou observações. E se curtiu, não esqueça de aplaudir! Até o próximo post.

Para acessar a versão em inglês deste mesmo artigo, clique:
Web scraping to extract contact information — Part 1: Mailing Lists

--

--