news 2026/9/7 5:29:20

爬虫数据落库MySQL实战:编码、去重与批量写入全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
爬虫数据落库MySQL实战:编码、去重与批量写入全解析

简介:围绕“Python+爬虫+MySQL”这一组合,这套zip压缩包面向需要把网页数据抓取并入库的开发者,提供一套可直接运行的参考实现。压缩包共含17个文件,其中6个py脚本分别负责连接数据库、执行SQL查询、批量写入和参数化安全操作,覆盖了从安装依赖、创建连接、游标执行到结果处理的完整链条;4个md文档则用来解释使用流程与排错思路,是快速上手的入口。此外还包含service、dockerfile、json、db等部署与配置相关文件,整体大小仅76KB,结构清晰、便于按需修改。当前已有306人浏览学习,适合具备基础Python语法、希望快速掌握爬虫与数据库联动的读者。除了常规的pymysql连接、fetchall读取结果及插入语句外,包内还结合安全与性能视角,给出防SQL注入的占位符写法,并对比了批量插入、索引设计等优化建议;同时附带用于MySQL指标监控的Prometheus导出器开发版luck-prometheus-exporter-mysql-develop相关文件,让读者既能完成从网页抓取到入库的闭环,也能延伸到生产环境中对数据库运行状态的监控与调优。无论是爬虫课程设计、数据采集小项目,还是为MySQL运维补充监控能力,都能从中找到可复用的脚本与思路。 把网页数据抓下来存进MySQL,这事儿听起来挺成熟:写个爬虫拿到HTML或者JSON,连上数据库insert一下就完事。但真做起来,你会发现乱码、重复、入库慢、连不上库、字段对不上……每一步都在考验心态。这篇文章把我用爬虫抓数据落库到MySQL的完整思路、核心细节和踩坑记录整理出来,适合刚学会Requests、想把数据持久化存起来的同学参考,也适合已经在做采集任务、想优化入库效率的朋友。

先说明一下标题的语义:这里的“抓取MySQL数据”指的是把网页、接口里的数据用爬虫采集下来,再写入MySQL存储管理。如果反过来,是想从别人提供的MySQL数据源里拉数,那是数据同步的范畴,不在本文讨论范围内。我下面写的所有内容,都围绕“爬虫采集 → 数据清洗 → 落库MySQL”这条链路展开。

1. 项目概述与方案选型

1.1 爬虫抓数据落库到底在解决什么问题

大部分个人项目或小团队做数据采集,目标不只是“抓到数据”,而是“能持续、稳定、可追溯地把数据存下来”。文件存JSON、CSV虽然简单,但数据量上来之后查询、去重、增量更新都很难受。MySQL作为关系型数据库,优势在于结构化存储、SQL查询灵活、事务可靠,生态工具也成熟。

用爬虫配合MySQL,核心要解决三件事:一是采集端稳定,能控制频率、处理反爬;二是数据落地干净,编码一致、字段对齐、无重复;三是任务可恢复,中间断了能续爬,不会白跑。这三件事听起来基础,实操里每一步都有坑。

1.2 爬虫侧技术选型:Requests + BeautifulSoup 还是 Scrapy

爬虫框架的选择取决于目标规模和复杂度。如果只是抓几十个页面、几个字段,用Requests配合BeautifulSoup完全够用,代码直观、调试方便。如果目标站点结构复杂、需要分布式采集、或者要处理大量并发请求,那用Scrapy更合适,它的Downloader Middleware、Item Pipeline天生就是为批量采集设计的。

我在实际项目里的习惯是:验证阶段用Requests快速写原型,确认页面结构后再决定要不要迁移到Scrapy。个人建议,新手先别一上来就上框架,把Requests和解析逻辑吃透,后面用任何框架都事半功倍。本文为了便于演示,统一用Requests方案展开。

1.3 存储层选型:MySQL的身位和边界

有人会问,为啥不用MongoDB存爬虫数据?MongoDB对灵活字段确实友好,改字段不用跑DDL,但它的强项是文档型存储,做聚合统计没有SQL舒服。MySQL的优势在约束和关联:唯一键可以去重,事务能保证批量写入不半途而废,字段类型能约束数据格式。

还有一种常见做法是“文件采集 + 定期导入”:爬虫先写CSV,再通过LOAD DATA导入MySQL。这种方案适合超大数据量,但多了一层文件流转,排错麻烦。我建议绝大多数场景直接让爬虫连库写入,省掉中间文件的维护成本。

2. 四项核心细节:编码、去重、批量写入与异常恢复

2.1 编码与数据清洗:乱码问题的根源

爬虫数据乱码,90%是编码声明和实际解析不一致造成的。HTTP响应头里写的 charset 不一定准,页面meta里声明的也不一定准,有些老站点甚至不声明编码。我用Requests的时候,会先通过resp.encoding或者resp.apparent_encoding做预判,实在不行就用resp.content解码。

入库之前,字符集必须统一成UTF-8,MySQL这边也要对应使用utf8mb4,注意不是utf8。原因很简单:utf8在MySQL里最多存3字节,遇到emoji或者生僻字会直接报错,而utf8mb4是完整的4字节UTF-8,现在的主流选择。接字符串时,代码里的连接串、建表语句、字段注释,全部对齐成utf8mb4,可别一处UTF-8一处latin1,那种混搭最容易出灵异乱码。

