ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

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

爬虫数据落库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-8MySQL这边也要对应使用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_PASSWORDyourpass \ -e MYSQL_DATABASEspider_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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;VARCHAR(255)是经验值标题一般够用。URL类型我习惯定500有些长链接太长会报超长错误insert时SQL模式严格的话会直接抛异常这点要注意。3.3 完整代码从Requests请求到pymysql入库下面是一个完整可跑通的示例抓一个模拟列表页面解析出标题和链接批量写入MySQL。import requests from bs4 import BeautifulSoup import pymysql # 数据库连接 conn pymysql.connect( host127.0.0.1, port3306, userroot, passwordyourpass, databasespider_db, charsetutf8mb4 ) 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 fhttps://example.com/list?page{page} resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) for item in soup.select(.item): title item.get_text(stripTrue) 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): Cant connect to local MySQL server through socketMySQL服务未启动或者连接时用了socket文件而服务端没有先确认服务状态Linux下systemctl status mysql本机连接可改连127.0.0.1走TCP绕开socketAccess denied for user rootlocalhost密码错误或账号权限问题重置密码确认root账号是否只允许localhost登录远程连接需要单独授权Unknown database spider_db没建库或者连错实例先执行CREATE DATABASE确认连接参数里的database名字正确Incorrect string value: \xF0\x9F...字符集用了utf8遇到4字节emoji表和连接的字符集改成utf8mb4Lost connection to MySQL server during query单次写入数据量过大或者连接超时分批写入每批500条以内适当调大max_allowed_packetData too long for column title字段长度不够扩大VARCHAR长度或者入库前先截断字符串4.2 锁表、时区与连接数三个容易忽略的坑第一个坑是锁表。批量写入时如果不小心跑了长事务其他查询会卡住表现为“看起来像假死”。排查手段是执行SHOW PROCESSLIST;看有没有长时间未提交的事务。我踩过几次坑之后统一给批量写入的逻辑加上了分批commit单批次不超过500条事务很快结束锁的窗口就很小了。第二个坑是时区。MySQL 8.0默认的时区跟系统不一定一致插入CURRENT_TIMESTAMP得到的时间可能和你本地差8小时。可以在连接参数里加init_commandSET time_zone 8:00或者在jdbc连接串里指定serverTimezoneAsia/Shanghai用pymysql的话就在连接时直接指定。第三个坑是连接数。爬虫程序开多线程时每个线程一个连接连接池没限制一下把MySQL连接数打满后面所有请求都排队。我给你一个保守建议线程数和数据库连接数保持一致最好用连接池管理SQLAlchemy或者DBUtils都可以。线程数不是越大越好你本地MySQL的max_connections默认151你开200个线程不炸才怪。5. 一点个人体会这套流程我前前后后至少跑了十几个采集项目给我最大的感受是爬虫本身的难度往往不在“爬”而在数据治理。编码、去重、批量写、断点续爬这些活儿看着琐碎但每一项都会在你跑到一半的时候跳出来咬你一口。拿我自己来说早期最狼狈的一次是凌晨挂着脚本跑数据第二天一看因为一条脏数据导致整批回滚白白跑了一晚上。最后再分享一个小技巧给入库的数据表加一个source字段记录数据来源的站点或批次。后续排查数据问题时能一眼看出这批数据是哪次任务采的配合created_at能快速定位问题时间段。这个字段加不加在数据量小的时候没感觉到了几百万行清洗数据的时候真的能救命。本文还有配套的精品资源点击获取
返回列表