Uma situação comum no web scraping é quando a lista de resultados da análise é muito longa e contém informações mistas.
Por exemplo, deve ter reparado que as nossas imagens anteriores podem ou não conter um atributo alt.
Ou imagine que iríamos extrair todos os links do artigo. Todos sabemos que um artigo da Wikipédia tem MUITOS links, e talvez não queiramos uma lista completa deles. O resultado terá links externos e internos, referências e citações, por isso precisamos de os classificar em várias categorias.
Para resolver este problema, vamos utilizar uma função lambda. Basicamente, a função lambda irá receber como parâmetro cada elemento da lista de resultados e aplicar a condição que definirmos, tal como se utilizássemos um filtro.
Para um exemplo prático, vamos supor que precisamos de extrair todos os links internos, aceder aos respetivos artigos e fazer um resumo de cada um. Considerando que um dos casos de utilização do Python é a Inteligência Artificial, este exemplo pode ser uma excelente aplicação para obter dados de treino.
Em primeiro lugar, precisaremos de instalar a biblioteca NLTK, pois calcular um resumo implica processar a linguagem humana.
pip install -U nltk
E, claro, importá-la para o nosso código:
import re
import nltk
import heapq
# need to download only for the first execution
# warning: the size of the dataset is big; hence it will take time
nltk.download()
Nota: se for utilizador do macOS, poderá receber um erro “SSL: falha na verificação do certificado”. A causa pode ser o facto de o Python 3.6 utilizar uma versão incorporada do OpenSSL. Basta abrir o local onde instalou o Python e executar este ficheiro:
/Your/Path/Here/Python 3.6/Install Certificates.command
Como pode ver, também importámos a biblioteca re, utilizada para operações com expressões regulares, e a heapq, uma implementação de fila de heap.
Ótimo, temos tudo o que precisamos para começar a escrever o código. Vamos começar por extrair os links internos. Se voltares ao navegador, irás notar algumas coisas sobre os elementos que nos interessam.
Essas coisas seriam:
- O atributo href tem um valor;
- O valor de href começa com “/wiki/”;
- O pai do link é uma tag ;
Estas características vão ajudar-nos a diferenciar os links de que precisamos de todos os outros.
Agora que sabemos como encontrar os links, vamos ver como podemos extraí-los.
count = 0
def can_do_summary(tag):
global count
if count > 10: return False
# Reject if parent is not a paragraph
if not tag.parent.name == 'p': return False
href = tag.get('href')
# Reject if href is not set
if href is None: return False
# Reject is href value does not start with /wiki/
if not href.startswith('/wiki/'): return False
compute_summary(href)
return True
def extract_links(soup):
soup.find_all(lambda tag: tag.name == 'a' and can_do_summary(tag))
def main():
URL = 'https://en.wikipedia.org/wiki/Beer'
page = requests.get(URL)
soup = BeautifulSoup(page.content, 'html.parser')
extract_links(soup)
main()
Muito bem, então o que aconteceu aqui? Olhando para a função extract_links(), podemos ver que, em vez do nome de uma tag, passámos uma função lambda como parâmetro para o método .find_all(). Isso significa que selecionamos apenas aquelas que correspondem à nossa condição entre todas as tags do documento HTML.
Como pode ver, a condição de uma tag é ser um link e ser aceite pela função can_do_summary() definida acima. Ali, rejeitamos tudo o que não corresponda às características observadas anteriormente. Além disso, utilizámos uma variável global para limitar o número de links extraídos a 10. Se precisar de todos eles, sinta-se à vontade para remover a variável count.
No final, chamamos a função compute_summary() para o link recém-encontrado. É aí que o artigo é resumido.
def compute_summary(href):
global count
full_link = 'https://en.wikipedia.org' + href
page = requests.get(full_link)
soup = BeautifulSoup(page.content, 'html.parser')
# Concatenate article paragraphs
paragraphs = soup.find_all('p')
article_text = ""
for p in paragraphs:
article_text += p.text
# Removing Square Bracket, extra spaces, special characters and digits
article_text = re.sub(r'\[[0-9]*\]', ' ', article_text)
article_text = re.sub(r'\s+', ' ', article_text)
formatted_article_text = re.sub('[^a-zA-Z]', ' ', article_text)
formatted_article_text = re.sub(r'\s+', ' ', formatted_article_text)
# Converting text to sentences
sentence_list = nltk.sent_tokenize(article_text)
# Find frequency of occurrence of each word
stopwords = nltk.corpus.stopwords.words('english')
word_frequencies = {}
for word in nltk.word_tokenize(formatted_article_text):
if word not in stopwords:
if word not in word_frequencies.keys():
word_frequencies[word] = 1
else:
word_frequencies[word] += 1
maximum_frequency = max(word_frequencies.values())
for word in word_frequencies.keys():
word_frequencies[word] = (word_frequencies[word] / maximum_frequency)
# Calculate the score of each sentence
sentence_scores = {}
for sent in sentence_list:
for word in nltk.word_tokenize(sent.lower()):
if word in word_frequencies.keys():
if len(sent.split(' ')) < 30:
if sent not in sentence_scores.keys():
sentence_scores[sent] = word_frequencies[word]
else:
sentence_scores[sent] += word_frequencies[word]
# Pick top 7 sentences with highest score
summary_sentences = heapq.nlargest(7, sentence_scores, key=sentence_scores.get)
summary = '\n'.join(summary_sentences)
count += 1
Resumindo, fazemos um pedido HTTP para o URL recém-descoberto e convertemos o resultado num objeto BeautifulSoup, tal como fizemos no início do artigo.
Para calcular um resumo, extraímos todos os parágrafos do artigo e concatenamo-los. Depois disso, removemos todos os caracteres especiais que possam interferir nos cálculos.
Em termos simples, um resumo é feito calculando as palavras mais frequentes e atribuindo a cada frase uma pontuação com base na frequência das suas palavras. No final, selecionamos as 7 frases com a pontuação mais elevada.
Este não é o tema do nosso artigo, mas pode ler mais aqui se estiver curioso ou mesmo interessado em Processamento de Linguagem Natural.