¿Los cambios en el formateador de cadenas de Python en ediciones recientes han roto el conector MySQL?

Nov 28 2020

Estoy escribiendo un script simple, o debería ser simple, para adquirir tweets de la API de Twitter (tengo claves de desarrollador / aplicación y estoy usando la interfaz Tweepy, no raspado ni nada por el estilo; puedo deshacerme de Tweepy por algo más cercano a la API moderna, pero es casi seguro que eso no es lo que está causando este problema aquí).

Tengo una instancia de MySQL a la que me conecto y puedo consultar bien, hasta que llega el momento de insertar el tweet, que tiene muchos caracteres especiales, casi inevitablemente. Para ser claros, estoy usando el controlador / conector oficial de Python para MySQL.

import mysql.connector
from mysql.connector import errorcode

Ahora, soy consciente de que StackOverflow está LISTO con subprocesos donde la gente obtiene mi error exacto, simplemente indicando que verifique el manual de sintaxis de MySQL. Estos hilos, que no son tan antiguos (y no estoy usando la última versión de Python, utilizo 3.7.9 para compatibilidad con algunas bibliotecas de PNL) insisten en que la respuesta es colocar la cadena que tiene los caracteres especiales en un antiguo estilo cadena de formato DENTRO del método cursor.execute, para encerrar los marcadores de posición de la variable de cadena entre comillas y para pasar una tupla con un segundo valor vacío si, como en mi caso, solo se debe insertar una variable. Esta también es una solución publicada como parte de una respuesta de informe de error en el sitio web de MySQL y, sin embargo, no he tenido éxito.

Esto es lo que tengo: siguiendo las instrucciones en docenas de páginas aquí y el sitio web oficial de la base de datos:

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 es mi objeto cursor porque soy un idiota)

resultado esperado : el formateador de cadenas pasa a MySQL una cadena de tweet modificada que puede procesar y agregar como una fila a la tabla tweets_lgbt

resultado real : la inserción falla en un error de sintaxis para cualquier tweet

Intenté ir tan lejos como para usar expresiones regulares para eliminar todo menos alfanuméricos y espacios, el mismo problema. Me pregunto si las nuevas características de formato de cadena de las versiones actuales de Python han roto la compatibilidad con este conector. Prefiero usar el controlador oficial, pero cambiaré a un ORM si es necesario. (Probé las funciones más nuevas, como las cadenas F, y descubrí que causaban el mismo resultado).

Respuestas

2 CodeIt Nov 28 2020 at 13:53

Así es como debes insertar una fila en tu tabla,

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

Cosas a tener en cuenta

  1. Los argumentos de un formateador de cadenas son como los argumentos de una función. Por lo tanto, no puede agregar una coma al final para convertir una cadena en una tupla allí.

  2. Si está intentando insertar varios valores a la vez, puede usar cursor.executemany o esta respuesta .

2 snakecharmerb Nov 28 2020 at 14:30

Tengo estas observaciones:

  • la VALUEScláusula requiere paréntesisVALUES (%s)
  • las comillas / escapes de valores deben delegarse al executemétodo del cursor , utilizando marcadores de posición sin comillas en el SQL y pasando los valores como segundo argumento: cursor.execute(sql, (tweet_text,))ocursor.executemany(sql, [(tweet_text1,), (tweet_text2,)])
  • Una vez que se aplican estos pasos, no es necesario codificar / encadenar / regex-ifying: asumiendo que twi_textes a stry el juego de caracteres / colación de la base de datos admite el rango UTF-8 completo (por ejemplo, utf8mb4), entonces la inserción debería tener éxito.
    • en particular, se debe evitar codificar a stry luego invocar strel resultado: terminas con"b'my original string'"

Esta versión modificada del código en la pregunta funciona para mí:

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.")