我有几个不同的函数,它们使用熊猫刮取不同的表,将每个表保存到一个dataframe中,并将它们保存到PostgreSQL数据库中。我能够成功地抓取每个表,并将其保存为一个dataframe,但在将其保存到SQL中时,我遇到了一些问题。我试图这样做如下:
from sqlalchemy import create_engine
# Opening sql connection
engine = create_engine('postgresql://postgres:pw@localhost/name')
con = engine.connect()
def df1():
df = scraped_data
df.to_sql(table_name, con, if_exists='replace')
df1()
def df2():
df = scraped_data
df.to_sql(table_name, con, if_exists='replace')
df2()
# Closing connection
con.close()
我能够成功地将df1
保存到SQL,但在运行df2
时会出现错误。这两个函数之间唯一真正的区别是它们从不同的源抓取数据,其他的一切本质上都是一样的。
对于其他数据文件,我还有几个其他函数,但是不管调用函数的顺序如何,只有第一个函数才能工作。
对于我调用的所有其他函数,我总是得到相同的错误:
psycopg2.ProgrammingError: incomplete placeholder: '%(' without ')'
他们还链接了一个关于错误背景的页面:http://sqlalche.me/e/f405),尽管我仍然不知道如何处理它。
我只是觉得奇怪的是,它是如何工作的一个功能,而不是其他,当唯一的变化是我正在从url中删除。
编辑
我正在从NFL的网站上抓取数据。
df2
做了一件非常类似的事情,但是从http://www.nfl.com/stats/categorystats?archive=false&conference=null&role=TM&offensiveStatisticCategory=TEAM_PASSING&defensiveStatisticCategory=null&season=2019&seasonType=REG&tabSeq=2&qualified=false&Submit=Go中提取数据。
看起来,主要的区别是df1
使用Pct
来表示列标题中的百分比,而df2
使用%
。
发布于 2020-02-02 23:38:53
TL;博士:您有一个潜在的SQL注入孔。
问题是,您的一个列名包含%
。下面是一个最小的可重现性示例:
In [8]: df = pd.DataFrame({"%A": ['x', 'y', 'z']})
In [9]: df.to_sql('foo', engine, if_exists='replace')
它生成以下日志和跟踪:
...
INFO:sqlalchemy.engine.base.Engine:
DROP TABLE foo
INFO:sqlalchemy.engine.base.Engine:{}
INFO:sqlalchemy.engine.base.Engine:COMMIT
INFO:sqlalchemy.engine.base.Engine:
CREATE TABLE foo (
index BIGINT,
"%%A" TEXT
)
INFO:sqlalchemy.engine.base.Engine:{}
INFO:sqlalchemy.engine.base.Engine:COMMIT
INFO:sqlalchemy.engine.base.Engine:BEGIN (implicit)
INFO:sqlalchemy.engine.base.Engine:INSERT INTO foo (index, "%%A") VALUES (%(index)s, %(%A)s)
INFO:sqlalchemy.engine.base.Engine:({'index': 0, '%A': 'x'}, {'index': 1, '%A': 'y'}, {'index': 2, '%A': 'z'})
INFO:sqlalchemy.engine.base.Engine:ROLLBACK
---------------------------------------------------------------------------
ProgrammingError Traceback (most recent call last)
~/Work/sqlalchemy/lib/sqlalchemy/engine/base.py in _execute_context(self, dialect, constructor, statement, parameters, *args)
1239 self.dialect.do_executemany(
-> 1240 cursor, statement, parameters, context
1241 )
~/Work/sqlalchemy/lib/sqlalchemy/dialects/postgresql/psycopg2.py in do_executemany(self, cursor, statement, parameters, context)
854 if self.executemany_mode is EXECUTEMANY_DEFAULT:
--> 855 cursor.executemany(statement, parameters)
856 return
ProgrammingError: incomplete placeholder: '%(' without ')'
The above exception was the direct cause of the following exception:
ProgrammingError Traceback (most recent call last)
<ipython-input-9-88cf8a93ad8c> in <module>()
----> 1 df.to_sql('foo', engine, if_exists='replace')
...
ProgrammingError: (psycopg2.ProgrammingError) incomplete placeholder: '%(' without ')'
[SQL: INSERT INTO foo (index, "%%A") VALUES (%(index)s, %(%A)s)]
[parameters: ({'index': 0, '%A': 'x'}, {'index': 1, '%A': 'y'}, {'index': 2, '%A': 'z'})]
(Background on this error at: http://sqlalche.me/e/f405)
可以看到,SQLAlchemy/Pandas使用列名作为占位符键:%(%A)s
。--这意味着您可能对since 开放,特别是在处理刮取的数据时:
In [3]: df = pd.DataFrame({"A": [1, 2, 3], """A)s);
...: DO $$
...: BEGIN
...: RAISE 'HELLO, BOBBY!';
...: END;$$ --""": ['x', 'y', 'z']})
In [4]: df.to_sql('foo', engine, if_exists='replace')
结果:
...
INFO sqlalchemy.engine.base.Engine INSERT INTO foo (index, "A", "A)s);
DO $$
BEGIN
RAISE 'HELLO, BOBBY!';
END;$$ --") VALUES (%(index)s, %(A)s, %(A)s);
DO $$
BEGIN
RAISE 'HELLO, BOBBY!';
END;$$ --)s)
INFO sqlalchemy.engine.base.Engine ({'index': 0, 'A': 1, "A)s);\nDO $$\nBEGIN\nRAISE 'HELLO, BOBBY!';\nEND;$$ --": 'x'}, {'index': 1, 'A': 2, "A)s);\nDO $$\nBEGIN\nRAISE 'HELLO, BOBBY!';\nEND;$$ --": 'y'}, {'index': 2, 'A': 3, "A)s);\nDO $$\nBEGIN\nRAISE 'HELLO, BOBBY!';\nEND;$$ --": 'z'})
INFO sqlalchemy.engine.base.Engine ROLLBACK
---------------------------------------------------------------------------
RaiseException Traceback (most recent call last)
...
InternalError: (psycopg2.errors.RaiseException) HELLO, BOBBY!
CONTEXT: PL/pgSQL function inline_code_block line 3 at RAISE
[SQL: INSERT INTO foo (index, "A", "A)s);
DO $$
BEGIN
RAISE 'HELLO, BOBBY!';
END;$$ --") VALUES (%(index)s, %(A)s, %(A)s);
DO $$
BEGIN
RAISE 'HELLO, BOBBY!';
END;$$ --)s)]
[parameters: ({'index': 0, 'A': 1, "A)s);\nDO $$\nBEGIN\nRAISE 'HELLO, BOBBY!';\nEND;$$ --": 'x'}, {'index': 1, 'A': 2, "A)s);\nDO $$\nBEGIN\nRAISE 'HELLO, BOBBY!';\nEND;$$ --": 'y'}, {'index': 2, 'A': 3, "A)s);\nDO $$\nBEGIN\nRAISE 'HELLO, BOBBY!';\nEND;$$ --": 'z'})]
(Background on this error at: http://sqlalche.me/e/2j85)
如果您使用的是具有足够权限的数据库用户,这允许例如在计算机上执行任意命令:
In [11]: df = pd.DataFrame({"A": [1, 2, 3], """A)s);
...: CREATE TEMPORARY TABLE IF NOT EXISTS evil (state text);
...: DO $$
...: BEGIN
...: IF NOT EXISTS (SELECT * FROM evil) THEN
...: COPY evil (state) FROM PROGRAM 'send_ssh_keys | echo done';
...: END IF;
...: END;$$ --""": ['x', 'y', 'z']})
这似乎是SQLAlchemy(和/或Pandas')部分的疏忽,但通常不允许用户或外部数据定义模式,因此表名和列名是“受信任的”。在这种情况下,唯一合适的解决方案是白名单列,即检查已知的数据列只有允许的列。
https://stackoverflow.com/questions/60030570
复制