Web-Scraping de Python con BeautifulSoup

Sep 16 2020

Soy relativamente nuevo en Python y para una tarea tuve que escribir un programa que busca una página web con BeautifulSoup, extraer todos los párrafos de ella y extraer todas las palabras que terminan con "ing", y al final guardarlo en un archivo con el formato "Word" + tabulador + "recuento de palabras" + "nueva línea".

Este es mi código hasta ahora. ¿Hay alguna forma más pitónica de manejar esto? ¿O en general formas de mejorar el código?

from bs4 import BeautifulSoup
import requests
import re


def main():
    site = "https://en.wikipedia.org/wiki/Data_science"
    r = requests.get(site).content
    soup = BeautifulSoup(r)
    ps = soup.findAll('p')
    fulltext = ''
    for p in ps:
        fulltext += p.get_text()
    words = match_words(fulltext)
    formated_words = sort_and_format(words)
    with open(r"Q1_Part1.txt","w") as file:
        file.write(formated_words)


def match_words(string):
    pattern = re.compile(r'\b(\w*ing)\b')
    words = re.findall(pattern, string.lower())
    matching_words = {}
    for word in words:
        if word in matching_words:
            matching_words[word] += 1
        else:
            matching_words[word] = 1
    return matching_words

def sort_and_format(dict):
    ordered_keys = sorted(dict, key=dict.get, reverse=True)
    output_string = ''
    for r in ordered_keys:
        output_string += f"{r}\t{dict[r]}\n"
    return output_string
    
main()

Respuestas

3 Carcigenicate Sep 16 2020 at 20:57
if word in matching_words:
    matching_words[word] += 1
else:
    matching_words[word] = 1

Si está comprobando si un diccionario tiene una clave antes de agregarla, una defaultdictpuede ser una mejor opción:

from collections import defaultdict

matching_words = defaultdict(int)
matching_words[word] += 1

intdevuelve 0cuando se llama sin argumentos, y 0se usa como valor predeterminado para el diccionario cuando la clave no existe.


fulltext = ''
for p in ps:
    fulltext += p.get_text()

Esto no es muy eficaz. El rendimiento de las +=cadenas ha mejorado en versiones posteriores de Python, pero en general sigue siendo más lento. La alternativa típica es usar join:

pieces = [p.get_text() for p in ps]
fulltext = "".join(pieces)

# Or just

fulltext = "".join([p.get_text() for p in ps])

Luego, de manera similar en sort_and_format:

output_string = "".join([f"{r}\t{dict[r]}\n"] for r in ordered_keys])

En sort_and_format, ha nombrado el parámetro dict. Esto es subóptimo por un par de razones:

  • dict es un nombre genérico que no describe correctamente los datos.
  • dict es el nombre de una clase incorporada, y sombrearlo hace que su código sea más confuso y evita que use el archivo.

Sin embargo, indicar el tipo puede ser útil, por lo que podría introducir sugerencias de tipo aquí

from typing import Dict

def sort_and_format(words: Dict[str, int]) -> str:
    . . .

Esto dice que las funciones acepta un Dictionary mapeo strIngs a ints, y devuelve un string


Además sort_and_format, descubrí que cuando comienzas a ceñirte anda los nombres, eso puede sugerir que la función está haciendo demasiado. Puede encontrar que el código tendrá más sentido si la clasificación y el formato se realizan por separado. Esas funciones pueden manejar puramente el formateo y, en su lugar, se les puede entregar una secuencia para trabajar. Si esa secuencia está ordenada, genial, si no, también genial. No importa a los efectos de formatear cuál es el orden de clasificación.