2.2 主键去重:避免重复数据的三种策略

重复数据是爬虫落库的第一大痛点。我常用的去重策略有三种,按优先级排:

  • 业务唯一键:在表上给URL或者业务编号建唯一索引,写入时用ON DUPLICATE KEY UPDATE做更新或忽略,这是最稳的方案。
  • 采集前查库去重:插入前先SELECT一下,有就跳过。缺点是每插一条多一次查询,数据量大了性能不好。
  • 内存BloomFilter去重:适合海量URL场景,采集前先过滤一遍,但BloomFilter有误判率,一般配合唯一键一起用。

我在项目里最常用的是方案一,简单、可靠、幂等。同样一条URL跑了两次,第二次进来要么更新更新时间,要么直接忽略,不会产生脏数据。

2.3 连接管理与批量写入:别一条条insert

很多新手写爬虫入库,循环里一条条执行INSERT,数据量小没问题,但爬到几百上千条之后就开始慢。MySQL单条insert的代价主要在SQL解析、网络往返和事务提交。连接不复用、事务频繁提交,性能能差出一个数量级。

正确做法是复用同一个连接,抓一批数据后用executemany()批量写入,最后一次性commit。以500条一批为例,实测比逐条insert快5到10倍。还有一点,爬虫程序别在每次请求前都新建连接,连接建立本身也是成本。

2.4 异常捕获与断点续爬:保证任务可恢复

爬虫最怕的不是报错,而是跑着跑着挂了,数据全丢或者全乱。稳妥的做法是记录采集进度,比如已经抓过的URL列表,或者当前分页的偏移量。这样即使进程中断,重启后也能从断点继续。

落库阶段也同样要有容错:单条数据解析失败不能中断整个批次,我用try/except把异常数据单独记到日志或错误表里,跑完统一排查。注意,批量写入的时候,如果中间有脏数据,整批可能会回滚。所以入库前尽量把字段清洗做好,该转int的转int,该截断的截断。

3. 从零搭建环境并实现完整流程

3.1 MySQL 8.0安装与初始化(本机和Docker两种方式)

Windows和macOS用户去官网下载MySQL Installer或dmg包,Linux用户用包管理器,这几种方式网上教程很多,我重点提醒几个容易出问题的点:一是root密码和认证插件,MySQL 8.0默认用caching_sha2_password,老客户端连不上,如果遇到认证问题,可以切回mysql_native_password;二是服务名和启动方式,Windows装完记得在服务管理器里确认MySQL服务已启动。

如果你装了Docker,最省事的方式是直接起容器,不用在本机装一堆依赖:

docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=yourpass \ -e MYSQL_DATABASE=spider_db \ -v mysql_data:/var/lib/mysql \ mysql:8.0

这里有个细节:MYSQL_DATABASE环境变量会自动帮你建好一个库,免得进去再手敲CREATE DATABASE。数据目录挂在命名卷里,容器删了数据还在,这一点很实用。

3.2 建库建表:字段设计与字符集选择

建表之前先想清楚要存什么。以抓取文章列表为例,至少要存标题、URL、来源、抓取时间。URL一般要加唯一索引,防止重复采集。我在设计时习惯预留一个updated_at字段,配上ON UPDATE CURRENT_TIMESTAMP,这样数据每次更新都会自动盖时间戳,排查问题很好用。

建表语句如下:

CREATE DATABASE IF NOT EXISTS spider_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE spider_db; CREATE TABLE IF NOT EXISTS article ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, url VARCHAR(500) NOT NULL, source VARCHAR(100) DEFAULT '', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_url (url) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

VARCHAR(255)是经验值,标题一般够用。URL类型我习惯定500,有些长链接太长会报超长错误,insert时SQL模式严格的话会直接抛异常,这点要注意。

3.3 完整代码:从Requests请求到pymysql入库

下面是一个完整可跑通的示例,抓一个模拟列表页面,解析出标题和链接,批量写入MySQL。

import requests from bs4 import BeautifulSoup import pymysql # 数据库连接 conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='yourpass', database='spider_db', charset='utf8mb4' ) cursor = conn.cursor() # 请求头设置 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } rows = [] for page in range(1, 4): # 爬3页 url = f'https://example.com/list?page={page}' resp = requests.get(url, headers=headers, timeout=10) resp.raise_for_status() soup = BeautifulSoup(resp.text, 'html.parser') for item in soup.select('.item'): title = item.get_text(strip=True) link = item.find('a')['href'] if title and link: rows.append((title, link, 'example.com')) # 批量写入,重复URL自动更新updated_at sql = """ INSERT INTO article (title, url, source) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE updated_at = NOW() """ cursor.executemany(sql, rows) conn.commit() print(f'本次入库 {len(rows)} 条') cursor.close() conn.close()

这套代码里有两个细节值得留意:第一,executemany的第二个参数必须是一个可迭代的元组列表,顺序要和SQL里的占位符对齐;第二,raise_for_status()会在HTTP状态码非2xx时抛异常,避免把错误页面当成正常内容入库。

