爬虫抓取的数据通常都会定期或实时地写入数据库,入库动作看似简单,但安全性往往被忽略。如果使用传统的字符串格式化来构造SQL语句,数据中的特殊字符就可能破坏语句结构,产生SQL注入漏洞。要彻底解决这个问题,SQLAlchemy的参数映射机制是目前Python生态中最成熟的实践。

爬虫入库时SQL注入是怎么产生的
在爬虫项目中,最常见的入库方式是把抓取到的字段直接拼接到INSERT语句里。例如,一个商品爬虫可能会这样写:
# 不安全的SQL拼接示例
def save_item(name, price):
sql = "INSERT INTO items(name, price) VALUES('%s', '%s')" % (name, price)
cursor.execute(sql)
这段代码在字段值正常时没有问题,但如果name字段来自网页数据,且被恶意构造为'); DROP TABLE items; --,则最终生成的SQL会成为:
INSERT INTO items(name, price) VALUES(''); DROP TABLE items; --', '100')
从这个例子可以看到,拼接式SQL无法区分数据和命令,任何包含引号、分号、注释符的字段值都可能被解释为SQL语法。对于爬虫来说,目标网站的结构和内容不可控,一些字段可能包含用户输入或第三方API返回的文本,这些内容天然带有不可信属性。因此,入库时必须采用参数化查询的方式,把数据边界彻底划清。
SQLAlchemy参数映射如何阻断注入风险
SQLAlchemy是Python中最常用的ORM和数据库工具库,它提供的参数映射机制正是为了对抗SQL注入而设计的。核心思想很简单:SQL语句中的动态值使用占位符,真实的参数值通过独立接口传给数据库驱动。驱动层会对参数进行安全编码,确保参数永远不会被当作SQL指令执行。
在SQLAlchemy Core层面,我们可以使用text()函数编写带绑定参数的SQL。占位符使用冒号加名称表示,比如:name、:price。执行时,将参数以字典形式传递给execute()方法,字典中的键对应占位符名。下面是一个标准示例:
from sqlalchemy import create_engine, text
engine = create_engine("mysql+pymysql://user:password@localhost/db")
def save_item(name, price):
with engine.connect() as conn:
conn.execute(
text("INSERT INTO items(name, price) VALUES(:name, :price)"),
{"name": name, "price": price}
)
即使name的值真的包含'); DROP TABLE items; --,当它作为:name参数传入时,数据库驱动只会把它当作一个普通的字符串常量,所有引号和分号都会被转义,不会影响外部SQL语句的结构。这就是参数映射最核心的价值。
除了Core接口,SQLAlchemy ORM也提供了同样安全的写入方式。定义好模型类后,直接通过session.add()添加对象,ORM会自动生成参数化语句。这种方式更贴近业务逻辑,代码可读性更高:
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Float
from sqlalchemy.orm import Session
Base = declarative_base()
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True)
name = Column(String(255))
price = Column(Float)
session = Session(engine)
session.add(Item(name=name, price=price))
session.commit()
ORM方式同样采用了参数映射,底层会将模型字段值绑定到预编译的INSERT语句中,因此不存在注入可能。使用哪种方式取决于项目架构:如果希望把SQL写得更透明,选择Core的text();如果项目已经有完整的模型层,直接使用ORM会更加自然。
在爬虫项目中落地SQLAlchemy参数映射
理解了原理后,我们在一个真实的爬虫入库流程中实践参数映射。假设我们从某公开API抓取商品信息,字段包括商品名称和价格,抓取后批量写入数据库。使用SQLAlchemy的批量接口可以提升效率,同时保持参数化安全。
首先,我们需要建立数据库连接,并确保表结构已定义。为了示例简洁,这里直接使用前面定义的Item模型。接下来,把爬虫抓取到的数据封装成模型对象,通过session.bulk_save_objects()一次性写入:
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine("mysql+pymysql://user:password@localhost/db")
Session = sessionmaker(bind=engine)
session = Session()
def bulk_save(items):
# items: 包含(name, price)元组的列表
objects = [Item(name=name, price=price) for name, price in items]
session.bulk_save_objects(objects)
session.commit()
如果不想引入ORM模型,也可以使用executemany或SQLAlchemy Core的批量插入语法,同样利用参数映射。比如用text()配合列表参数:
with engine.connect() as conn:
conn.execute(
text("INSERT INTO items(name, price) VALUES(:name, :price)"),
[{"name": name, "price": price} for name, price in items]
)
值得注意的是,无论批量还是单条写入,都必须坚持使用参数占位符,绝不能在语句里直接格式化字段值。此外,还可以设置连接池的字符集为utf8mb4,并对长度超过数据库列宽的数据做截断或清洗,这些配套措施能进一步提升入库健壮性。但防住SQL注入,参数映射是最关键的一环。
最后,将参数映射贯彻到所有与数据库交互的代码中,尤其要注意爬虫框架中回调函数、日志记录等地方可能隐藏的拼接SQL。定时审查代码,用sqlalchemy_utils等工具检测潜在风险,也是保障数据库安全的好习惯。
Python爬虫SQL注入SQLAlchemy参数映射修改时间:2026-08-12 05:55:00