Una situación habitual en el scraping web es cuando la lista de resultados del análisis es muy larga y contiene información variada.
Por ejemplo, quizá hayas notado que nuestras imágenes anteriores pueden contener o no un atributo alt.
O imagina que extrajéramos todos los enlaces del artículo. Todos sabemos que un artículo de Wikipedia tiene MUCHOS enlaces, y puede que no queramos una lista completa de ellos. El resultado tendrá enlaces externos e internos, referencias y citas, por lo que necesitamos clasificarlos en varias categorías.
Para resolver este problema, vamos a utilizar una función lambda. Básicamente, la lambda tomará como parámetro cada elemento de la lista de resultados y aplicará la condición que definamos, igual que si utilizáramos un filtro.
Como ejemplo práctico, supongamos que necesitamos extraer todos los enlaces internos, acceder a sus artículos y hacer un resumen de cada uno. Teniendo en cuenta que uno de los casos de uso de Python es la Inteligencia Artificial, este ejemplo podría ser una excelente aplicación para obtener datos de entrenamiento.
En primer lugar, tendremos que instalar la biblioteca NLTK, ya que calcular un resumen implica procesar el lenguaje humano.
pip install -U nltk
Y, por supuesto, importarla en nuestro 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: si eres usuario de macOS, es posible que te aparezca un error «SSL: certificate verify failed». La causa puede ser que Python 3.6 utiliza una versión integrada de OpenSSL. Todo lo que tienes que hacer es abrir la ubicación donde instalaste Python y ejecutar este archivo:
/Your/Path/Here/Python 3.6/Install Certificates.command
Como puedes ver, también hemos importado la biblioteca re, utilizada para operaciones con expresiones regulares, y heapq, una implementación de la cola de montón.
Bien, ya tenemos todo lo necesario para empezar a escribir el código. Comencemos extrayendo los enlaces internos. Si vuelves al navegador, notarás algunas cosas sobre los elementos que nos interesan.
Esas cosas serían:
- El atributo href tiene un valor;
- El valor de href comienza por «/wiki/»;
- El elemento padre del enlace es una etiqueta ;
Estas características nos ayudarán a diferenciar los enlaces que necesitamos de todos los demás.
Ahora que sabemos cómo encontrar los enlaces, veamos cómo podemos extraerlos.
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()
Muy bien, ¿qué ha pasado aquí? Si nos fijamos en la función extract_links(), vemos que, en lugar del nombre de una etiqueta, hemos pasado una función lambda como parámetro al método .find_all(). Eso significa que seleccionamos solo aquellas que cumplen nuestra condición de entre todas las etiquetas del documento HTML.
Como puedes ver, la condición de una etiqueta es que sea un enlace y que sea aceptada por la función can_do_summary() definida anteriormente. Allí, rechazamos todo lo que no coincida con las características observadas anteriormente. Además, hemos utilizado una variable global para limitar el número de enlaces extraídos a 10. Si los necesitas todos, no dudes en eliminar la variable count.
Al final, llamamos a la función compute_summary() para el enlace recién encontrado. Ahí es donde se resume el artículo.
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
En resumen, realizamos una solicitud HTTP a la URL recién encontrada y convertimos el resultado en un objeto BeautifulSoup, tal y como hicimos al principio del artículo.
Para calcular un resumen, extraemos todos los párrafos del artículo y los concatenamos. A continuación, eliminamos todos los caracteres especiales que podrían interferir en los cálculos.
En términos sencillos, un resumen se elabora calculando las palabras más frecuentes y asignando a cada frase una puntuación basada en la frecuencia de sus palabras. Al final, seleccionamos las 7 frases con la puntuación más alta.
Este no es el tema de nuestro artículo, pero puedes leer más aquí si sientes curiosidad o incluso te apasiona el procesamiento del lenguaje natural.