news 2026/10/13 1:21:32

爬虫数据存储实战:从CSV到MongoDB的选型与落地

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
爬虫数据存储实战:从CSV到MongoDB的选型与落地

1. 为什么爬完的数据不能直接扔进文本文件

很多人写爬虫,跑完第一反应就是把结果一股脑写进result.txt或者data.csv,觉得“数据到手就行”。我早期也这么干过,直到有一次需要从三万条记录里筛出“某个字段为空且另一个字段重复”的条目,用文本编辑器打开直接卡死,写脚本逐行解析又因为字段里混着换行符和逗号,切分逻辑改了三版才对。那次之后我才真正理解:爬虫的终点不是“抓到”,而是“存好”。

数据结构化存储要解决的核心问题就三个:存得进去、查得出来、扩得动。文本文件在数据量小、字段少、不需要复杂查询时确实够用,但一旦字段超过五六个、记录上万条、或者需要按条件筛选和关联,它就会变成负担。结构化存储的本质,是把“一堆字符串”变成“有类型、有约束、有关系”的数据,让后续的清洗、分析、展示都能站在一个稳定的地基上。

这一章我会把爬虫数据落地的几条主流路径讲透:从最轻量的 CSV 和 JSON,到单机数据库 SQLite,再到适合中小规模的 MySQL,最后聊一聊什么时候该上 MongoDB 这类文档型存储。每种方案我都会说清楚它适合什么场景、字段怎么设计、代码怎么写、坑在哪里。如果你已经能稳定抓到数据,但每次存数据都靠“随手写个文件”,那这篇内容应该能帮你把最后这一段路走扎实。

2. 先想清楚:你的数据到底该用什么结构存

2.1 从“字段”和“关系”两个维度做判断

选存储方案之前,先别急着打开数据库客户端。拿张纸,把你要存的数据字段列出来,然后问自己两个问题。

第一个问题:这些字段之间有没有“一对多”或“多对多”的关系?比如你爬的是商品列表,每个商品有多个规格、多张图片、多条评论。如果全塞进一张表,就会出现大量重复字段,改一个商品名要更新几十行。这时候就该拆成“商品表 + 规格表 + 评论表”,用外键关联。反过来,如果每条记录都是独立的,字段固定,那单表就够了。

第二个问题:字段结构会不会变?有些数据源今天返回五个字段,明天加一个“促销标签”,后天又改字段名。这种“结构不稳定”的场景,用固定表结构的 MySQL 会很痛苦,每次都要改表。而 MongoDB 这种文档型存储,每条记录可以有自己的字段集合,天然适应这种变化。

我一般用下面这张表做快速判断:

判断维度适合关系型(MySQL/SQLite)适合文档型(MongoDB)适合文件(CSV/JSON)
字段是否固定固定经常变固定
是否有表间关系有弱或无无
数据量级万到千万万到百万千以内
是否需要复杂查询需要一般不需要
是否多人协作是是否

2.2 字段类型设计:别让“看起来是数字”骗了你

这是新手最容易踩的坑。爬下来的数据全是字符串,但存进去的时候要转成正确的类型。比如价格"29.90"应该存成DECIMAL(10,2)而不是VARCHAR,否则后面排序会出现"100" < "99"这种荒唐结果。时间字段"2024-01-15 10:30:00"要转成DATETIME,不然按时间范围查询只能靠字符串比较,遇到格式不统一就崩。

但也不是所有“数字”都该转。比如手机号、身份证号、订单号,它们虽然由数字组成,但不参与数学运算,而且可能有前导零,这种必须用字符串存。我见过有人把订单号存成BIGINT,结果前导零全丢了,对账时对不上。

还有一个细节:空值怎么表示。爬虫经常遇到某个字段没抓到的情况。在数据库里,NULL表示“未知”,空字符串""表示“抓到了但是空的”,两者语义不同。设计表的时候要想清楚,别混用。比如“评论数”没抓到应该是NULL,而不是0,因为0表示“确实没有评论”。

