数据的存储
一、TXT文本文件存储
TXT文本几乎兼容任何平台,缺点就是不利于检索。
1、实例
import re
import requests
from pyquery import PyQuery as pq
url = 'https://ssr1.scrape.center/'
html = requests.get(url).text # 用requests库提取网站首页的HTML代码
doc = pq(html)
items = doc('.el-card').items() # 用pyquery库将电影的名称、类别、上映时间、评分信息提取出来。
file = open('moives.txt','w',encoding='utf-8')
for item in items:
# 电影名称
name = item.find('a > h2').text()
file.write(f'名称:{name}\n')
# 类别
categories = [item.text() for item in item.find('.categories button span').items()]
file.write(f'类别:{categories}\n')
# 上映时间
published_at = item.find('.info:contains(上映)').text()
published_at = re.search('(\d{4}-\d{2}-\d{2})',published_at).group(1) \
if published_at and re.search('\d{4}-\d{2}-\d{2}',published_at) else None
file.write(f'上映时间:{published_at}\n')
score = item.find('p.score').text()
file.write(f'{"=" * 50}\n')
file.close()
# open方法的第一个参数是要保存的目标文件名称;第二个参数代表数据以何种方式写入文本,此处为w,表示以覆盖的方式写入;第三个参数指定了文件的编码为utf-8
2、打开方式
- r:以只读方式打开一个文件,意思是只能读取内容,而不能写入。这也是默认方式。
- rb:以二进制只读方式打开一个文件,通常用于打开二进制文件,例如音频、图片。视频等。
- r+:以读写方式打开一个文件,既可以读文件又可以写文件。
- rb+:以二进制读写方式打开一个文件、同样既可以读又可以写。
- w:以写入方式打开一个文件。若存在则覆盖。不存在则创建。
- wb:二进制的写入。
- w+:以读写方式打开一个文件,若存在则覆盖。不存在则创建。
- wb+:二进制的读写。
- a:以追加方式打开一个文件,存在则追加,不存在则创建。
- ab:二进制追加。
- a+:以读写方式打开一个文件,存在则追加,不存在则创建
- ab+:二进制的读写追加方式
3、简化写法
with open('moives.txt','w',encoding='utf-8') as file:
file.write(f'名称:{name}\n')
file.write(f'类别:{categories}\n')
file.write(f'上映时间:{published_at}\n')
file.write(f'评分:{score}\n')
二、JSON文件存储
JSON,全称:JavaScript Object Notation,也就是JavaScript对象标记,通过对象和数组的组合来表示数据,是一种轻量级的数据交换方式。
1、对象和数组
- 对象在JavaScript中是指用花括号{}包围起来的内容,数据结构是{key1:value1,key2:value2,…}这种键值对结构。在面向对象的语言中,key表示对象的属性,value表示属性对应的值,前者可以使用整数和字符串表示,后者可以是任意类型。
- 数组在JavaScript中是指用[]包围起来的内容,数据结构是[“java”,“javascript”,“vb”,…]这种索引结构。
# 这是一个JSON对象
[{
"name":"Bob",
"gender":"male",
"birthday":"1992-10-18"
},{
"name":"Selina",
"gender":"female",
"birthday":"1995-10-18"
}]
2、读取JSON
可以调用JSON库中的loads方法将JSON文本字符串转为JSON对象。实际上,JSON对象就是Python中列表和字典的嵌套与组合。反过来,我们可以通过dumps方法将JSON对象转为文本字符串。
print(type(str)) # <class 'str'>
data = json.loads(str)
print(data)
print(type(data)) # <class 'list'> 列表类型
可以用索引取值:
data[0]['name']
data[0].get('name')
# 推荐使用get方法,即使键名不存在也不会报错。
- 注意、JSON的数据需要用双引号包围起来,而不能使用单引号。
# 读取JSON文本文件 法1:
with open('data.json',encoding='utf-8') as file:
str = file.read()
data = json.loads(str)
print(data)
# 读取JSON文本文件 法2:
data = json.load(open('data.json',encoding='utf-8'))
print(data)
3、输出JSON
调用dumps方法将JSON对象转化为字符串。
import json
data = [{
'name':'Bob',
'gender':'male',
'birthday':'1992-10-18'
}]
with open('data.json','w',encoding='utf-8') as file:
file.write(json.dumps(data))
- 若想保存JSON对象的缩进格式,可以再往dumps方法中添加一个参数indent,代表缩进字符的个数。
with open('data.json','w',encoding='utf-8') as file:
file.write(json.dumps(data,indent=2))
要想输出中文,还需要指定参数ensure_ascii为False,以及规定文件输出的编码:
with open('data.json','w',encoding='utf-8') as file:
file.write(json.dumps(data,indent=2,ensure_ascii=False))
三、CSV文件存储
CSV,全称Comma-Separated Values,中文叫做逗号分隔值或字符分隔值,其文件以纯文本形式****存储表格数据。
1、写入
import csv
with open('data.csv','w') as csvfile: # 打开data.csv文件,指定模式为写入。
writer = csv.writer(csvfile) # 使用csv库的writer方法初始化对象
writer.writerow(['id','name','age'])# 调用writerow方法传入每行的数据
writer.writerow(['10001','Mike','20'])
writer.writerow(['10002','Bob','22'])
writer.writerow(['10003','Jordan','21'])
- 若要修改列与列之间的分隔符,传入delimiter参数:
with open('data.csv','w') as csvfile:
writer = csv.writer(csvfile,delimiter=' ')
writer.writerow(['id','name','age'])
- 也可调用writerows写入多行,此时参数需要传入二维列表:
with open('data.csv','w') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['id','name','age'])
writer.writerow([['10001','Mike','20'],['10002','Bob','22'],['10003','Jordan','21']])
- 一般情况下,爬虫爬取到的都是结构化数据,我们一般会用字典表示这种数据。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'})
# 需要追加模式则w改为a
# 要写入中文则,添加编码格式encoding='utf-8'
2、读取
with open('data.csv','r',encoding='utf-8') as csvfile:
reader = csv.reader(csvfile)
for row in reader:
print(row)
四、MySql存储
- 关系型数据库是基于关系模型的数据库,而关系模型是通过二维表来保存的,所以关系型数据库中数据的存储方式就是行列组成的 表,每一列代表一个字段,每一行代表一条记录。
- 表可以看做某个实体的集合,实体之间存在的联系需要通过表与表之间的关联关系体现。
1、准备工作
pip3 install pymysql
2、连接数据库
import pymysql
db = pymysql.connect(host='127.0.0.1', user='root', password='password', port=3306)
cursor = db.cursor()
cursor.execute('SELECT VERSION()')
data = cursor.fetchone()
print('Database version:', data)
# 创建数据库
cursor.execute("CREATE DATABASE spiders DEFAULT CHARACTER SET utf8mb4")
db.close()
# 通过pymysql的connect方法声明了一个MySQL对象db,传入MySQL运行的host、user(用户名)、password(密码)、port(端口号)。
# 连接成功后,调用cursor方法获得MySQL的操作游标,利用游标可以执行MySQL语句。
# 第一个SQL语句用于获得MySQL的当前版本,然后调用fetchone方法得到了第一条数据,即版本号
# 第二个SQL语句用于创建数据库spiders,默认编码为UTF-8。
3、创建表
db = pymysql.connect(host='127.0.0.1', user='root', password='password', port=3306,db= 'spiders')
cursor = db.cursor()
# 创建一个名为students的数据表
sql = 'CREATE TABLE IF NOT EXISTS students (id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, age INT NOT NULL, PRIMARY KEY (id))'
cursor.execute(sql)
db.close()
4、插入数据
data = {
'id': '20120001',
'name': 'Bob',
'age': 20
}
table = 'students'
keys = ','.join(data.keys()) # id,name,age
values = ','.join(['%s'] * len(data)) # %s,%s,%s
sql = 'INSERT INTO {table}({keys}) VALUES ({values})'.format(table=table, keys=keys, values=values)
try:
if cursor.execute(sql, tuple(data.values())):
print('Successful')
db.commit()
except:
print('Failed')
db.rollback()
db.close()
# 首先构造需要插入的字段:id,name和age。
5、更新数据
data = {
'id': '20120001',
'name': 'Bob',
'age': 21
}
table = 'students'
keys = ','.join(data.keys())
values = ','.join(['%s'] * len(data))
sql = 'INSERT INTO {table}({keys}) VALUES ({values}) ON DUPLICATE KEY UPDATE '.format(table=table, keys=keys, values=values)
update = ','.join(["{key} = %s".format(key=key) for key in data])
sql += update
try:
if cursor.execute(sql, tuple(data.values())*2):
print('Successful')
db.commit()
except:
print('Failed')
db.rollback()
db.close()
6、删除数据
table = 'students'
condition = 'age > 20'
keys = ','.join(data.keys())
values = ','.join(['%s'] * len(data))
# 删除数据
sql = 'DELETE FROM {table} WHERE {condition}'.format(table=table,condition=condition)
try:
cursor.execute(sql)
print('Successful')
db.commit()
except:
print('Failed')
db.rollback()
db.close()
7、查询数据
sql = 'SELECT * FROM students WHERE age >= 20'
try:
cursor.execute(sql)
print('Count:',cursor.rowcount)
row = cursor.fetchone()
while row:
print('Row:',row)
row = cursor.fetchone()
except:
print('Error')
五、MongoDB存储
NoSQL,全称Not Only SQL,意为不仅仅是SQL,泛指非关系型数据库。NoSQL是基于键值对的,而且不需要经过SQL层的解析,数据之间没有耦合性,性能非常高。
- 非关系型数据库又可以分为:
- 键值存储数据库:代表有Redis、Voldemort、Oracle BDB等。
- 列存储数据库:代表有Cassandra、HBase和Riak等。
- 文档型数据库:代表有CouchDB和MongDB等。
- 图形数据库:代表有NEO4J。infoGrid和Infinite Graph等。
因此,对于爬虫的数据存储来说,一条数据可能存在因某些字段提取失败而缺失的情况,而且数据可能随时调整。另外,数据之间还存在嵌套关系。如果使用关系型数据库,一是要提前建表,二是如果数据存在嵌套关系,还需要进行序列化操作才可以实现存储,这不方便。
MongDB是由C++语言编写的非关系型数据库,是一个基于分布式分拣存储的开源数据库系统,其内容的存储形式类似JSON对象。它的字段值可以包含其他文档、数组及文档数组,灵活。
六、Redis缓存存储
Redis是一个基于内存的、高效的键值型非关系型数据库,存取效率极高,而且支持多种数据存储结构,使用起来也简单。
七、Elasticserach搜索引擎存储
这是一个全文搜索引擎,可以快速存储。搜索和分析海量数据。
八、RabbitMQ的使用
在爬取数据的过程中,可能需要一些进程间的通信机制:
- 一个进程负责构造爬取请求,另一个进程负责执行爬取请求。
- 某个数据爬取进程执行完毕,通知另一个负责数据处理的进程开始处理数据。
- 某个进程新建了一个爬取任务,通知另一个负责数据爬取的进程开始爬取数据。
为了降低这些进程的耦合度,需要一个类似消息列队的中间件来存储和转发消息,实现进程间的通信。有了消息列队,以上各机制中的两个进程就可以独立执行,他们之间的通信则由消息列队实现。
- 一个进程根据需要爬取的任务,构造请求对象并放入消息列队,另一个进程从列队中取出请求对象并执行爬取。
- 某个数据爬取进程执行完毕,就向消息列队发送消息,当另一个负责数据处理的进程监听到这类消息时,就开始处理数据。
- 某个进程新建了一个爬取任务后,就向消息列队发送消息,当另一个负责数据爬取的进程监听到这类消息时,就开始爬取数据。
RabbitMQ就是实现消息列队的消息列队中间件之一。