MediaCrawler
MediaCrawler copied to clipboard
请问MediaCrawler\store\douyin\douyin_store_impl.py里异步函数save_data_to_json为什么要使用锁
同一文件中的异步函数save_data_to_csv并没有使用锁且正常运行,为什么数据保存类型为json时需要使用锁。 因为我发现随着json文件逐渐变大,爬取数据的速度开始急剧下降,所以观察了一下这部分代码
async with self.lock:
if os.path.exists(save_file_name):
async with aiofiles.open(save_file_name, 'r', encoding='utf-8') as file:
save_data = json.loads(await file.read())
save_data.append(save_item)
async with aiofiles.open(save_file_name, 'w', encoding='utf-8') as file:
await file.write(json.dumps(save_data, ensure_ascii=False))
而且保存方式是从文件中读入→添加内容→再写入文件,是否可以替换成以追加写入方式打开文件?如果不可以,为什么?
CSV是按行写入,且每行写入都是通过await调用,可以用追加写不会有竞争问题。 JSON的数据格式及结构不太好按行写去实现,所以当前的实现方式是比较粗暴的。 另外爬虫速度变慢是锁+json文件变大序列化和反序列化两个一起拖慢速度额的。 更加推荐的方式是存数据库。
jsonl 可能会是一个更好的格式
这两个问题都很有见地,让我们分别来探讨。
为什么JSON存储需要使用锁?
在处理JSON文件时,尤其是在并发环境下,使用锁是为了保证数据的一致性和完整性。由于JSON数据需要作为一个整体读取和写入,当多个异步任务试图同时修改同一个文件时,如果不加锁,就可能出现以下问题:
- 数据覆盖:两个或多个任务可能读取到相同的原始数据,当它们各自添加了新数据后再写回文件,后写的数据会覆盖之前写入的数据,导致丢失。
- 文件损坏:如果多个写操作几乎同时发生,文件的结构可能会被破坏,导致JSON格式不正确,无法再次正确读取。
而CSV文件的写入通常是逐行进行的,不需要读取整个文件内容,因此不太可能出现并发写入导致的数据丢失或文件损坏问题,这就是为什么CSV存储实现中可能没有使用锁。
是否可以使用追加写入方式替代完整读写?
对于JSON文件来说,直接以追加模式写入是不可行的,原因如下:
-
JSON格式要求:JSON文件是以特定格式存储的(通常是一个对象或数组),直接追加内容到文件末尾会破坏JSON的格式,使得文件无法被正确解析。
例如,如果JSON文件是一个数组,直接追加一个新的对象到文件末尾,不仅需要在对象之前添加逗号,还要确保不破坏最外层的数组结构(
[])。这在技术上是可行的,但实现起来相对复杂,容易出错。 -
数据一致性:即使技术上能够通过某种方式实现安全的追加(比如,通过修改文件的最后部分来插入新数据),这种方法在并发环境下仍然需要锁来保证数据的一致性。
性能问题
你提到随着JSON文件变大,性能急剧下降,这是因为整个文件需要被读入内存并解析为对象,然后再次被序列化回文件。这个过程中,内存和CPU的使用量都会随着文件大小的增加而增加。
为了解决这个问题,你可以考虑以下几个方案:
- 分割文件:将数据存储在多个较小的文件中,每个文件存储一定数量的数据项。
- 数据库:对于大量数据,使用数据库而不是文件存储通常是更好的选择。数据库设计来高效处理大量数据的读写和查询。
- 内存数据库:对于临时数据,可以考虑使用内存数据库(如Redis),在需要持久化时再将数据写入磁盘。
综上所述,虽然直接追加写入到JSON文件可能看起来是一个简单的解决方案,但由于格式和并发问题,它并不适用于JSON数据的存储。在处理大量数据时,考虑使用数据库或其他数据存储解决方案会更加有效。
非常感谢,改用csv格式存储后这种问题消失了。json确实不适合并行写入