2.3 主键和去重:爬虫数据的生命线

爬虫最怕重复抓取。同一个页面可能因为分页逻辑、重试机制、增量更新被多次抓到。如果没有去重机制,数据库里就会堆满重复记录,后续统计全错。

最稳妥的做法是给每条记录一个业务主键。比如爬商品,就用商品 ID;爬文章,就用文章 URL 的哈希值。这个主键在数据库里设成UNIQUE约束,插入时用INSERT ... ON DUPLICATE KEY UPDATE(MySQL)或INSERT OR REPLACE(SQLite),这样重复数据会自动更新而不是新增。

如果实在找不到天然主键,就自己生成一个:把几个关键字段拼起来做 MD5。但要注意,拼接顺序和分隔符要固定,否则同样的内容会算出不同的哈希。

提示:不要用自增 ID 做主键来去重。自增 ID 只能保证“每条记录唯一”,不能保证“业务内容唯一”。去重必须靠业务字段。

3. 轻量方案:CSV 和 JSON 的正确打开方式

3.1 CSV 不是“逗号分隔”那么简单

很多人以为 CSV 就是“用逗号把字段连起来”,写代码时直接",".join(fields)。结果字段里本身有逗号,整个文件就乱了。正确的 CSV 写法要处理三种特殊情况:字段含逗号、字段含换行符、字段含双引号。标准做法是用双引号把字段包起来,字段内的双引号用两个双引号转义。

Python 的csv模块已经帮你处理好了这些,别自己手写拼接。下面是一个标准写法:

import csv with open('products.csv', 'w', newline='', encoding='utf-8-sig') as f: writer = csv.DictWriter(f, fieldnames=['id', 'name', 'price', 'url']) writer.writeheader() for item in data: writer.writerow({ 'id': item['id'], 'name': item['name'], 'price': item['price'], 'url': item['url'] })

注意encoding='utf-8-sig',这个-sig会写入 BOM 头,让 Excel 打开时不乱码。如果你不用 Excel 看,用utf-8就行。newline=''是为了防止 Windows 下多出空行。

CSV 的优点是通用,任何工具都能打开。缺点是没有类型,所有字段都是字符串;没有索引,查一条数据要全表扫描;不支持嵌套结构,遇到“一个商品多个规格”就只能拆成多行或者把规格塞进一个字段里。所以 CSV 只适合最终导出给非技术人员看,不适合做中间存储。

3.2 JSON 适合存“不规则”的数据

JSON 的优势是能表达嵌套结构。比如一个商品有多个规格,直接写成数组:

{ "id": "A1001", "name": "某商品", "specs": [ {"color": "红", "size": "M", "price": 29.9}, {"color": "蓝", "size": "L", "price": 32.9} ], "tags": ["新品", "包邮"] }

这种结构用 CSV 表达就很别扭。JSON 的另一个好处是和 Python 的 dict/list 无缝转换,json.dumps()和json.loads()两行搞定。

但 JSON 做存储有几个硬伤。第一,不能追加。每次保存都要把整个列表序列化再写文件,数据量大了内存扛不住。第二,不能按条件查询。想找“价格大于 30 的商品”,只能全部加载到内存再遍历。第三,文件损坏风险高。如果程序在写文件时崩溃,整个 JSON 文件可能变成半截,所有数据都读不出来。

所以我的建议是:JSON 只用来做临时交换格式或配置文件,不要用来做爬虫的最终存储。如果非要用,就按天分文件,比如2024-01-15.json,每天一个文件,降低单文件损坏的影响面。

3.3 文件存储的适用边界

说了这么多,文件存储也不是一无是处。下面这些场景用文件反而更合适:

  • 数据量小于一千条,且不需要频繁查询
  • 需要把数据发给别人,对方没有数据库环境
  • 作为数据管道的中转,比如爬虫写 JSON,另一个程序读 JSON 再入库
  • 做数据备份,数据库导出成 CSV 存档

