Utilizando a Google como corretor ortográfico

Usando o "Você quis dizer" da busca da Google para realizar correção ortográfica.

  1. Introdução
  2. Problemas
  3. Corrigindo os problemas
  4. Casos úteis
  5. Performance

Introdução

Há um tempo pensei em tirar proveito do “Você quis dizer” da Google para realizar correção ortográfica. Uma pesquisa rápida me levou pra um script de quatro anos atrás e que continua funcionando. Os problemas que foram identificados estão corrigidos no meu fork.

def didYouMean(q):
    q = str(str.lower(q)).strip()
    url = "http://www.google.com/search?q=" + urllib.quote(q)
    html = getPage(url)
    soup = BeautifulSoup(html)
    ans = soup.find('a', attrs={'class' : 'spell'})
    try:
        result = repr(ans.contents)
        result = result.replace("u'","")
        result = result.replace("/","")
        result = result.replace("<b>","")
        result = result.replace("<i>","")
        result = re.sub('[^A-Za-z0-9\s]+', '', result)
        result = re.sub(' +',' ',result)
    except AttributeError:
        result = 1
    return result

Basicamente, é feito o request utilizando a url com a query sendo a palavra que queremos corrigir e então utiliza-se o BeatifulSoup para pegar a linha onde é mostrada o “Você quis dizer”, que faz parte da classe spell. É feito um pré-processamento do resultado e caso não seja sugerida correção, retonar-se 1.

python didYouMean.py "futebor"
       => futebol
python didYouMean.py "futebol"
       => 1

Problemas

A Google não faz muito idéia do que temos na cabeça quando fazemos uma pesquisa, mas guiando-se pela popularidade e contexto é possível fazer um bom trabalho. Neste quesito, quanto mais contexto damos, melhor, pois ajudará na desambiguação. Por exemplo, a pesquisa consegue identificar que estamos falando do remédio Cataflam quando cometemos um pequeno erro por causa da proximidade da palavra correta e da popularidade da pesquisa, mas assim que vamos cometendo mais erros, falta contexto e a correção fica errada:

python didYouMean.py "catafram"
       => cataflam
python didYouMean.py "catafran"
       => catamaran
python didYouMean.py "capafran"
       => cifran
python didYouMean.py "kapafran"
       => kapalaran  

Há também o problema do idioma e do encoding. Se fazemos, por exemplo:

python didYouMean.py "camiza"
       => 1

python didYouMean.py "paixão"
       => paixxe3o

No primeiro caso “camiza” está correto, mas não para o português do Brasil. Enquanto no segundo vemos que o caractere “ã” precisa ser tratado.

Corrigindo os problemas

Idioma

Mesmo uma alteração no cabeçalho (o header do request) “Accept-Language” não foi capaz de corrigir problemas com a língua desejada. Porém, o comportamento tornou-se o desejado quando ao invés de realizar a query no endereço originalmente utilizado, realizou-se com a versão encriptada:

url = "https://encrypted.google.com/search?q=" + urllib.quote(q)

Encoding

Ainda temos o problema de encoding. Para resolvê-lo vamos tirar o pré-processamento do código original, pois ele era aplicado ao contents do que era encontrado, o que incluia as tags do HTML. Usaremos o atributo text e deixaremos de usar o repr().

import pandas as pd
df = pd.DataFrame()
df["Português"] = ["paichão", "missanga", "corapão", "cassamba"]
df["Português Corrigido"] = df["Português"].apply(lambda x: didYouMean(x))
   Português   Português Corrigido
0   paichão        u'paix\xe3o'
1  missanga       u'mi\xe7anga'
2   corapão    u'cora\xe7\xe3o'
3  cassamba       u'ca\xe7amba'

A correção é feita com eliminando todo o pré-processamento original e com a seguinte modificação:

### Not using repr()
### Using text instead of contents
result = ans.text
   Português            Português Corrigido
0   paichão              paixão
1  missanga             miçanga
2   corapão             coração
3  cassamba             caçamba

Contexto

A correção depende muito do contexto. Portanto, vamos permitir que o usuário passe o contexto da palavra para que a probabilidade de acertar o intuito do usuário aumente.

### Passing a context with query string
url = "https://www.google.com/search?q=" + urllib.quote(q + " " + context) 
### Excluding context before returning the result
result = [word for word in result.split(" ") if word not in unicode(context, "utf-8")]
result = " ".join(result)

Dessa forma:

df = pd.DataFrame()
df["Remedios"] = ["catafram", "catafran", "capafran", "kapafran"]
df["Corrigido"] = df["Remedios"].apply(lambda x: didYouMean(x, encrypted=True))
df["Corrigido Contexto"] = df["Remedios"].apply(lambda x: didYouMean(x, context="bula remédio", encrypted=True))
   Remedios   Corrigido          Corrigido Contexto
0  catafram   cataflam           cataflam
1  catafran  catamaran           cataflam
2  capafran     cifran           cataflam
3  kapafran  kapalaran           cataflam

O contexto também pode ser usado para informar a língua, passando algo como “pt-br” e “português brasil”.

Casos úteis

Até agora estamos utilizando muito do que um dicionário tradicional poderia fazer. Uma boa utilidade para a ferramenta seria a correção de termos muito específicos, como remédios, nomes de Pokemons e etc, Para isso, bastaria adicionar o contexto, ou dependendo da especificidade, nem isso é necessário.

### Example 1
df = pd.DataFrame()
df["Remedios"] = ["capafram", "ruvitril", "puscoban", "huyabc", "adivil"]
df["Corrigido"] = df["Remedios"].apply(lambda x: didYouMean(x, context="bula remédio", encrypted=True))
print(df)

### Example 2
df = pd.DataFrame()
df["Pokemons"] = ["Picachu", "xamander", "boobasar", "skertle"]
df["Corrigido"] = df["Pokemons"].apply(lambda x: didYouMean(x))
print(df)
   Remedios Corrigido
0  capafram  cataflam
1  ruvitril  rivotril
2  puscoban  buscopan
3    huyabc    hyabak
4    adivil     advil


   Pokemons	    Corrigido
0   Picachu            pikachu
1  xamander         charmander
2  boobasar          bulbasaur
3   skertle           squirtle

Performance

Como pode-se imaginar, a performance do script da maneira que está não é muito boa.


def test():
    df = pd.DataFrame()
    df["Remedios"] = ["capafram", "ruvitril", "puscoban", "huyabc", "adivil"]
    df["Corrigido"] = df["Remedios"].apply(lambda x: didYouMean(x, context="bula remédio", encrypted=True))

print(timeit.timeit("test()", setup="from __main__ import test", number=15))

158.442110062

Ou seja, demora-se 2 minutos e 40 segundos para realizar a correção de 75 termos. Vamos tentar cuidar disso em uma próxima oportunidade!