5

更新
在按照 Nathan 的建议通过 execute() 行列表之后,代码执行得更远,但仍然卡在执行函数上。错误消息如下:

    query = query % db.literal(args)
TypeError: not all arguments converted during string formatting

所以它仍然无法正常工作。有人知道为什么现在出现类型错误吗?
结束更新

我有一个 .xls 格式的大型邮件列表。我正在使用 python 和 xlrd 将 xls 文件中的名称和电子邮件检索到两个列表中。现在我想把每个名字和电子邮件放入一个 mysql 数据库。我在这部分使用 MySQLdb。显然我不想为每个列表项做一个插入语句。
这是我到目前为止所拥有的。

from xlrd import open_workbook, cellname
import MySQLdb

dbname = 'h4h'
host = 'localhost'
pwd = 'P@ssw0rd'
user = 'root'

book = open_workbook('h4hlist.xls')
sheet = book.sheet_by_index(0)
mailing_list = {}
name_list = []
email_list = []

for row in range(sheet.nrows):
    """name is in the 0th col. email is the 4th col."""
    name = sheet.cell(row, 0).value  
    email =  sheet.cell(row, 4).value
    if name and email:
        mailing_list[name] = email

for n, e in sorted(mailing_list.iteritems()):
    name_list.append(n)
    email_list.append(e)

db = MySQLdb.connect(host=host, user=user, db=dbname, passwd=pwd)
cursor = db.cursor()
cursor.execute("""INSERT INTO mailing_list (name,email) VALUES (%s,%s)""",
              (name_list, email_list))

游标执行时的问题。这是错误:_mysql_exceptions.OperationalError: (1241, 'Operand should contain 1 column(s)')我最初尝试将我的查询放入一个 var 中,但随后它只是发出一条关于将元组传递给 execute() 的消息。

我究竟做错了什么?这甚至可能吗?

列表很大,我绝对不能将插入放入循环中。我查看了使用 LOAD DATA INFILE,但我真的不明白如何格式化文件或查询,当我不得不阅读 MySQL 文档时我的眼睛流血了。我知道我可能会使用一些在线 xls 到 mysql 转换器,但这对我来说也是一个学习练习。有没有更好的方法

4

4 回答 4

18

您需要提供executemany()行列表。您不需要将姓名和电子邮件分成单独的列表,只需创建一个包含两个值的列表。

rows = []

for row in range(sheet.nrows):
    """name is in the 0th col. email is the 4th col."""
    name = sheet.cell(row, 0).value  
    email =  sheet.cell(row, 4).value
    rows.append((name, email))

db = MySQLdb.connect(host=host, user=user, db=dbname, passwd=pwd)
cursor = db.cursor()
cursor.executemany("""INSERT INTO mailing_list (name,email) VALUES (%s,%s)""", rows)

executemany()更新:正如@JonClements 提到的,它不应该是execute().

于 2012-10-22T22:35:55.047 回答
7

要修复TypeError: not all arguments converted during string formatting- 您需要使用该cursor.executemany(...)方法,因为它接受一个可迭代的元组(多行),而cursor.execute(...)期望参数是单行值。

执行命令后,您需要确保事务已提交,以便使用db.commit().

于 2012-10-22T23:47:51.907 回答
3

如果您对代码的高性能感兴趣,这个答案可能会更好。

excutemany方法相比,以下方法execute会更快:

INSERT INTO mailing_list (name,email) VALUES ('Jim','jim@yahoo.com'),('Lucy','Lucy@gmail.com')

您可以轻松地从@Nathan Villaescusa 修改答案并获取新代码。

cursor.execute("""INSERT INTO mailing_list (name,email) VALUES (%s)""".format(",".join(str(i) for i in rows))

这是我自己的测试结果:

excutemany:10000 runs takes 220 seconds

execute:10000 runs takes 12 seconds.

速度差异约为15倍。

于 2017-09-29T08:31:35.783 回答
0

采用@PengjuZhao 的想法,它应该可以简单地为所有要传递的值添加一个占位符。@PengjuZhao 答案的不同之处在于,这些值作为第二个参数传递给 execute() 函数,这应该是注入攻击安全的,因为这只在运行时评估(与“.format()”相反)。

cursor.execute("""INSERT INTO mailing_list (name,email) VALUES (%s)""", ",".join(str(i) for i in rows))

仅当这不能正常工作时,请尝试以下方法。

####

@PengjuZhao 的回答表明,executemany() 要么具有强大的 Python 开销,要么在不需要的地方使用多个 execute() 语句,否则 executemany() 不会比单个 execute() 语句慢太多。

这是一个将 NathanVillaescusa 和 @PengjuZhao 的答案放在单个 execute() 方法中的函数。

该解决方案构建动态数量的占位符以添加到 sql 语句中。它是一个手动构建的 execute() 语句,具有多个“%s”占位符,它的性能可能优于 executemany() 语句。

例如,在 2 列,插入 100 行:

  • execute(): 200 次 "%s" (= 取决于行数)
  • executemany(): 仅 2 次 "%s" (= 与行数无关)。

该解决方案有可能具有@PengjuZhao 的答案的高速而不冒注入攻击的风险。

  1. 准备函数的参数:

您将值存储在一维 numpy 数组arr_name中,arr_email然后逐行转换为串联值列表。或者,您使用@NathanVillaescusa 的方法。

from itertools import chain

listAllValues = list(chain([
arr_name.reshape(-1,1), arr_email.reshape(-1,1)
]))

column_names = 'name, email'

table_name = 'mailing_list'
  1. 使用占位符获取 sql 查询:

简单地numRows = int((len(listAllValues))/numColumns)避免传递行数。如果您在 listAllValues 的 2 列中插入 6 个值,那么显然会产生 6/2 = 3 行。

def getSqlInsertMultipleRowsInSqlTable(table_name, column_names, listAllValues):
    numColumns = len(column_names.split(","))
    numRows = int((len(listAllValues))/numColumns)
    
    placeholdersPerRow = "("+', '.join(['%s'] * numColumns)+")"
    placeholders = ', '.join([placeholdersPerRow] * numRows)
    
    sqlInsertMultipleRowsInSqlTable = "insert into `{table}` ({columns}) values {values};".format(table=table_name, columns=column_names, values=placeholders)

    return sqlInsertMultipleRowsInSqlTable

strSqlQuery = getSqlInsertMultipleRowsInSqlTable(table_name, column_names, listAllValues)
  1. 执行 strSqlQuery

最后一步:

db = MySQLdb.connect(host=host, user=user, db=dbname, passwd=pwd)
cursor = db.cursor()
cursor.execute(strSqlQuery, listAllValues)

该解决方案希望不会像@PengjuZhao 的回答那样存在注入攻击的风险,因为它仅使用占位符而不是值填充 sql 语句。listAllValues值此时仅在此处单独传递,其中strSqlQuery只有占位符而不是值:

cursor.execute(strSqlQuery, listAllValues)

execute() 语句获取带有占位符 %s 的 sql 语句和两个单独参数中的值列表,就像在@NathanVillaescusa 的回答中所做的那样。我仍然不确定这是否可以避免注入攻击。据我了解,只有将值直接放在sql语句中才会发生注入攻击,如果我错了,请发表评论。

于 2020-09-02T10:14:26.517 回答