不会用python来读取文件的话,我想是不太可能会学会爬虫的,不然你怎么处理获取的数据
本文所用的weekly_hiring_comments.json示例的结构如下:
1 2 3 4 5 6 7 8 9 10 [ { "issue" : 692 , "author" : "ruanyf" , "created_at" : "2019-07-18T07:00:46Z" , "text" : "### **高级 Web 前端工程师**\r\n \r\n[深圳追一科技](https://zhuiyi.ai/),人工智能创业公司。工作地点:深圳市南山区。\r\n\r\n公司主打 NLP 方向的 B 端 AI 产品落地,诚求英才。要求4年以上实际前端项目的开发经验,熟练掌握 Vue 或 React 生态,查看[详细信息](https://www.zhipin.com/job_detail/79ca9be7fb736e4d03Nz3924FVA~.html)。\r\n\r\nEmail 联系:[winchang@wezhuiyi.com](mailto:winchang@wezhuiyi.com)" , "url" : "https://github.com/ruanyf/weekly/issues/692#issuecomment-512691467" } , ]
open方法
读写文件一般都通过open方法来进行操作,基本用法看下面的代码就很容易理解了:
1 2 3 4 5 with open ("weekly_hiring_comments.json" , "r" , encoding="utf-8" ) as f: posts = json.load(f) with open ("本科及以上.json" , "w" , encoding="utf-8" ) as f: json.dump(bachelor_posts, f, ensure_ascii=False , indent=2 )
三个参数分别为:
file(文件路径)
mode(操作方式)
encoding(解码方式)
mode 的值包括以下几种:
‘r’ ,表示读取文件
‘w’ 表示写入文件(现有同名文件会被覆盖)
‘a’ 表示打开文件并追加内容,任何写入的数据会自动添加到文件末尾
‘r+’ 表示打开文件进行读写
mode 实参是可选的,省略时的默认值为 ‘r’
当然,如果看源码的话还能看到一堆参数,但我们一般只用得到上述的三个参数:
1 2 3 4 5 6 7 8 9 10 def open ( file: FileDescriptorOrPath, mode: OpenTextMode = "r" , buffering: int = -1 , encoding: str | None = None , errors: str | None = None , newline: str | None = None , closefd: bool = True , opener: _Opener | None = None , ) -> TextIOWrapper: ...
现在的问题是这个读写的文件会有很多种格式(.pdf,.txt,.json,.html,.js, …),我们来看看open是怎么处理的:
text mode - 默认格式: 通常情况下,文件以该模式打开,一般使用utf-8进行编码,该模式主要用于处理文本文件
binary mode - 以二进制模式读取文件,需要在mode词尾加上一个’b’,如wb,ab等,在二进制模式下无法指定encoding(也没有必要指定),该模式主要用于读取.png,.mp3,.pdf这样的二进制文件
换句话说,open函数根本不会对每种文件进行特殊处理,只是有两种读取方式而已了,对于一些特殊的文件格式,我们都需要额外用其他库去处理.
但是对于一般的文件格式,open函数读取文件名后会返回一个TextIOWrapper对象,它有两种常用的方法:
.read()方法: 将全文读入一个字符串变量
例子: content = f.read()
.write()方法: 写入字符串
例子: f.write(f"## 招聘 \n\n")
json系统库:处理json文件
既然是系统库,那自然要先导入后使用,事实上只有两个常用函数: json.load()和json.dump().
示例
1 2 3 4 5 6 7 with open ("weekly_hiring_comments.json" , "r" , encoding="utf-8" ) as f: posts = json.load(f) bachelor_posts = [] with open (out_dir / "本科及以上.json" , "w" , encoding="utf-8" ) as f: json.dump(bachelor_posts, f, ensure_ascii=False , indent=2 )
看看源码和参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 (function) def load ( fp: SupportsRead[str | bytes ], *, cls: type [JSONDecoder] | None = None , object_hook: ((dict [Any , Any ] ) -> Any ) | None = None , parse_float: ((str ) -> Any ) | None = None , parse_int: ((str ) -> Any ) | None = None , parse_constant: ((str ) -> Any ) | None = None , object_pairs_hook: ((list [tuple [Any , Any ]] ) -> Any ) | None = None , **kwds: Any ) -> Any (function) def dump ( obj: Any , fp: SupportsWrite[str ], *, skipkeys: bool = False , ensure_ascii: bool = True , check_circular: bool = True , allow_nan: bool = True , cls: type [JSONEncoder] | None = None , indent: int | str | None = None , separators: tuple [str , str ] | None = None , default: ((Any ) -> Any ) | None = None , sort_keys: bool = False , **kwds: Any ) -> None
速览一下就知道用法了,读json文件时指定文件名,写json文件时指定写入内容和写入文件名就可以了
处理md文件
md文件没有专门的库,直接读写就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 with open ("weekly_hiring_comments.json" , "r" , encoding="utf-8" ) as f: posts = json.load(f) out = Path("weekly_hiring_comments.md" ) with out.open ("w" , encoding="utf-8" ) as f: for i, p in enumerate (posts, 1 ): f.write(f"## 招聘 {i} \n\n" ) f.write(f"- Issue: #{p['issue' ]} \n" ) f.write(f"- 作者: {p['author' ]} \n" ) f.write(f"- 时间: {p['created_at' ]} \n" ) f.write(f"- 来源: {p['url' ]} \n\n" ) f.write(p["text" ]) f.write("\n\n---\n\n" )
pathlib库
该库在不同平台下都能轻松读取文件路径 ,而不需要操心系统问题或者字符串问题.
如果以前从未用过此模块,或不确定哪个类适合完成任务,那要用的可能就是 Path。它在运行代码的平台上实例化为具体路径.
接下来我们来详细介绍这个Path对象
Path对象的创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pathlib import Pathout_file: Path = Path("a.md" ) out_file: Path = Path("modules" ) / "a.py" out_dir: Path = Path("modules" ) out_file: Path = out_dir / "issues.md"
上述的代码由于没有指定绝对路径,故都是相对于python运行目录的路径,但我们也可以指定绝对路径,如下文所示:
1 2 3 4 5 6 7 from pathlib import Pathabs_path_win = Path("C:/Users/Admin/Desktop/a.md" ) abs_path_unix = Path("/home/user/project/a.md" )
也就是说,我们不需要再去折腾不同操作系统的路径问题了,统一用/就可以确定相对的路径.
使用Path来创建文件夹
只需要调用mkdir方法即可:
1 2 out_dir: Path = Path("issues_md" ) out_dir.mkdir(exist_ok=True )
exist_ok参数的作用: 默认为False,设置为True时,即便当前路径下有这个文件夹,也不会报错
Path对象的open方法
事实上,这个open方法与python内置的open方法基本没有区别,只是把文件路径提到外面来了而已:
1 2 3 4 5 6 7 out_dir: Path = Path("issues_md" ) out_dir.mkdir(exist_ok=True ) for issue, items in by_issue.items(): path = out_dir / f"issue_{issue} .md" with path.open ("w" , encoding="utf-8" ) as f: f.write(f"# Issue #{issue} 招聘汇总\n\n" )
Path对象的glob方法(待补充)
实战
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import jsonfrom pathlib import Pathfrom collections import defaultdictwith open ("weekly_hiring_comments.json" , "r" , encoding="utf-8" ) as f: posts = json.load(f) by_issue = defaultdict(list ) for p in posts: by_issue[p["issue" ]].append(p) out_dir: Path = Path("issues_md" ) out_dir.mkdir(exist_ok=True ) for issue, items in by_issue.items(): path = out_dir / f"issue_{issue} .md" with path.open ("w" , encoding="utf-8" ) as f: f.write(f"# Issue #{issue} 招聘汇总\n\n" ) for i, p in enumerate (items, 1 ): f.write(f"## 招聘 {i} \n\n" ) f.write(f"- 作者: {p['author' ]} \n" ) f.write(f"- 时间: {p['created_at' ]} \n" ) f.write(f"- 来源: {p['url' ]} \n\n" ) f.write(p["text" ]) f.write("\n\n---\n\n" )
re系统库
该库是对正则表达式(regular expression)的封装,所以叫re.
compile方法
compile是一个实例化pattern对象的方法,pattern一词在re中指的是正则表达式字符串
1 2 3 4 5 prog = re.compile (pattern) result = prog.match (string) result = re.match (pattern, string)
事实上,re库中的大多数常用方法都有两种写法,一种是模式.方法(参数),另一种是方法.(模式,参数).为了规范起见,我们后面都采用模式.方法(参数)写法,就不再次说明了
search方法与match方法
re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import re line = "Cats are smarter than dogs" ; matchObj = re.match ( r'dogs' , line, re.M|re.I) if matchObj: print "match --> matchObj.group() : " , matchObj.group() else : print "No match!!" matchObj = re.search( r'dogs' , line, re.M|re.I) if matchObj: print "search --> searchObj.group() : " , matchObj.group() else : print "No match!!"
运行结果
1 2 No match!! search --> searchObj.group() : dogs
实战
下面的整个代码流程为:
载入json文件为列表posts
使用compile方法组织匹配模式
将posts里对应学历要求的帖子中的text字段里的值插入列表中
导出json文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 with open ("weekly_hiring_comments.json" , "r" , encoding="utf-8" ) as f: posts = json.load(f) bachelor_patterns = [ r"本科及以上" , r"本科以上" , ] master_patterns = [ r"硕士及以上" , r"硕士以上" , ] bachelor_re = re.compile ("|" .join(bachelor_patterns)) master_re = re.compile ("|" .join(master_patterns)) bachelor_posts = [] master_posts = [] for p in posts: text = p.get("text" , "" ) if master_re.search(text): master_posts.append(p) elif bachelor_re.search(text): bachelor_posts.append(p) out_dir = Path("degree_split" ) out_dir.mkdir(exist_ok=True ) with open (out_dir / "本科及以上.json" , "w" , encoding="utf-8" ) as f: json.dump(bachelor_posts, f, ensure_ascii=False , indent=2 ) with open (out_dir / "硕士及以上.json" , "w" , encoding="utf-8" ) as f: json.dump(master_posts, f, ensure_ascii=False , indent=2 )
读取.env文件
对于密码,API密钥这些文件,用json文件存取不够方便也不够安全,因此我们有了.env文件,样式如下:
1 2 token ="ghp_xxxxxxxxxxxx"
当我们想要读取这个.env文件中的token字段时,我们可以导入dotenv库和os库来进行简单的读取:
1 2 3 4 5 from dotenv import load_dotenvimport osload_dotenv() TOKEN = os.getenv("token" )
load_dotenv()函数会递归寻找.env文件并返回内容供os库读取,从而避免了写路径的麻烦.