As alterações do formatador de strings do Python nas edições recentes quebraram o conector do MySQL?

Nov 28 2020

Estou escrevendo um script simples - ou deveria ser simples - para adquirir tweets da API do Twitter (tenho chaves de desenvolvedor / aplicativo e estou usando a interface Tweepy, não scraping ou qualquer coisa desse tipo - posso abandonar Tweepy por algo mais próximo de a API moderna, mas isso quase certamente não é o que está causando esse problema aqui).

Tenho uma instância do MySQL à qual me conecto e posso consultar muito bem, até chegar a hora de inserir o tweet - que tem muitos caracteres especiais, quase inevitavelmente. Para ser claro, estou usando o driver / conector oficial do Python para MySQL.

import mysql.connector
from mysql.connector import errorcode

Agora, estou ciente de que o StackOverflow está cheio de threads em que as pessoas obtêm exatamente o meu erro - simplesmente pedindo para verificar o manual de sintaxe do MySQL. Esses threads, que não são tão antigos (e não estou usando o Python mais recente, uso 3.7.9 para compatibilidade com algumas bibliotecas de PNL) insistem que a resposta é colocar a string que contém os caracteres especiais em um antigo style format string DENTRO do método cursor.execute, para colocar espaços reservados para variáveis ​​de string entre aspas e para passar uma tupla com um segundo valor vazio se, como no meu caso, apenas uma variável deve ser inserida. Esta também é uma solução postada como parte de uma resposta de relatório de bug no site MySQL - e ainda assim, não tive sucesso.

Aqui está o que eu tenho - seguindo as instruções em dezenas de páginas aqui e no site oficial do banco de dados:

for tweet in tweepy.Cursor(twilek.search, q=keyword, tweet_mode='extended').items():
            twi_tweet = tweet.full_text
            print(twi_tweet)
            twi_tweet = twi_tweet.encode('utf8')
            requests_total+=1
            os.environ['TWITTER_REQUESTS'] = str(requests_total)
            requests_total = int(os.environ.get('TWITTER_REQUESTS'))
            # insert the archived tweet text into the database table
            sql = 'USE hate_tweets'
            ms_cur.execute(sql)
            twi_tweet = str(twi_tweet)
            insert_tweet = re.sub(r'[^A-Za-z0-9 ]+', '', twi_tweet)
            ms_cur.execute("INSERT INTO tweets_lgbt (text) VALUES %s" % (insert_tweet,))
            cnx.commit()
            print(ms_cur.rowcount, "record inserted.")

(twilek é meu objeto de cursor porque sou um idiota)

resultado esperado : o formatador de string passa ao MySQL uma string de tweet modificada que pode processar e adicionar como uma linha à tabela tweets_lgbt

resultado real : a inserção falha em um erro de sintaxe para qualquer tweet

Eu tentei ir mais longe a ponto de usar regex para remover tudo, exceto alfanuméricos e espaços - o mesmo problema. Estou me perguntando se os novos recursos de formato de string das versões atuais do Python quebraram a compatibilidade com este conector. Prefiro usar o driver oficial, mas mudarei para um ORM se for necessário. (Eu tentei os recursos mais recentes, como strings F, e descobri que eles causaram o mesmo resultado.)

Respostas

2 CodeIt Nov 28 2020 at 13:53

É assim que você deve inserir uma linha em sua tabela,

insert_tweet = "ABCEFg 9 XYZ"
"INSERT INTO tweets_lgbt (text) VALUES ('%s');"%(insert_tweet)
"INSERT INTO tweets_lgbt (text) VALUES ('ABCEFg 9 XYZ');"

Coisas a serem observadas

  1. Os argumentos para um formatador de string são como os argumentos para uma função. Portanto, você não pode adicionar uma vírgula no final para converter uma string em uma tupla.

  2. Se estiver tentando inserir vários valores de uma vez, você pode usar cursor.executemany ou esta resposta .

2 snakecharmerb Nov 28 2020 at 14:30

Eu tenho estas observações:

  • a VALUEScláusula requer parêntesesVALUES (%s)
  • as aspas / escapes de valores devem ser delegadas ao executemétodo do cursor , usando marcadores de posição não citados no SQL e passando os valores como o segundo argumento: cursor.execute(sql, (tweet_text,))oucursor.executemany(sql, [(tweet_text1,), (tweet_text2,)])
  • uma vez que essas etapas são aplicadas, não há necessidade de codificação / stringificação / regex-ifying: assumindo que twi_texté a stre o conjunto de caracteres / agrupamento do banco de dados suporta o intervalo UTF-8 completo (por exemplo utf8mb4), então a inserção deve ser bem-sucedida.
    • em particular, codificar a stre, em seguida, chamar stro resultado deve ser evitado: você acaba com"b'my original string'"

Esta versão modificada do código na questão funciona para mim:

import mysql.connector

DDL1 = """DROP TABLE IF EXISTS tweets_lgbt"""
DDL2 = """\
CREATE TABLE tweets_lgbt (
    `text` VARCHAR (256))
"""

# From https://twitter.com/AlisonMitchell/status/1332567013701500928?s=20
insert_tweet = """\
Particularly pleased to see @SarahStylesAU
 quoted in this piece for the work she did
👌

Thrive like a girl: Why women's cricket in Australia is setting the standard
"""

# Older connector releases don't support with... 
with mysql.connector.connect(database='test') as cnx:
    with cnx.cursor() as ms_cur:

        ms_cur.execute(DDL1)
        ms_cur.execute(DDL2)

        ms_cur.execute("INSERT INTO tweets_lgbt (`text`) VALUES (%s)",  (insert_tweet,))
        cnx.commit()
        print(ms_cur.rowcount, "record inserted.")