Python Web-Scraping com BeautifulSoup

Sep 16 2020

Sou relativamente novo em Python e, para uma tarefa, tive que escrever um programa que busca uma página da web com BeautifulSoup, extrair todos os parágrafos dela e todas as palavras que terminam com "ing" e, no final, salvá-lo em um arquivo com o formato "Word" + tab + "wordcount" + "newline".

Este é o meu código até agora. Existe uma maneira mais pythônica de lidar com isso? Ou geralmente maneiras de melhorar o 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()

Respostas

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

Se você estiver verificando se um dicionário tem uma chave antes de adicioná-lo, defaultdictpode ser uma opção melhor:

from collections import defaultdict

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

intretorna um 0quando chamado sem argumentos, e que 0é usado como um valor padrão para o dicionário quando a chave não existe.


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

Isso não é muito eficiente. O desempenho das +=strings melhorou nas versões posteriores do Python, mas geralmente ainda é mais lento. A alternativa típica é usar join:

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

# Or just

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

Então, da mesma forma em sort_and_format:

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

Em sort_and_format, você nomeou o parâmetro dict. Isso não é ideal por alguns motivos:

  • dict é um nome genérico que não descreve corretamente os dados.
  • dict é o nome de uma classe integrada, e sombreando-o torna seu código mais confuso e impede que você use a classe integrada.

No entanto, indicar o tipo pode ser útil, então posso apresentar dicas de tipo aqui

from typing import Dict

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

Este diz que as funções aceita um Dictionary mapeamento strIngs para ints, e retorna um string


Além disso sort_and_format, descobri que, quando você começa a se limitar anda nomes, isso pode sugerir que a função está fazendo muito. Você pode descobrir que o código fará mais sentido se a classificação e a formatação ocorrerem separadamente. Essas funções podem lidar puramente com a formatação e podem receber uma sequência para trabalhar. Se essa sequência for ordenada, ótimo; se não, ótimo também Para fins de formatação, não importa qual é a ordem de classificação.