关键是要认清:文件是“输出格式”,不是“存储引擎”。你可以用文件做最终交付,但中间过程最好有数据库兜底。

4. SQLite:单机爬虫的性价比之王

4.1 为什么我推荐新手从 SQLite 开始

SQLite 是一个“嵌入式数据库”,整个数据库就是一个文件,不需要安装服务、不需要配置账号密码、不需要启动进程。Python 标准库直接带sqlite3模块,import就能用。对于单机爬虫来说,它几乎完美:有表结构、有索引、支持 SQL 查询、支持事务、单文件方便备份。

我自己的很多小项目都是 SQLite 起步。比如爬一个站点的文章列表,几万条数据,用 SQLite 存,查询、去重、导出都很方便。等到数据量真的上来了,或者需要多台机器同时写,再迁移到 MySQL,迁移成本也不高,因为 SQL 语法基本通用。

SQLite 的另一个好处是事务安全。爬虫最怕写一半程序崩了,数据处于“写了一半”的状态。SQLite 支持事务,你可以把一批插入放在一个事务里,要么全成功,要么全回滚,不会出现半截数据。

4.2 建表:从爬虫字段到表结构

假设我们爬的是某站点的文章,字段有:文章 ID、标题、作者、发布时间、正文、原文链接、抓取时间。建表语句如下:

