导读:本期聚焦于追梦人创作的《如何修复Python爬虫存储数据时的SQL注入风险_对采集内容进行预处理入库》,敬请观看详情。爬虫抓取回来的网页内容千奇百怪,一段包含单引号的商品描述、一条带有反斜杠的用户评论,都可能让拼接到SQL语句里的数据变得面目全非,轻则插入失败报错,重则被恶意页面内容注入攻击语句拖走整个数据库。本文围绕Python爬虫入库环节的安全问题展开,先分析字符串拼接SQL为什么危险,再给出参数化查询、占位符写法、特殊字符转义、批量入库executemany等具体修复手段,同时介绍pymysql、sqlite3、SQLAlchemy三种常用库的正确用法,并补充入库前的数据清洗与校验策略,帮助爬虫工程写出既稳定又安全的存储代码。

不少爬虫工程师在写数据入库模块时,习惯用字符串拼接的方式构造INSERT语句,把抓到的标题、正文、评论直接塞进SQL字符串里。这种写法在抓取正常网页时看不出问题,可一旦目标页面本身被植入了恶意内容,或者采集到的数据里含有单引号、注释符等特殊字符,轻则数据插入报错导致任务中断,重则采集内容被构造成攻击载荷,直接威胁数据库安全。本文结合实际场景,聊聊爬虫入库环节的SQL注入风险该怎么排查和修复。

如何修复Python爬虫存储数据时的SQL注入风险_对采集内容进行预处理入库

为什么字符串拼接SQL是爬虫最大的安全隐患

先看一段典型的错误代码。很多初学者写爬虫存储时是这样的:

import pymysql

conn = pymysql.connect(host='127.0.0.1', user='root', password='123456', database='spider')
cursor = conn.cursor()

title = " iPhone 15 保护壳(防摔款"      # 抓取到的标题
content = "商品描述:'优质材质',详见说明"  # 抓取到的正文含单引号

sql = "INSERT INTO goods (title, content) VALUES ('%s', '%s')" % (title, content)
cursor.execute(sql)
conn.commit()

这段代码有两个层面的风险。第一层是稳定性问题:正文里的单引号会破坏SQL语法结构,MySQL收到这条语句后会把'优质材质'当成一段完整的字符串边界,后面的内容全部变成非法语法,直接抛出1064语法错误,爬虫任务随之挂掉。这在采集UGC内容、商品评论、论坛帖子时尤其常见,用户随手打一个引号就能让你的程序崩掉。

第二层才是真正的注入风险。假设某个恶意页面被你抓到了,它的评论内容是whatever', 'x', 'y'); DROP TABLE goods;--,拼接后SQL语句的语义就被完全改变,攻击者可以借此执行任意语句,包括读取其他表的数据、篡改记录甚至删除整张表。爬虫采集的数据来源是不可控的第三方网页,本质上等同于把外部输入直接写进SQL,这正是SQL注入产生的经典条件。所以要明确一个原则:凡是外部采集的内容进入SQL,都必须走参数化,绝不能拼接。

参数化查询:最根本的修复手段

修复SQL注入的标准答案是参数化查询,也就是把SQL语句的骨架和具体数据分开传给数据库驱动,由驱动负责安全处理。不同库的占位符写法略有差别:pymysql和sqlite3用%s,而SQLite原生模块也接受问号?。改写后的正确代码如下:

import pymysql

conn = pymysql.connect(host='127.0.0.1', user='root', password='123456', database='spider')
cursor = conn.cursor()

title = " iPhone 15 保护壳(防摔款"
content = "商品描述:'优质材质',详见说明"

sql = "INSERT INTO goods (title, content) VALUES (%s, %s)"
cursor.execute(sql, (title, content))
conn.commit()
conn.close()

注意这里的%s不再是Python的字符串格式化符,而是数据库驱动的占位符,数据和语句是作为两个独立参数传给execute的。驱动会把数据单独发送给数据库服务器,数据库解析完语句骨架后再绑定参数值,无论数据里包含引号、注释符还是分号,都只会被当作普通字符串内容处理,不可能改变语句语义。

有两个细节容易踩坑。一是参数必须放在元组或列表里,即使只有一个值也要写成(title,)而不是(title),后者只是一个字符串。二是不同驱动的占位符风格不同:MySQL的驱动用%s,PostgreSQL的psycopg2也用%s,但原生sqlite3模块同时支持?和%s,如果项目中途换数据库,记得检查占位符写法是否兼容。

