Python爬虫中,MySQL唯一索引导致重复数据插入时ID自增的问题如何解决?

看了一下 scrapy 没这问题。
Python爬虫中,MySQL唯一索引导致重复数据插入时ID自增的问题如何解决?

42 回复

虽然我不懂你说了什么,但是 INSERT 数据难道 ID 不应该自增?


问题核心: 在并发爬虫场景下,使用唯一索引(或唯一约束)防止重复数据时,因 INSERT ... ON DUPLICATE KEY UPDATEREPLACE INTO 等语句触发“更新”行为,导致自增ID不连续跳号。这是MySQL的预期行为,但如果你关心ID的连续性,需要调整策略。

解决方案: 放弃依赖数据库自增ID的连续性,或改用“先查后插”的逻辑来避免触发自增ID的消耗。

具体代码示例(先查后插):

import pymysql
from contextlib import contextmanager

@contextmanager
def get_db_connection():
    conn = pymysql.connect(host='localhost', user='user', password='passwd', database='test_db')
    try:
        yield conn
    finally:
        conn.close()

def insert_article_if_not_exists(article_data):
    """
    article_data: dict, 包含 title, url, content 等字段
    假设 url 字段上有唯一索引
    """
    with get_db_connection() as conn:
        with conn.cursor() as cursor:
            # 1. 先检查是否存在
            check_sql = "SELECT id FROM articles WHERE url = %s"
            cursor.execute(check_sql, (article_data['url'],))
            existing = cursor.fetchone()
            
            if existing:
                print(f"文章已存在,ID: {existing[0]}")
                return existing[0]  # 返回已存在的ID
            
            # 2. 不存在则插入
            insert_sql = """
                INSERT INTO articles (title, url, content, publish_time)
                VALUES (%s, %s, %s, %s)
            """
            cursor.execute(insert_sql, (
                article_data['title'],
                article_data['url'],
                article_data['content'],
                article_data.get('publish_time')  # 使用get避免KeyError
            ))
            new_id = cursor.lastrowid
            conn.commit()
            print(f"新文章插入成功,ID: {new_id}")
            return new_id

# 使用示例
data = {
    'title': 'Python爬虫技巧',
    'url': 'https://example.com/article1',  # 假设这是唯一标识
    'content': '文章内容...',
    'publish_time': '2023-10-01 12:00:00'
}
article_id = insert_article_if_not_exists(data)

关键点解释:

  1. 先执行SELECT查询,判断唯一键(如url)是否已存在。
  2. 如果存在,直接返回已有的ID,不执行INSERT。
  3. 如果不存在,才执行INSERT,此时自增ID会正常连续增长。
  4. 这种方法避免了ON DUPLICATE KEY UPDATEREPLACE触发的“伪插入”,从而防止自增ID被消耗。

注意: 在高并发下,这种方法可能存在竞态条件(两个线程同时查不到然后都插入)。如果爬虫并发很高,需要在程序层加锁(如对URL加分布式锁)或使用数据库的SELECT ... FOR UPDATE(但会降低性能)。对于大多数爬虫场景,先查后插+适度并发控制已经足够。

总结建议: 用“先查后插”代替ON DUPLICATE KEY UPDATE来保持ID连续。

不懂你说了什么,插了数据当然会自增,不知道你是怎么判断重复的,重复的跳过就是了

我猜是说,判断重复的数据后并灭有插入,但是 ID 却消耗掉了.可能和 on duplicate key update 的问题类似吧.

难道爬虫都不过滤重复数据了么,布隆过滤器了解一下

过滤不了,也没用。只能在数据库上设置唯一索引。

用框架就意味着你失去灵活性,就这么简单的任务都无法搞定,所以少用框架

你数据能设置唯一索引, 你代码过滤不了? 数据库不是代码??

这是业务问题,跟框架没什么关系

你该去好好看看 INSERT 语句 ON DUPLICATE 时的用法了。

同意 3 楼…
是不是写入的时候用的是“ replace into ”
而不是 “ on duplicate key update ”

以前用过 scrapy+django+djangoitem 爬到内容直接用 django ORM 入库,每条信息都会有一个唯一索引用识别,
再爬的时候 Foo.objects.get_or_create(defaults__exact=‘bar’, defaults={‘defaults’: ‘baz’})

on duplicate key update 也会导致原 id 变化的

#10 无论 replace into 还是 on duplicate key 都会导致 id+1

#11,写错了应该是用 .objects.update_or_create,没有就新建,有就更新。

ON DUPLICATE ID 一样会自增。

表的数据有的不能更新的,所以这个办法不太适合我。。


字段唯一索引,新增数据重复了,然后 你的处理逻辑是什么?直接丢弃 /新建一个 /更新?
请说出你的故事。

有重复数据的话直接丢弃,这步 mysql 自动执行了,但是 ID 也会增长,我需要的是 ID 不增长,数据直接丢。

既然这样的需求,为什么不考虑用布隆过滤器进行过滤呢…

主要数据太多上亿,采集无法 24 小时开。

innodb 不重复都会出现跳 ID 事务机制导致的



alter table tablename drop column id;
alter table tablename add id mediumint(8) not null primary key auto_increment first;

我觉得没有必要执念于此。

采集完全没有必要 24 小时工作呀,而且上亿的数据布隆过滤器足够应付…

是自增主键跳了吗,正常,多线程插入就会跳。

on duplicate key update 重复是会跳号的。



我读书少你们两个不要骗我, 我要是没记错的话:
设置 UNIQUE 索引后(比如 A)
执行 INSERT INTO TABLE (A,B) VALUES (123,456) ON DUPLICATE KEY UPDATE B = 456;
这是更新旧有行的吧~~

看 mysql 日志就可以了,应该是先写入,再删除

自己实现 UPDATE OR INSERT 啦

如果你用 orm,一般 orm 会提供的

没提供的话也就是查询两次的事,一次查 ID 是否存在,一次插入 /更新数据

哪个 ORM 可以实现?

我用 sqlalchemy,没仔细研究,应该是没有的,所以我查两次

lz 是发现主键 ID 不连续吧,MySQL InnoDB 插入数据是先自增主键 ID,然后再写入数据,因为你有唯一键,所以重复的时候插入失败,但是主键 ID 已经自增了,如果你没办法保证插入不重复的数据,那么把 InnoDB 换成 MyISAM 即可

你去试试就知道了

哦,我理解题目错了,我以为是出现重复记录。原来是自增跳号。innodb 就是这样的,myisam 没有这个问题。

等下,看了 34 楼,我发现我好像看错题了…………囧…………
你们说的是 on duplicate update 后再查数据自增 ID 不连续吧…………[手动捂脸]

加一层 redis set 去重啊 入库前先判断 set 里有没有 有了 pass 没有了 set+1 入库
另外你不要爬一条插一条啊 肯定是到最后一起提交的

很早遇到这个问题,不过我觉得这不算问题,id 只是一个唯一标识而已,跳号其实没有影响;
如果可以的话,在数据爬完之后另外找一个库重新写入一遍也是可以的。

你是想说数据重复了没清洗掉吧

去重啊,偷懒的话 url 设为主键不久 ok 了

回到顶部