¿Los cambios en el formateador de cadenas de Python en ediciones recientes han roto el conector MySQL?
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
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
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í.
Si está intentando insertar varios valores a la vez, puede usar cursor.executemany o esta respuesta .
Tengo estas observaciones:
- la
VALUES
cláusula requiere paréntesisVALUES (%s)
- las comillas / escapes de valores deben delegarse al
execute
mé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_text
es astr
y 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
str
y luego invocarstr
el resultado: terminas con"b'my original string'"
- en particular, se debe evitar codificar a
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.")