如果项目用的是SQLite,比如轻量级的分布式爬虫本地缓存,写法几乎一样:

import sqlite3

conn = sqlite3.connect('spider_data.db')
cursor = conn.cursor()

sql = "INSERT INTO goods (title, content) VALUES (?, ?)"
cursor.execute(sql, (title, content))
conn.commit()
conn.close()

批量入库与数据预处理:让存储又快又稳

爬虫数据量通常很大,一条一条execute再commit效率很低。参数化查询天然支持批量操作,用executemany一次提交整批数据,配合事务控制可以大幅提升写入速度:

import pymysql

items = [
    ("苹果手机", " descriptions with 'quote' "),
    ("华为耳机", "正常描述"),
    ("小米电视", "包含 -- 注释符的内容"),
]

sql = "INSERT INTO goods (title, content) VALUES (%s, %s)"
try:
    cursor.executemany(sql, items)
    conn.commit()
except Exception as e:
    conn.rollback()
    print(f"批量入库失败,已回滚: {e}")

这里同时演示了异常回滚。批量写入时只要有一条数据触发唯一键冲突或字段超长,整批都会失败,所以建议用try块包裹并rollback,或者改用INSERT IGNORE、ON DUPLICATE KEY UPDATE语句配合参数化来容忍重复数据,这在爬虫去重场景非常实用。

除了SQL层面的防护,入库前对采集内容做一轮清洗同样重要。网页抓下来的字符串经常带着HTML标签、超长空白、不可见字符,直接入库既浪费存储也容易出显示问题。常见的预处理包括用正则去掉HTML标签、截断超长字段、规范化空白字符、过滤掉包含明显攻击特征的极端内容:

import re

def clean_text(text, max_len=500):
    if text is None:
        return ''
    # 去掉HTML标签
    text = re.sub(r'<[^>]+>', '', text)
    # 规范化空白字符(含换行、制表符)
    text = re.sub(r'\s+', ' ', text).strip()
    # 截断到字段允许的长度
    return text[:max_len]

# 入库前统一清洗
items = [(clean_text(t), clean_text(c)) for t, c in raw_items]

需要强调的是,清洗是为了数据质量和长度安全,它不能替代参数化查询。有些教程教你手动给单引号加反斜杠来防注入,这种做法防御不彻底,遇到GBK等多字节编码场景还可能被绕过,务必以参数化为主、清洗为辅。

用ORM进一步降低出错概率

如果项目规模变大,字段和表越来越多,手写SQL的维护成本会上升。这时可以引入SQLAlchemy这类ORM框架,它默认所有查询都是参数化的,从机制上杜绝了拼接SQL的可能:

from sqlalchemy import create_engine, Column, Integer, String, Text
from sqlalchemy.orm import declarative_base, sessionmaker

engine = create_engine('mysql+pymysql://root:123456@127.0.0.1/spider?charset=utf8mb4')
Base = declarative_base()

class Goods(Base):
    __tablename__ = 'goods'
    id = Column(Integer, primary_key=True, autoincrement=True)
    title = Column(String(200))
    content = Column(Text)

Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()

# 直接传入任意特殊字符的字符串都是安全的
session.add(Goods(title="含'引号'的标题", content="任意内容 -- 都安全"))
session.commit()
session.close()

ORM的好处是把表结构映射成Python类,代码可读性和可维护性都更好,而且天然带连接池,对高并发爬虫很友好。缺点是学习曲线和性能开销,对于超大规模的批量写入,原生executemany或者直接用LOAD DATA方案仍然更快。可以根据项目阶段选择:小脚本用pymysql加参数化就够了,中大型爬虫框架建议上ORM。

最后再总结几条实践建议:所有入库数据一律走占位符,代码里全局搜索execute后跟字符串拼接的写法,基本能揪出全部隐患;数据库账号只授予INSERT和SELECT权限,不給DROP和DELETE,即使被注入也把损失控制在最小;采集字段长度在清洗阶段就截断,避免依赖数据库报错;养成使用try块加rollback的习惯,保证批量任务失败后数据一致。做到这几点,爬虫的存储模块就算经得起恶意内容的考验了。

SQL注入参数化查询Python爬虫修改时间:2026-09-11 10:58:54

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/0911/54631.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。