爬虫入库sql注入风险99%源于未参数化拼接原始字段,必须用execute(sql, params)或白名单校验表名/列名等标识符,禁用字符串拼接。

直接说结论:爬虫入库时的SQL注入风险,99%来自把原始采集字段(尤其是标题、作者、URL、正文片段)未经参数化就拼进SQL语句——这不是“要不要修”的问题,而是“不修就等于敞开数据库大门”。
为什么爬虫场景特别容易中招
爬虫拿到的数据天然不可信:网页里可能藏 <script></script> 标签、JSON 字符串、带单引号的中文标点、甚至故意构造的 ' OR 1=1 --。一旦你用 f-string 或 % 拼接进 INSERT 语句,这些内容就会被数据库当 SQL 语法执行。
常见错误写法:
sql = f"INSERT INTO news (title, url) VALUES ('{item['title']}', '{item['url']}')"
哪怕 item['title'] 是 "黑客攻击' -- 注入成功",拼出来就是合法但危险的 SQL。
- 爬虫常批量插入,容易忽略单条数据的风险
- 字段名(如
title)看似固定,但若来源配置动态化(比如从 URL 路径提取表名),news_{source}这类拼接也会触发标识符注入 - 使用
pymysql/sqlite3原生驱动时,误以为%s是 Python 字符串格式化,其实它是数据库占位符——混用会报错或失效
pymysql 和 sqlite3 必须用参数化 execute
原生驱动不支持字符串拼接安全,只认 execute(sql, params) 的第二参数。占位符必须统一用 %s(MySQL)或 ?(SQLite),不能混用 %(name)s 除非你确认驱动支持且传的是字典。
-
pymysql:只接受元组或列表传参,cursor.execute("SELECT * FROM t WHERE id = %s", (123,))—— 注意末尾逗号 -
sqlite3:支持?(位置)和:name(命名),但命名方式需配字典:conn.execute("SELECT * FROM t WHERE id = :id", {"id": 123}) - 绝对不要写
cursor.execute("... %s" % value),这是双重错误:Python 层已拼接,数据库层又丢参数 - 如果字段值是
None,直接传进去即可,驱动自动转为NULL,不用手动改成字符串"NULL"
SQLAlchemy text() + 命名参数才是爬虫友好方案
爬虫常需动态表名、字段列表或条件,ORM 的 session.add() 不够灵活,这时 text() 是刚需,但必须配合命名参数绑定。
- 安全写法:
session.execute(text("INSERT INTO :table (title, url) VALUES (:title, :url)"), {"table": "news_sina", "title": t, "url": u})—— 错!:table是标识符,会被当字符串值,不是真实表名 - 正确做法:表名/字段名必须白名单校验,值才走
:param:if table_name not in ["news_sina", "news_ifeng"]: raise ValueError("非法表名"),再拼f"INSERT INTO {table_name} (...)" - INSERT 列名列表也属标识符:不要让用户控制
"INSERT INTO t ({user_cols}) VALUES ...",应预定义字段集,或用白名单过滤user_cols.split(",") - 批量插入用
session.execute(text(...), [dict1, dict2, ...]),比循环单条快 5–10 倍,且仍保持参数安全
最容易被忽略的非值注入点
爬虫入库时,开发者盯着 title content 防注入,却放过了三类“非值”控制点:
-
INSERT的列名列表:比如从网页<meta name="schema" content="title,author,pubtime">提取后直接拼进 SQL —— 攻击者可注入title,author); DROP TABLE users; -- - 分表逻辑的来源标识:URL 是
https://example.com/sina/news/123,直接取sina拼成news_sina表名,但没校验sina是否在白名单里 - ORDER BY / GROUP BY 字段:爬虫结果页允许用户选“按热度排序”,传参
sort=score,若直接拼ORDER BY :sort,实际执行的是ORDER BY 'score'(字符串字面量),不起作用;若拼ORDER BY {sort}又不校验,就等于开放列名注入
这些地方没有“参数化”一说,只能靠白名单硬控,或者彻底剥离动态权——比如把排序字段映射成内部枚举,sort=hot → order_col = "score DESC",再拼进 SQL。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