3.4 运行与验证:数据入库后的自查手段

写完后不要急着跑大批量,先抓一页试试。跑完用下面几条SQL自查:

-- 看总数 SELECT COUNT(*) FROM article; -- 看最近入库的数据 SELECT id, title, url, created_at FROM article ORDER BY id DESC LIMIT 10; -- 看重复情况 SELECT url, COUNT(*) FROM article GROUP BY url HAVING COUNT(*) > 1;

前两条验证数据有没有进来,第三条验证唯一键有没有生效。如果出现重复,先检查表结构里的UNIQUE KEY是否建上,再检查INSERT语句是否写了ON DUPLICATE KEY UPDATE。数据量一上来,我还会用EXPLAIN看一下查询有没有走索引。

4. 常见问题与排查技巧实录

4.1 高频报错速查表

把平时高频遇到的问题整理成了一张速查表,按错误特征排序:

报错特征常见原因解决方案
ERROR 2002 (HY000): Can't connect to local MySQL server through socketMySQL服务未启动,或者连接时用了socket文件而服务端没有先确认服务状态,Linux下systemctl status mysql;本机连接可改连127.0.0.1走TCP,绕开socket
Access denied for user 'root'@'localhost'密码错误或账号权限问题重置密码;确认root账号是否只允许localhost登录,远程连接需要单独授权
Unknown database 'spider_db'没建库,或者连错实例先执行CREATE DATABASE;确认连接参数里的database名字正确
Incorrect string value: '\xF0\x9F...'字符集用了utf8,遇到4字节emoji表和连接的字符集改成utf8mb4
Lost connection to MySQL server during query单次写入数据量过大,或者连接超时分批写入,每批500条以内;适当调大max_allowed_packet
Data too long for column 'title'字段长度不够扩大VARCHAR长度,或者入库前先截断字符串

4.2 锁表、时区与连接数:三个容易忽略的坑

第一个坑是锁表。批量写入时如果不小心跑了长事务,其他查询会卡住,表现为“看起来像假死”。排查手段是执行SHOW PROCESSLIST;看有没有长时间未提交的事务。我踩过几次坑之后,统一给批量写入的逻辑加上了分批commit,单批次不超过500条,事务很快结束,锁的窗口就很小了。

第二个坑是时区。MySQL 8.0默认的时区跟系统不一定一致,插入CURRENT_TIMESTAMP得到的时间可能和你本地差8小时。可以在连接参数里加init_command='SET time_zone = "+8:00"',或者在jdbc连接串里指定serverTimezone=Asia/Shanghai,用pymysql的话就在连接时直接指定。

第三个坑是连接数。爬虫程序开多线程时,每个线程一个连接,连接池没限制,一下把MySQL连接数打满,后面所有请求都排队。我给你一个保守建议:线程数和数据库连接数保持一致,最好用连接池管理,SQLAlchemy或者DBUtils都可以。线程数不是越大越好,你本地MySQL的max_connections默认151,你开200个线程不炸才怪。

5. 一点个人体会

这套流程我前前后后至少跑了十几个采集项目,给我最大的感受是:爬虫本身的难度往往不在“爬”,而在数据治理。编码、去重、批量写、断点续爬,这些活儿看着琐碎,但每一项都会在你跑到一半的时候跳出来咬你一口。拿我自己来说,早期最狼狈的一次是凌晨挂着脚本跑数据,第二天一看,因为一条脏数据导致整批回滚,白白跑了一晚上。

最后再分享一个小技巧:给入库的数据表加一个source字段,记录数据来源的站点或批次。后续排查数据问题时,能一眼看出这批数据是哪次任务采的,配合created_at能快速定位问题时间段。这个字段加不加,在数据量小的时候没感觉,到了几百万行清洗数据的时候,真的能救命。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 5:29:10

腾讯云AI Skills实战:把聊天Agent养成全能执行者

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 5:28:56

Ant Design Alert 组件设计语言解读:内容、类型与交互变体

Ant Design Alert 组件设计语言解读:内容、类型与交互变体 【免费下载链接】ant-design An enterprise-class UI design language and React UI library 项目地址: https://gitcode.com/GitHub_Trending/an/ant-design 本文基于 Ant Design 官方仓库中 Alert…

作者头像 李华
网站建设 2026/9/7 5:27:55

基于MicroPython的DMA链式触发与Scatter-Gather数据聚合实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 5:26:43

嵌入式设备Web服务器实战:用Mongoose快速搭建远程管理界面

简介:mongoose是一套用C语言编写的轻量级嵌入式Web服务器,专注解决物联网设备、智能家居等资源受限环境下的HTTP/HTTPS服务需求,采用事件驱动和非阻塞I/O模型,API简洁,适合各类嵌入式开发者集成使用。压缩包共包含53个…

作者头像 李华
网站建设 2026/9/7 5:26:27

用流水线思维学汇川:PLC入门、InoProShop实战与知识库搭建

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 5:26:19

分布式Datalog引擎核心解析:增量查询如何取代全量重算

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华