爬虫文件存储:txt文档,json文件,csv文件
5.1 文件存储
文件存储形式可以是多种多样的,比如可以保存成 TXT 纯文本形式,也可以保存为 Json 格式、CSV 格式等,本节我们来了解下文本文件的存储方式。
5.1.1 TXT文本存储
将数据保存到 TXT 文本的操作非常简单,而且 TXT 文本几乎兼容任何平台,但是有个缺点就是不利于检索,所以如果对检索和数据结构要求不高,追求方便第一的话,可以采用 TXT 文本存储,本节我们来看下利用 Python 保存 TXT 文本文件的方法。
1. 本节目标
本节我们要保存知乎发现页面的热门问题部分,将其问题和答案统一保存成文本形式。
2. 基本实例
首先可以用 Requests 将网页源代码获取下来,然后使用 PyQuery 解析库进行解析,接下来将提取的标题、回答者、回答保存到文本,代码如下:
import requests
from pyquery import PyQuery as pq
url = 'https://www.zhihu.com/explore'
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
}
html = requests.get(url, headers=headers).text
doc = pq(html)
items = doc('.explore-tab .feed-item').items()
for item in items:
question = item.find('h2').text()
author = item.find('.author-link-line').text()
answer = pq(item.find('.content').html()).text()
file = open('explore.txt', 'a', encoding='utf-8')
file.write('\n'.join([question, author, answer]))
file.write('\n' + '=' * 50 + '\n')
file.close()
在这里主要是为了演示文件保存的方式,因此 Requests 异常处理部分在此省去,我们首先用 Requests 提取了知乎发现页面,然后将热门问题的问题、回答者、答案全文提取出来,然后利用了Python提供的 open() 方法打开一个文本文件,获取一个文件操作对象,这里赋值为 file,然后利用 file 对象的 write() 方法将提取的内容写入文件,最后记得调用一下 close() 方法将其关闭,这样抓取的内容即可成功写入到文本中了。
运行程序,可以发现在本地生成了一个 explore.txt 文件,这样热门问答的内容就被保存文文本形式了。
在这里 open() 方法的第一个参数即为要保存的目标文件名称,第二个参数为 a,代表以追加方式写入到文本,另外我们还指定了文件的编码为utf-8,最后写入完成之后,我们还需要调用 close() 方法来关闭文件对象。
3. 打开方式
在刚才的实例中,第二个参数我们设置成了 a,这样在每次写入文本时不会清空源文件,而是在文件末尾写入新的内容,这是一种文件打开方式。关于文件打开方式,其实还有另外的几种,在此列举如下:
r,以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
rb,以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。
r+,打开一个文件用于读写。文件指针将会放在文件的开头。
rb+,以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。
w,打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
wb ,以二进制格式打开一个文件只用于写入。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
w+, 打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
wb+,以二进制格式打开一个文件用于读写。如果该文件已存在则将其覆盖。如果该文件不存在,创建新文件。
a,打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 ab 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+,打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
ab+,以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。
4. 简化写法
另外文件写入还有一种简写方法,那就是使用 with as 语法,在 with 控制块结束时,文件会自动关闭,所以就不需要再调用 close() 方法了。
所以上面的保存方式我们可以简写如下:
with open('explore.txt', 'a', encoding='utf-8') as file:
file.write('\n'.join([question, author, answer]))
file.write('\n' + '=' * 50 + '\n')
如果想保存时将原文清空,那么可以将第二个参数改写为 w,代码如下:
with open('explore.txt', 'w', encoding='utf-8') as file:
file.write('\n'.join([question, author, answer]))
file.write('\n' + '=' * 50 + '\n')
5. 结语
以上便是利用 Python 将结果保存为 TXT 文件的方法,此种方法简单易用,操作高效,是一种最基本的保存数据的方法。
5.1.2 Json文件存储
Json,全称为 JavaScript Object Notation, 也就是 JavaScript 对象标记,通过对象和数组的组合来表示数据,构造简洁但是结构化程度非常高,它是一种轻量级的数据交换格式,本节我们来了解一下利用 Python 保存数据到 Json 文件的方法。
1. 对象和数组
在 JavaScript 语言中,一切都是对象。因此,任何支持的类型都可以通过 Json 来表示,例如字符串、数字、对象、数组等。但是对象和数组是比较特殊且常用的两种类型。
对象,对象在 JavaScript 中是使用花括号 {} 包裹起来的内容,数据结构为 {key1:value1, key2:value2, ...} 的键值对结构。在面向对象的语言中,key 为对象的属性,value 为对应的值。键名可以使用整数和字符串来表示。值的类型可以是任意类型。
数组,数组在 JavaScript 中是方括号 [] 包裹起来的内容,数据结构为 ["java", "javascript", "vb", ...] 的索引结构。在 JavaScript 中,数组是一种比较特殊的数据类型,它也可以像对象那样使用键值对,但还是索引使用得多。同样,值的类型可以是任意类型。
所以一个 Json 对象可以写为如下形式:
[{
"name": "Bob",
"gender": "male",
"birthday": "1992-10-18"
}, {
"name": "Selina",
"gender": "female",
"birthday": "1995-10-18"
}]
由中括号包围的就相当于列表类型,列表的每个元素可以是任意类型,在示例中它是字典类型,由大括号包围。
Json 可以由以上两种形式自由组合而成,可以无限次嵌套,结构清晰,是数据交换的极佳方式。
2. 读取Json
Python 为我们提供了简单易用的 json 库来供我们实现 Json 文件的读写操作,我们可以调用 json 库的 loads() 方法将 Json 文本字符串转为 Json 对象,可以通过 dumps()方法将 Json 对象转为文本字符串。
例如在这里有一段 Json 形式的字符串,它是 str 类型,我们用 Python 将可其转换为可操作的数据结构,如列表或字典。
import json
str = '''
[{
"name": "Bob",
"gender": "male",
"birthday": "1992-10-18"
}, {
"name": "Selina",
"gender": "female",
"birthday": "1995-10-18"
}]
'''
print(type(str))
data = json.loads(str)
print(data)
print(type(data))
运行结果:
<class 'str'>
[{'name': 'Bob', 'gender': 'male', 'birthday': '1992-10-18'}, {'name': 'Selina', 'gender': 'female', 'birthday': '1995-10-18'}]
<class 'list'>
在这里我们使用了 loads() 方法将字符串转为 Json 对象,由于最外层是中括号,所以最终的类型是列表类型。
这样一来我们就可以用索引来取到对应的内容了,例如我们想取第一个元素里的 name 属性,就可以使用如下方式获取:
data[0]['name']
data[0].get('name')
得到的结果都是:
Bob
通过中括号加 0 索引我们可以拿到第一个字典元素,然后再调用其键名即可得到相应的键值。在获取键值的时候有两种方式,一种是中括号加键名,另一种是 get() 方法传入键名。推荐使用 get() 方法来获取内容,这样如果键名不存在的话不会报错,会返回None。另外 get() 方法还可以传入第二个参数即默认值,我们用一个示例感受一下:
data[0].get('age')
data[0].get('age', 25)
运行结果:
None
25
在这里我们尝试获取年龄 age,其实在原字典中是不存在该键名的,如果不存在,默认会返回 None,如果传入第二个参数即默认值,那么在不存在的情况下则返回该默认值。
值得注意的是 Json 的数据需要用双引号来包围,不能使用单引号。例如若使用如下形式表示则会出现错误:
import json
str = '''
[{
'name': 'Bob',
'gender': 'male',
'birthday': '1992-10-18'
}]
'''
data = json.loads(str)
运行结果:
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 3 column 5 (char 8)
在这里会出现 Json 解析错误的提示,是因为在这里数据用了单括号来包围,请千万注意 Json 字符串的表示需要用双引号,否则 loads() 方法会解析失败。
如果我们是从 Json 文本中读取内容,例如在这里有一个data.json 文本文件,其内容是刚才我们所定义的 Json 字符串。
我们可以先将文本文件内容读出,然后再利用 loads() 方法转化。
import json
with open('data.json', 'r') as file:
str = file.read()
data = json.loads(str)
print(data)
运行结果:
[{'name': 'Bob', 'gender': 'male', 'birthday': '1992-10-18'}, {'name': 'Selina', 'gender': 'female', 'birthday': '1995-10-18'}]
以上是读取 Json 文件的方法。
3. 输出Json
另外我们还可以调用 dumps() 方法来将 Json 对象转化为字符串。
例如我们将刚上例中的列表重新写入到文本。
import json
data = [{
'name': 'Bob',
'gender': 'male',
'birthday': '1992-10-18'
}]
with open('data.json', 'w') as file:
file.write(json.dumps(data))
利用 dumps() 方法我们可以将 Json 对象转为字符串,然后再调用文件的 write() 方法即可写入到文本
另外如果我们想保存 Json 的格式,可以再加一个参数 indent,代表缩进字符个数。
with open('data.json', 'w') as file:
file.write(json.dumps(data, indent=2))
这样得到的内容会自动带有缩进,格式会更加清晰。
另外如果 Json 中包含中文字符,例如我们将之前的 Json 的部分值改为中文,再用之前的方法写入到文本。
import json
data = [{
'name': '王伟',
'gender': '男',
'birthday': '1992-10-18'
}]
with open('data.json', 'w') as file:
file.write(json.dumps(data, indent=2))
写入结果如图 5-4 所示:
图 5-4 写入结果
可以看到中文字符都变成了 Unicode 字符,这并不是我们想要的结果。
为了输出中文,我们还需要指定一个参数 ensure_ascii 为 False,另外规定文件输出的编码。
with open('data.json', 'w', encoding='utf-8') as file:
file.write(json.dumps(data, indent=2, ensure_ascii=False))
这样我们就可以输出 Json 为中文了,所以如果字典中带有中文的内容我们需要设置 ensure_ascii 参数为 False 才可正常写入中文。
4. 结语
本节我们了解了用 Python 进行 Json 文件读写的方法,在后面做数据解析时经常会用到,建议熟练掌握。
5.1.3 CSV文件存储
CSV,全称叫做 Comma-Separated Values,中文可以叫做逗号分隔值或字符分隔值,其文件以纯文本形式存储表格数据。该文件是一个字符序列,可以由任意数目的记录组成,记录间以某种换行符分隔,每条记录由字段组成,字段间的分隔符是其它字符或字符串,最常见的是逗号或制表符,不过所有记录都有完全相同的字段序列,相当于一个结构化表的纯文本形式,它相比 Excel 文件更加简介,XLS 文本是电子表格,它包含了文本、数值、公式和格式等内容,而 CSV 中不包含这些内容,就是特定字符分隔的纯文本,结构简单清晰,所以有时候我们用 CSV 来保存数据是比较方便的,本节我们来讲解下 Python 读取和写入 CSV 文件的过程。
1. 写入
在这里我们先看一个最简单的例子:
import csv
with open('data.csv', 'w') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['id', 'name', 'age'])
writer.writerow(['10001', 'Mike', 20])
writer.writerow(['10002', 'Bob', 22])
writer.writerow(['10003', 'Jordan', 21])
首先打开了一个 data.csv 文件,然后指定了打开的模式为 w,即写入,获得文件句柄,随后调用 csv 库的 writer() 方法初始化一个写入对象,传入该句柄,然后调用 writerow() 方法传入每行的数据即可完成写入。
运行结束后会生成一个名为 data.csv 的文件,数据就成功写入了,直接文本形式打开的话内容如下:
id,name,age
10001,Mike,20
10002,Bob,22
10003,Jordan,21
可以看到写入的文本默认是以逗号分隔的,调用一次 writerow() 方法即可写入一行数据
实际执行结果:每隔一行都有一个空行,改写成如下形式则没有空行了:
with open('data.csv', 'w',newline='') as csvfile:
如果我们想修改列与列之间的分隔符可以传入 delimiter 参数,代码如下:
import csv
with open('data.csv', 'w') as csvfile:
writer = csv.writer(csvfile, delimiter=' ')
writer.writerow(['id', 'name', 'age'])
writer.writerow(['10001', 'Mike', 20])
writer.writerow(['10002', 'Bob', 22])
writer.writerow(['10003', 'Jordan', 21])
例如这里在初始化写入对象的时候传入 delimiter 为空格,这样输出的结果的每一列就是以空格分隔的了,内容如下:
id name age
10001 Mike 20
10002 Bob 22
10003 Jordan 21
实际效果:每行的数据都写在第一列了
另外我们也可以调用 writerows() 方法同时写入多行,此时参数就需要为二维列表,例如:
import csv
with open('data.csv', 'w') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['id', 'name', 'age'])
writer.writerows([['10001', 'Mike', 20], ['10002', 'Bob', 22], ['10003', 'Jordan', 21]])
输出效果是相同的,内容如下:
id,name,age
10001,Mike,20
10002,Bob,22
10003,Jordan,21
但是一般情况下爬虫爬取的都是结构化数据,我们一般会用字典来表示,在 csv 库中也提供了字典的写入方式,实例如下:
import csv
with open('data.csv', 'w') as csvfile:
fieldnames = ['id', 'name', 'age']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerow({'id': '10001', 'name': 'Mike', 'age': 20})
writer.writerow({'id': '10002', 'name': 'Bob', 'age': 22})
writer.writerow({'id': '10003', 'name': 'Jordan', 'age': 21})
在这里我们先定义了三个字段,用 fieldnames 表示,然后传给 DictWriter 初始化一个字典写入对象,然后可以先调用 writeheader() 方法先写入头信息,然后再调用 writerow() 方法传入相应字典即可,最终写入的结果是完全相同的,内容如下:
id,name,age
10001,Mike,20
10002,Bob,22
10003,Jordan,21
这样我们就可以完成字典到 CSV 文件的写入了。
另外如果我们想追加写入的话可以修改文件的打开模式,如将 open() 函数的第二个参数改成 a 就可以变成追加写入,代码如下:
import csv
with open('data.csv', 'a') as csvfile:
fieldnames = ['id', 'name', 'age']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writerow({'id': '10004', 'name': 'Durant', 'age': 22})
这样在上面的基础上再执行这段代码,文件内容便会变成:
id,name,age
10001,Mike,20
10002,Bob,22
10003,Jordan,21
10004,Durant,22
可见数据被追加写入到了文件中。
如果我们要写入中文内容的话可能会遇到字符编码的问题,此时我们需要给 open() 参数指定一个编码格式,比如这里再写入一行包含中文的数据,代码需要改写如下:
import csv
with open('data.csv', 'a', encoding='utf-8') as csvfile:
fieldnames = ['id', 'name', 'age']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writerow({'id': '10005', 'name': '王伟', 'age': 22})
在这里需要给 open() 函数指定编码,否则可能会发生编码错误。
注意:
1.指定编码的话,使用文本文档打开csv文件,中文可以显示,但是使用表格打开csv文件中文会乱码
2.不指定编码的话,使用文本文档打开csv文件,中文显示乱码,但是使用表格打开csv文件中文会显示
以上便是 CSV 文件的写入方法。
另外如果我们接触过 Pandas 等库的话,可以调用 DataFrame 对象的 to_csv() 方法也可以非常方便地将数据写入到 CSV 文件中。
2. 读取
我们同样可以使用 csv 库来读取 CSV 文件,例如我们现在将刚才写入的文件内容读取出来,代码如下:
import csv
with open('data.csv', 'r', encoding='utf-8') as csvfile:
reader = csv.reader(csvfile)
for row in reader:
print(row)
运行结果:
['id', 'name', 'age']
['10001', 'Mike', '20']
['10002', 'Bob', '22']
['10003', 'Jordan', '21']
['10004', 'Durant', '22']
['10005', '王伟', '22']
在这里我们构造的是 Reader 对象,通过遍历输出了每行的内容,每一行都是一个列表形式,注意在这里如果 CSV 文件中包含中文的话需要指定文件编码。
另外如果我们接触过 Pandas 的话,可以利用 read_csv() 方法将数据从 CSV 中读取出来,例如:
import pandas as pd
df = pd.read_csv('data.csv')
print(df)
运行结果:
id name age
0 10001 Mike 20
1 10002 Bob 22
2 10003 Jordan 21
3 10004 Durant 22
4 10005 王伟 22
在做数据分析的时候此种方法用的比较多,也是一种比较方便的读取 CSV 文件的方法。
3. 结语
本节我们了解了 CSV 文件的写入和读取方式,它也是一种常用的数据存储方式,需要熟练掌握。