本文以真实场景为例,讲述
aiofiles
找出项目中关键字相关的使用体验,也警醒自己,不要盲目使用异步。
# 打招呼
大家好,我是大家熟悉的技巧君
,故事来源于生活,今天我要说的是关于文件读取
的内容,还有其中的一些踩坑
二三事。
# 故事
前几天,博主接到了老板的一个任务
,是因为我们这边做了一些数据库连接的改造,让我们检查一下咱们组开发的项目里面有没有对应的关键字
。
比如给一个文件目录,让我去里面批量找每个文件的文本内容,最后查询出有没有类似的关键字
,有的话就捞出来让开发进行修改。
# 思考方案
其实我最开始想的比较简单,就是直接拿到文本数据,然后判断keyword
是否在文本中就行。
但事情没有想象的简单,首先我们要匹配的内容可能做了数据库分片
,比如可能需要匹配sharding00~64
这种文本,所以我们需要支持正则,然后我们需要找出对应的行数,也就是说in是行不通了。
# 那我们就转换方案,for line in f, 然后去line里面匹配关键字
,这样就有行号了。
接着我们来看我的实际解决方案:
# 遍历文件夹,获取所有文件
我们知道os.walk是可以拿到文件夹下面所有文件信息的,所以我们编写了这样的方法:
def get_files(dir_path):
file_list = []
for root, dirs, files in os.walk(dir_path):
for f in files:
if root.endswith(".git") or f.endswith(ignore_file):
continue
filepath = os.path.join(root, f)
file_list.append(filepath)
return file_list
2
3
4
5
6
7
8
9
由于公司项目都是git维护的,我们不需要检测.git里面的文件,这样可以加快获取文件的速度
。最后我们返回了文件列表,里面的内容是文件的具体路径。
至于f.endswith(ignored_file),是因为项目中存在很多图片
文件,我们也不需要检测,因为它不是文本。
# 尝试同步写法
def read_file_sync(files):
keyword = "xxx"
flag = re.compile(keyword)
for file in files:
with open(file, mode='r', encoding='utf-8') as f:
current = 0
for line in f:
current += 1
data = re.findall(flag, line)
if len(data) > 0:
print(f"有文件存在敏感信息, 请检查: \nfile: {file}, keyword: {keyword} line: {current}")
2
3
4
5
6
7
8
9
10
11
一气呵成,写下同步读取文件
的代码,这样就可以拿到对应的line和文件了。
# 踩坑1
项目文件中有非utf-8编码的文件,导致直接打开文件失败:
所以我们需要打开文件的时候,不但加上try except处理,还要加上一个参数:errors="ignore"
,这样就可以忽略对应的错误。
# 异步上瘾
因为最近异步库使用的比较多,所以我根据了解,选定了aiofiles去替代同步的open
。
其实这个库
没有什么难度,封装得几乎与open的api一致,所以要上手的话会非常快。所以我也尝了下新鲜:
import aiofiles
async def read_single_async(filepath, keyword):
flag = re.compile(r)
try:
async with aiofiles.open(filepath, mode='r', encoding='utf-8', errors="ignore") as f:
data = await f.readlines()
for idx, line in enumerate(data, 1):
data = re.findall(flag, line)
if len(data) > 0:
print(f"有文件存在敏感信息, 请检查: \nfile: {filepath}, keyword: {keyword} line: {idx}")
except Exception as e:
print(f"{filepath}解析内容失败, error: {e}")
2
3
4
5
6
7
8
9
10
11
12
可以看到,除了await和async的添加以外,open换成了aiofiles.open,其他几乎没有差异。
真实的代码远远不只这些,我是抽离出了最核心的部分
,提供给需要制作类似关键字检查工具的小伙伴。
# 踩坑2
给想要继续使用aiofiles的人一些建议:
虽然官网给出了async for异步迭代器的功能,但是我实测速度非常慢,比同步还慢。千万不要使用!!!
同步和异步速度差距不大,
建议想使用aiofiles
的比对以后再说。
写这个例子是想说明,异步的aiofiles在读文件方面,没有想象中那么给力。
我个人觉得是这样:
能用到异步的地方(节省时间)一般是在await f.readlines(),但是我观察了,文件不大的时候是看不出来差距的,几乎一样的速度。 而我们同步的时候
# 踩坑3
在我极力想要验证aiofiles和open差距的时候,我把文件扩大到了200MB左右,发现差距不大。再继续扩大的话,报了内存超出
的错误。
但同步就表现的很好,虽然我一个文件夹里面4个600MB的文件,耗时也才20多秒可以读完所有。
异步的aiofiles可能因为async for并不完善,导致一直卡在那边。而readlines一次性读出文件又会引起内存超出的错误,所以结论就是,慎用aiofiles
,除非你测试的效果比预期更好。
# 测试截图
- 大文件测试
- 去掉100MB+的文件,改用readlines
当然1次测试是具有一定的偶然性
,但是差距并不如aiohttp那样夸张,也可能因为readlines相对比较快,比起网络io的话。如果aiofiles的async for真的有用的话,我想aiofiles理论上还是会快一些的。