Les modifications apportées au formateur de chaînes de Python dans les éditions récentes ont-elles rompu le connecteur MySQL?

Nov 28 2020

J'écris un script simple - ou cela devrait être simple - pour acquérir des tweets de l'API de Twitter (j'ai des clés de développeur / d'application et j'utilise l'interface Tweepy, pas de grattage ou quoi que ce soit de ce genre - je peux abandonner Tweepy pour quelque chose de plus proche de l'API moderne mais ce n'est certainement pas ce qui cause ce problème ici).

J'ai une instance MySQL à laquelle je me connecte et que je peux interroger très bien, jusqu'à ce qu'il vienne le temps d'insérer le tweet - qui contient beaucoup de caractères spéciaux, presque inévitablement. Pour être clair, j'utilise le pilote / connecteur Python officiel pour MySQL.

import mysql.connector
from mysql.connector import errorcode

Maintenant, je suis conscient que StackOverflow est LITTERED avec des threads où les gens obtiennent mon erreur exacte - indiquant simplement de vérifier le manuel de syntaxe MySQL. Ces threads, qui ne sont pas si anciens (et je n'utilise pas le dernier Python, j'utilise 3.7.9 pour la compatibilité avec certaines bibliothèques NLP) insistent pour que la réponse soit de placer la chaîne qui contient les caractères spéciaux dans un ancien- style format string DANS la méthode cursor.execute, pour mettre les espaces réservés des variables de chaîne entre guillemets et pour passer un tuple avec une seconde valeur vide si, comme dans mon cas, une seule variable doit être insérée. C'est aussi une solution publiée dans le cadre d'une réponse de rapport de bogue sur le site Web de MySQL - et pourtant, je n'ai pas réussi.

Voici ce que j'ai - en suivant les instructions sur des dizaines de pages ici et sur le site officiel de la base de données:

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 est mon objet curseur parce que je suis un con)

résultat attendu : le formateur de chaînes transmet à MySQL une chaîne de tweet modifiée qu'il peut traiter et ajouter sous forme de ligne à la table tweets_lgbt

résultat réel : l'insertion échoue sur une erreur de syntaxe pour n'importe quel tweet

J'ai essayé d'aller jusqu'à utiliser regex pour tout supprimer sauf les caractères alphanumériques et les espaces - même problème. Je me demande si les nouvelles fonctionnalités de format de chaîne des versions actuelles de Python ont une compatibilité cassée avec ce connecteur? Je préfère utiliser le pilote officiel mais je passerai à un ORM si je le dois. (J'ai essayé les nouvelles fonctionnalités telles que les chaînes F et j'ai trouvé qu'elles causaient le même résultat.)

Réponses

2 CodeIt Nov 28 2020 at 13:53

Voici comment insérer une ligne dans votre tableau,

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

Choses à noter

  1. Les arguments d'un formateur de chaîne sont exactement comme les arguments d'une fonction. Ainsi, vous ne pouvez pas ajouter une virgule à la fin pour convertir une chaîne en tuple à cet endroit.

  2. Si vous essayez d'insérer plusieurs valeurs à la fois, vous pouvez utiliser cursor.executemany ou cette réponse .

2 snakecharmerb Nov 28 2020 at 14:30

J'ai ces observations:

  • la VALUESclause nécessite des parenthèsesVALUES (%s)
  • les guillemets / échappements de valeurs doivent être délégués à la executeméthode du curseur , en utilisant des espaces réservés sans guillemets dans le SQL et en passant les valeurs comme deuxième argument: cursor.execute(sql, (tweet_text,))oucursor.executemany(sql, [(tweet_text1,), (tweet_text2,)])
  • une fois ces étapes appliquées, il n'est pas nécessaire d'encoder / de stringifier / de regex-ifying: en supposant que twi_textc'est a stret que le jeu de caractères / collation de la base de données prend en charge la plage UTF-8 complète (par exemple utf8mb4), l'insertion devrait réussir.
    • en particulier, il faut éviter d' encoder a strpuis d'appeler strle résultat: on se retrouve avec"b'my original string'"

Cette version modifiée du code dans la question fonctionne pour moi:

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