CREATE TABLE IF NOT EXISTS articles ( article_id TEXT PRIMARY KEY, title TEXT NOT NULL, author TEXT, publish_time DATETIME, content TEXT, url TEXT UNIQUE, crawled_at DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_publish_time ON articles(publish_time); CREATE INDEX IF NOT EXISTS idx_author ON articles(author);

几个设计点解释一下。article_id用TEXT而不是INTEGER,因为很多站点的 ID 是字符串,可能含字母。url加了UNIQUE约束,防止同一篇文章重复插入。crawled_at用DEFAULT CURRENT_TIMESTAMP,插入时不用手动传,数据库自动填当前时间。索引加在publish_time和author上,因为这两个字段最可能出现在查询条件里。

注意:索引不是越多越好。每个索引都会增加插入和更新的开销。只给“经常出现在 WHERE 子句里”的字段加索引。

4.3 插入与去重:UPSERT 的两种写法

SQLite 从 3.24 版本开始支持UPSERT语法,写法如下:

INSERT INTO articles (article_id, title, author, publish_time, content, url) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(article_id) DO UPDATE SET title = excluded.title, author = excluded.author, publish_time = excluded.publish_time, content = excluded.content, crawled_at = CURRENT_TIMESTAMP;

excluded是 SQLite 的关键字,代表“本次想要插入的那一行”。ON CONFLICT(article_id)指定冲突检测的字段。如果article_id已存在,就执行UPDATE,把标题、作者等字段更新成最新抓到的值,同时刷新抓取时间。

如果你的 SQLite 版本较老,可以用INSERT OR REPLACE:

INSERT OR REPLACE INTO articles (article_id, title, author, publish_time, content, url) VALUES (?, ?, ?, ?, ?, ?);

但INSERT OR REPLACE有个坑:它会先删除旧行再插入新行,如果表上有外键关联,可能触发级联删除。所以能用UPSERT就用UPSERT。

4.4 批量插入:别一条一条来

新手最容易犯的错是每抓一条就execute一次INSERT。这样每条都要开一次事务、写一次磁盘,速度极慢。正确做法是攒一批(比如 500 条)再统一插入,放在一个事务里:

import sqlite3 conn = sqlite3.connect('articles.db') cursor = conn.cursor() batch = [] for item in data: batch.append((item['id'], item['title'], item['author'], item['publish_time'], item['content'], item['url'])) if len(batch) >= 500: cursor.executemany(''' INSERT INTO articles (article_id, title, author, publish_time, content, url) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(article_id) DO UPDATE SET title = excluded.title, content = excluded.content, crawled_at = CURRENT_TIMESTAMP ''', batch) conn.commit() batch.clear() if batch: cursor.executemany('...', batch) conn.commit() conn.close()

实测下来,批量插入比逐条插入快几十倍。500 这个数字不是固定的,可以根据内存和单条数据大小调整。原则是“批越大越快,但别大到内存放不下”。

4.5 SQLite 的并发限制与应对

SQLite 默认是“写独占”的,同一时间只能有一个写操作。如果你的爬虫是多线程的,多个线程同时写会报database is locked。解决办法有三个:

第一,单线程写。爬取可以多线程,但把数据放进一个队列,由一个专门的写线程消费。这是最稳妥的方案。

第二,开启 WAL 模式。WAL(Write-Ahead Logging)允许读写并发,写操作不阻塞读操作:

PRAGMA journal_mode=WAL;

第三,设置超时。连接时加timeout参数,让程序在锁冲突时等待而不是立刻报错:

conn = sqlite3.connect('articles.db', timeout=30)

我一般三个一起用:WAL 模式 + 单写线程 + 30 秒超时。这样即使偶尔有锁冲突,程序也能自己恢复。

5. MySQL:从单机走向多人协作

5.1 什么时候该从 SQLite 换到 MySQL

SQLite 虽好,但有几个场景它扛不住。一是多台机器同时写,SQLite 的文件锁机制在网络上不适用。二是数据量超过千万级,单文件查询性能下降明显。三是需要远程访问,比如爬虫跑在服务器上,分析程序跑在本地。四是需要更细的权限控制,不同人只能看不同的表。

这时候就该上 MySQL 了。MySQL 是客户端-服务端架构,数据存在服务器上,多个客户端可以同时连接。它支持更丰富的字段类型、更复杂的查询、更完善的权限体系。代价是需要安装和配置服务,运维成本比 SQLite 高。

5.2 表结构设计:字符集和引擎的选择

建 MySQL 表时,有两个参数必须显式指定:字符集和存储引擎。

字符集用utf8mb4,不要用utf8。MySQL 的utf8其实是“最多三个字节的 UTF-8”,存不了 emoji 和某些生僻字。utf8mb4才是完整的四字节 UTF-8。爬虫数据里经常有表情符号,用utf8会直接报错或丢数据。

存储引擎用InnoDB,不要用MyISAM。InnoDB 支持事务、支持行级锁、支持外键,是现在的默认选择。MyISAM 虽然查询稍快,但不支持事务,写一半崩了数据就坏了。

CREATE TABLE articles ( article_id VARCHAR(64) PRIMARY KEY, title VARCHAR(512) NOT NULL, author VARCHAR(128), publish_time DATETIME, content MEDIUMTEXT, url VARCHAR(1024), crawled_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_url (url(255)), INDEX idx_publish_time (publish_time), INDEX idx_author (author) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

注意url字段用了VARCHAR(1024),但索引只取前 255 个字符url(255)。因为 MySQL 的索引有长度限制,太长的字段不能全字段索引。取前 255 个字符做唯一性判断,对绝大多数 URL 来说已经足够区分。

5.3 连接池:别每次插入都新建连接

MySQL 每次建立连接都要经过 TCP 握手、身份验证,开销不小。如果每插一条数据就connect一次,性能会惨不忍睹。正确做法是用连接池,程序启动时建好一批连接,用的时候从池里取,用完还回去。

Python 里常用DBUtils或SQLAlchemy的连接池。下面是一个基于pymysql和DBUtils的例子:

from dbutils.pooled_db import PooledDB import pymysql pool = PooledDB( creator=pymysql, maxconnections=10, mincached=2, host='localhost', port=3306, user='crawler', password='your_password', database='crawler_db', charset='utf8mb4' ) def save_batch(rows): conn = pool.connection() try: with conn.cursor() as cursor: cursor.executemany(''' INSERT INTO articles (article_id, title, author, publish_time, content, url) VALUES (%s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE title = VALUES(title), content = VALUES(content), crawled_at = CURRENT_TIMESTAMP ''', rows) conn.commit() finally: conn.close()

maxconnections=10表示最多同时有 10 个连接。mincached=2表示启动时先建 2 个备用。ON DUPLICATE KEY UPDATE是 MySQL 的 UPSERT 语法,和 SQLite 的ON CONFLICT作用一样。

5.4 批量插入的两种方式对比

MySQL 批量插入有两种写法。一种是executemany,把多条INSERT拼成一次网络请求。另一种是拼成一条INSERT INTO ... VALUES (...), (...), (...)的多值语句。

方式优点缺点适用场景
executemany代码简单,参数化安全每条语句独立解析通用场景
多值 INSERT解析一次,速度更快语句长度有限制,拼接麻烦大批量、字段少

我一般用executemany,因为pymysql内部会做优化,而且参数化能防 SQL 注入。如果追求极致速度,可以把executemany的batch_size调大,或者用LOAD DATA LOCAL INFILE直接导入 CSV,但那个配置起来更麻烦。

注意:MySQL 默认的max_allowed_packet是 4MB 或 16MB,一次插入的数据不能超过这个限制。如果批量太大报错,就减小批次大小。

6. MongoDB:当字段结构不稳定时怎么办

6.1 文档型存储的适用场景

有些爬虫目标的数据结构天生就不固定。比如爬电商平台的商品,不同类目的商品属性完全不同:手机有“屏幕尺寸”“电池容量”,衣服有“尺码”“材质”,食品有“保质期”“配料”。如果硬要用 MySQL 存,要么建几十个字段大部分为空,要么用“属性表”做键值对存储,查询起来很别扭。

MongoDB 把每条记录存成一个文档(类似 JSON),不同文档可以有完全不同的字段。查询时用点号访问嵌套字段,比如specs.color。这种灵活性对爬虫来说很友好,因为爬虫面对的数据源经常变,今天加个字段明天删个字段,用 MongoDB 不用改表结构。

但 MongoDB 也不是银弹。它不支持事务(早期版本,新版本支持但性能有代价),不支持多表关联查询,数据一致性弱于 MySQL。所以它适合“数据结构多变、查询简单、不需要强一致”的场景。

6.2 文档设计:嵌套还是引用

MongoDB 有两种组织关联数据的方式:嵌套(embedding)和引用(referencing)。嵌套是把子数据直接放进父文档里,引用是存一个 ID 指向另一个集合。

判断标准是:子数据是否总是和父数据一起查询?如果是,就嵌套。比如文章的评论,每次看文章都要看评论,那就把评论嵌在文章文档里。如果子数据会被独立查询,或者数量很大,就引用。比如用户和订单,订单需要独立按时间查询,就单独存一个集合。

嵌套的优点是一次查询拿到全部数据,不用做关联。缺点是文档有大小限制(MongoDB 单文档最大 16MB),而且更新嵌套数组里的某个元素比较麻烦。引用的优点是灵活,缺点是需要多次查询。

我一般这样处理:一对少(比如一个商品几个规格)用嵌套,一对多(比如一个作者几百篇文章)用引用。

6.3 插入与更新:upsert 的写法

MongoDB 的 upsert 通过update_one的upsert=True参数实现:

from pymongo import MongoClient, UpdateOne client = MongoClient('mongodb://localhost:27017/') db = client['crawler_db'] collection = db['products'] operations = [] for item in data: operations.append(UpdateOne( {'product_id': item['id']}, {'$set': { 'name': item['name'], 'price': item['price'], 'specs': item['specs'], 'crawled_at': datetime.now() }}, upsert=True )) if operations: collection.bulk_write(operations)

UpdateOne的第一个参数是查询条件,第二个是更新操作,upsert=True表示“如果不存在就插入”。bulk_write把多个操作打包发送,比逐条更新快很多。

6.4 索引:MongoDB 查询优化的关键

MongoDB 默认只给_id建索引,其他字段查询都是全集合扫描。数据量大了必须手动建索引:

collection.create_index('product_id', unique=True) collection.create_index('price') collection.create_index([('crawled_at', -1)])

unique=True保证product_id不重复。[('crawled_at', -1)]是降序索引,适合“按抓取时间倒序取最新”的查询。

注意:MongoDB 的索引也占空间,而且会拖慢写入。只给真正需要查询的字段建索引。可以用explain()查看查询是否走了索引。

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

7.1 数据存储常见问题速查表

问题现象可能原因排查方法解决方案
插入报“字段太长”字段长度超限查看报错字段和值长度扩大字段长度或截断
中文变问号字符集不对SHOW VARIABLES LIKE 'character%'改成 utf8mb4
重复数据越来越多没有唯一约束查询重复记录加 UNIQUE 索引 + UPSERT
插入越来越慢索引过多或批次太小查看索引数量删无用索引,增大批次
连接数爆满连接未关闭SHOW PROCESSLIST用连接池,确保 close
时间字段查不到时区不一致对比程序时间和数据库时间统一用 UTC 或统一时区
批量插入报 packet 错误单次数据超限查看 max_allowed_packet减小批次或调大参数

7.2 我踩过的三个坑

第一个坑:用浮点数存价格。早期我用FLOAT存商品价格,结果29.9存进去变成29.8999996,对账时差几分钱。后来全部改成DECIMAL(10,2),精确到分。浮点数在计算机里是二进制近似,不能用来存钱。

第二个坑:忘记提交事务。用 MySQL 时,INSERT之后没有commit,程序跑完数据一条没进去。因为 InnoDB 默认是事务模式,不提交就不生效。后来我养成了习惯:要么用autocommit=True,要么在finally里确保commit或rollback。

第三个坑:URL 做唯一索引但长度不够。我把url设成VARCHAR(255)并加唯一索引,结果有些 URL 超过 255 字符被截断,导致不同 URL 截断后相同,唯一约束误判。后来改成VARCHAR(1024),索引只取前 255 字符,问题解决。

7.3 数据校验:存进去之前先看一眼

我的习惯是,每批数据入库前先做一次快速校验:字段数量对不对、必填字段有没有空、数字字段能不能转成数字、时间字段格式对不对。校验不通过的就记日志,不入库。这样能避免脏数据污染数据库。

def validate(item): required = ['id', 'name', 'url'] for field in required: if not item.get(field): return False, f'missing {field}' try: float(item['price']) except (ValueError, TypeError): return False, 'invalid price' return True, None

这个校验函数很简单,但能拦住大部分低级错误。等数据量大了再回头清洗,成本要高得多。

8. 存储方案选型与迁移建议

8.1 一张表帮你做最终决定

场景推荐方案理由
学习练手,数据量小CSV/JSON零配置,直观
单机爬虫,万级数据SQLite零配置,有 SQL,单文件
多机协作,十万级以上MySQL支持并发,远程访问
字段结构多变MongoDB无固定表结构
需要全文搜索Elasticsearch专业搜索,但运维复杂
需要做数据分析数据仓库列式存储,聚合快

选型的核心原则是:先用最简单的方案跑通,遇到瓶颈再换。不要一上来就上分布式数据库,那是给自己找麻烦。我见过太多项目,数据还没一千条,先搭了一套集群,结果维护成本比开发成本还高。

8.2 从 SQLite 迁移到 MySQL 的注意事项

迁移时最容易出问题的是数据类型映射。SQLite 是动态类型,MySQL 是静态类型。SQLite 里存成TEXT的字段,到 MySQL 要选合适的VARCHAR长度。SQLite 的DATETIME到 MySQL 也是DATETIME,但格式要统一。

迁移步骤我一般这样走:先用 SQLite 的.dump导出 SQL,然后手动改数据类型和语法差异,再导入 MySQL。或者写个脚本,从 SQLite 读出来,批量插入 MySQL。后者更可控,因为可以在插入时做校验和转换。

迁移完成后,别急着删 SQLite 文件,保留一段时间做对照。确认 MySQL 里的数据和 SQLite 一致,再清理。

8.3 数据备份:别等丢了才后悔

不管用什么存储,备份都是必须的。SQLite 直接复制文件就行,但要在没有写入的时候复制,否则可能拿到损坏的文件。MySQL 用mysqldump导出 SQL,MongoDB 用mongodump导出 BSON。

我的习惯是每天凌晨自动备份一次,保留最近七天的备份。备份文件压缩后存到另一个目录,有条件的再同步到另一台机器。备份脚本要写日志,每次备份成功或失败都记下来,不然出了问题都不知道备份有没有在跑。

提示:备份之后一定要验证恢复流程。我见过有人备份了半年,真出事的时候发现备份文件是空的,因为脚本里路径写错了。定期做一次恢复演练,确保备份真的能用。

9. 写在最后:存储是爬虫的“下半场”

爬虫这个事,抓取是上半场,存储是下半场。上半场拼的是反爬对抗、请求调度、解析效率,下半场拼的是数据建模、查询优化、运维稳定。很多人上半场打得漂亮,下半场随便写个文件就交差,结果数据用不起来,等于白抓。

我自己的经验是,在写第一行爬虫代码之前,先把存储方案定下来。字段有哪些、类型是什么、主键是什么、去重怎么做、查询需求是什么,这些问题想清楚了,后面写代码就是填空。反过来,如果先抓再想怎么存,大概率要返工。

SQLite 和 MySQL 的语法差异不大,学会一个另一个很快上手。MongoDB 的思维方式和关系型数据库不同,但核心概念(文档、集合、索引)也不难理解。关键是动手试,拿几百条真实数据跑一遍,比看十篇教程都管用。

最后分享一个小技巧:给每批数据加一个batch_id字段,记录这批数据是哪次爬取任务写入的。这样如果发现某批数据有问题,可以按batch_id批量删除或回滚,不用一条条找。这个字段平时用不上,但出问题的时候能救命。

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

工业互联网中台:从数据孤岛到实时决策的落地实践

简介&#xff1a;本资源是一份面向制造业企业数字化转型决策者、IT架构师及工业互联网解决方案工程师的40页专业PPT课件&#xff0c;聚焦工业互联网背景下数字化中台的顶层设计与落地实践。内容系统阐释中台如何破解传统IT系统“烟囱林立、数据孤岛、重复开发、响应迟滞”等核心…

作者头像 李华
网站建设 2026/10/13 1:20:45

跨界嵌入式前先做迁移评估:别急着买STM32开发板

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

作者头像 李华
网站建设 2026/10/13 1:20:16

python脚本通过docling解析文档qrdant-edge索引使用RAG-MCP填充资料

这是一个用来快速提取文档关键值的脚本&#xff0c;可以用来批量提取一批相同类型&#xff0c;但是不同型号的公共参数值&#xff0c;可以避免人工大量重复性的搜索和来回翻阅工作&#xff1a; 例如快速提取esp32_datasheet_cn.pdf的以下字段 | **型号** | **封装类型** | **…

作者头像 李华
网站建设 2026/10/13 1:20:15

Sysmac Studio入门指南:从工程创建到仿真调试全流程

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

作者头像 李华
网站建设 2026/10/13 1:19:34

跨界嵌入式别急着买开发板:三条真实迁移路线与避坑指南

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

作者头像 李华
网站建设 2026/10/13 1:19:02

八数码难题:从BFS到A*的搜索算法优化实战

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

作者头像 李华