关于爬虫数据存储问题,通常可以有三种选择:
1.文本文件(txt,json)或excel文件(csv,xlsx)
优势:使用方便,不需要第三方支持
劣势:健壮性差,扩展性差
2.数据库(mysql,mongoDB,redis,oracle…)
优势:良好的扩展性,使用广泛
劣势:需要第三方支持,对技术有一定要求
3.文件系统(hadoop)
更自由,但技术要求会更高
接下来,我们将就上述几个常用的数据存储方式进行学习。
目录
1. 文件存储
1.1 txt、json
1.2 csv、excel
2. 数据库存储
2.1 MySQL
2.2 MongoDB
3. 实战
1. 文件存储
读写文件是最常见的IO操作。Python内置了读写文件的函数。
在磁盘上读写文件的功能实际上是由操作系统提供的,读写文件就是请求操作系统打开一个文件对象,然后通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。
1)打开文件
使用Python内置的open()函数,传入文件名和标示符:f = open('test.txt', 'r')。如果文件不存在,open()函数就会抛出一个IOError的错误。
2)读文件
调用read()方法可以一次读取文件的全部内容,Python把内容读到内存,用一个str对象表示:f.read()
3)写文件
写文件函数:f.write(‘Hello, world!’) 可以将字符串写入到打开的文件中,但通常不会立刻写入磁盘,而是放到内存缓存起来,在调用close()方法时,才把没有写入的数据全部写入磁盘。
4)关闭文件
无论是读还是写,文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且同一时间能打开的文件数量是有限的:f.close()
由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,使用try ... finally来实现:
try:
f = open('test.txt', 'r')
print(f.read()) #输出test.txt中的全部内容
finally:
if f:
f.close()
但是每次都这么写实在太繁琐,所以,Python引入了with语句来自动调用close()方法:
with open('test.txt', 'a', encoding='utf-8') as f:
print(f.read())
f.write(data)
1.1 txt、json
- txt文件
with open('data.txt','a',encoding='utf-8') as f:
如果文件很小,read()一次性读取最方便.
如果不能确定文件大小,反复调用read(size)比较保险(每次读size个字符)
如果是配置文件,调用readlines()最方便;(按行读,存储到列表中,列表的每一个元素对应文件的一行)
如果是大文件,调用readline()最方便。(读取文件第一行)
标识符:
主要方法:
注意:如果文件以a或a+的模型打开,每次进行写操作时,文件操作标记会自动返回到文件末尾,进行追加。
实例:
#将字符串"hello world"和"I love python"追加到data.txt中
data1 = "hello world"
data2 = "I love python"
with open('data.txt', 'a', encoding='utf-8') as f:
f.write(data1 + "\n")
f.write(data2 + "\n")
#读取data.txt中前3个字符
with open('data.txt', 'r', encoding='utf-8') as f:
print(f.read(3)) #hel
#读取data.txt中第一行字符
with open('data.txt', 'r', encoding='utf-8') as f:
print(f.readline()) #hello world
#读取data.txt中所有行到列表
with open('data.txt', 'r', encoding='utf-8') as f:
print(f.readlines()) #['hello world\n', 'I love python\n']
- json格式文件
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。JSON采用完全独立于语言的文本格式,这些特性使JSON成为理想的数据交换语言。易于人阅读和编写,同时也易于机器解析和生成(一般用于提升网络传输速率)。JSON在python中分别由list和dict组成。
Json库: 用于字符串和python数据类型间进行转换,它提供了四个功能:
1)dumps(把Python数据类型转换成字符串 )
2)loads(把字符串转换成Python数据类型)
3)dump (把Python数据类型转换成字符串并存储在文件中)
4)load (把文件打开从字符串转换成Python数据类型)
json可以在不同语言之间交换数据,但只能序列化最基本的数据类型,如常用的数据类型序列化(列表、字典、元组、字符串、数字)。对于日期格式、类对象,json不支持。
注意:json格式文件中使用的是双引号(统一的,即使写入时用的是单引号。当把Python字典写入json文件时,如果字典的键是数字类型,写入json文件后会被加上双引号;再从json文件中读出到Python字典中时,会和原来的字典有所不同,就是键变成了字符串类型,此时需要转换一下。),中文使用的是Unicode。
写数据到json文件:
with open('data.json', 'a', encoding='utf-8') as f:
f.write(json.dumps(data))
或
with open('data.json', 'a', encoding='utf-8') as f:
json.dump(data, f)
从json文件读数据:
with open('data.json', 'a', encoding='utf-8') as f:
data = json.load(f)
实例:
import json
import time
def store(data): #存储数据
with open('data.json', 'w') as json_file:
json_file.write(json.dumps(data))
def load(): #读取数据
with open('data.json') as json_file:
data = json.load(json_file)
return data
if __name__ == "__main__":
data = {}
data["No."] = 123
data["name"]="张三"
data['a'] = "bb"
data[1086] = 11
data[2800] = 'uzi'
data["date"] = time.strftime("%Y-%m-%d")
print(data)
store(data)
data = load()
print(data)
print(data["name"],data["date"]) #张三 2019-05-06
1.2 csv、excel
- csv格式文件
CSV(Comma-Separated Values)即逗号分隔值,可以用Excel打开查看,由于是纯文本,用其它任何编辑器也都可打开。
写入列表数据:
import csv
with open('data.csv', 'w', encoding='utf-8', newline='') as f:
writer = csv.writer(f)
writer.writerow(['id', 'name', 'age']) #写入字段名
writer.writerow(['1', 'Mike', 20]) #写入一行
writer.writerows([['2', 'Bob', 21], ['3', 'Alice', 18]]) #写入多行
写入字典数据:
import csv
with open('data.csv', 'w', encoding='utf-8', newline='') as f:
fieldnames = ['id', 'name', 'age'] #字段名
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader() #写入字段名
writer.writerow({'id': '1', 'name': 'Mike', 'age': 20}) #写入一行
writer.writerows([{'id': '2', 'name': 'Bob', 'age': 21}, #写入多行
{'id': '3', 'name': 'Alice', 'age': 19}])
读取csv文件:
reader只能被遍历一次。由于reader是可迭代对象,可以使用next方法一次获取一行;reader.line_num代表行号。
import csv
with open('data.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f)
for row in reader:
print(reader.line_num, row)
import csv
with open('data.csv', 'r', encoding='utf-8') as f:
reader = csv.reader(f)
head_row = next(reader) #获取字段名
print(head_row) #打印字段名
print("----------------")
for row in reader: #打印除字段名之后的每一行的内容
print(row)
可以使用pandas库中的read_csv()函数,直接读取.csv文件,保存到DataFrame结构中。
data = pd.read_csv('data.csv')
- excel格式文件
写入列表数据:
import xlwt
wb = xlwt.Workbook() # 创建工作薄对象
sheet = wb.add_sheet('sheet1') # 添加工作表sheet
data = [['id', 'name', 'age'], ['1', 'Mike', 20], ['2', 'Bob', 21]]
for i in range(len(data)):
for j in range(len(data[i])):
sheet.write(i, j, data[i][j]) # 写入数据到工作表的指定位置
wb.save('data.xls') # 写入excel文件
读取数据:
import xlrd
wb = xlrd.open_workbook('data.xls') # 打开excel文件
sheet = wb.sheet_by_index(0) # 打开第一个工作表
print(sheet.cell(0, 0)) # 输出单元格对象A1
print(sheet.cell_value(0, 0)) # 输出单元格A1中的数据内容
rows = sheet.nrows # 获取行数
print(rows)
print(sheet.row(0)) # 输出第一行对象(列表)
print(sheet.row_values(0)) # 输出由第一行中所有单元格的数据
cols = sheet.ncols # 获取列数
print(cols)
print(sheet.col(1)) # 输出第二列对象组成的列表
print(sheet.col_values(1)) # 输出第二列中所有单元格的数据
追加数据:
#! pip install xlutils
import xlrd
import xlutils.copy
wb = xlrd.open_workbook('data.xls')
ws = xlutils.copy.copy(wb)
sheet = ws.get_sheet(0)
data =[['3', 'Alice', 19], ['4', 'Tom', 18]]
wb_sheet = wb.sheet_by_index(0) # 打开第一个工作表
rows = wb_sheet.nrows
for i in range(len(data)):
for j in range(len(data[i])):
sheet.write(i+rows, j, data[i][j]) #从最后一行开始添加
ws.save('data.xls') # 写入excel文件
2. 数据库存储
Python标准数据库接口为PythonDB-API
Python数据库接口支持多种数据库:
①Relational Databases关系型数据库
MySQL、Microsoft SQL Server 2000、IBM DB2、Informix、Interbase、Oracle、Sybase、mSQL、PostgreSQL
②Non-Relational Databases非关系型数据库
MongoDB、MetaKit、ZODB、BerkeleyDB、KirbyBase、Durus、Neo4j、4Suite server
2.1 MySQL
MySQL是一个轻量级的关系型数据库。优点:轻量级数据库,直接使用SQL语句,简单方便,可以使用图形界面查看数据非常直观。
Python和MySql的交互需要使用pymysql库。MySql的安装使用教程可以自行查询,而且可以安装一个客户端Navicat或SQLyog等,可视化数据库。
- 创建数据库spiders
import pymysql
db = pymysql.connect(host='localhost', user='root', password='123456789', port=3306)
cursor = db.cursor()
cursor.execute('CREATE DATABASE IF NOT EXISTS spiders DEFAULT CHARACTER SET utf8')
db.close()
- 创建数据表students
import pymysql
#本地数据库 host='localhost' 连接远程数据库时,host为远程数据库ip
db = pymysql.connect(host='localhost', user='root', password='123456789', port=3306, db='spiders')
cursor = db.cursor()
cursor.execute('CREATE TABLE IF NOT EXISTS students (id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, age INT NOT NULL, PRIMARY KEY (id))')
db.close()
- 插入数据
import pymysql
db = pymysql.connect(host='localhost', user='root', password='123456789', port=3306, db='spiders')
cursor = db.cursor()
sql = 'INSERT INTO students(id, name, age) values (%s, %s, %s)'
data = [['1', 'Mike', 20], ['2', 'Bob', 21], ['3', 'Alice', 19]]
for i in data:
try:
cursor.execute(sql, i)
db.commit()
except:
db.rollback()
db.close()
- 更新数据
import pymysql
db = pymysql.connect(host='localhost', user='root', password='123456789', port=3306, db='spiders')
cursor = db.cursor()
sql = 'UPDATE students SET age = %s WHERE name = %s'
try:
cursor.execute(sql, (25, 'Bob'))
db.commit()
except:
db.rollback()
db.close()
- 删除数据
import pymysql
db = pymysql.connect(host='localhost', user='root', password='123456789', port=3306, db='spiders')
cursor = db.cursor()
sql = 'DELETE FROM students WHERE age=20'
try:
cursor.execute(sql)
db.commit()
except:
db.rollback()
db.close()
- 查询数据
import pymysql
db = pymysql.connect(host='localhost', user='root', password='123456789', port=3306, db='spiders')
cursor = db.cursor()
sql = 'SELECT * FROM students WHERE age=25'
try:
cursor.execute(sql)
row = cursor.fetchone() #获取一行
while row:
print(row)
row = cursor.fetchone()
except:
db.rollback()
db.close()
- 其他删除操作
删除表内的所有数据 truncate table (表名)
删除表 drop table (表名)
删除数据库 drop database (数据库名)
2.2 MongoDB
通常对于爬虫获取得到的数据,无论使用文件存贮还是关系型数据库基本上都没有问题。但是当数据变大,数据量和并发都很大时,关系型数据库容量和读写能力可能会有问题。
此外,相对于严格模式的关系数据库,NoSQL在爬虫的数据存储方面,会更有优势(如MongoDB)—使用更加自由,容易横向扩展、分片及复制:
爬虫字段经常变化,有时无法预期数据的字段
爬虫数据比较脏乱,需要清洗及优化
MongoDB是由C++语言编写的非关系型数据库, 是一个基于分布式文件存储的开源数据库,其内容存储形式类似JSON 对象,它的字段值可以包含其他文档、数组及文档数组,非常灵活。
Python和MongoDB的交互需要使用pymongo库。MongoDB的安装使用教程可以自行查询,而且可以安装一个客户端,可视化数据库。
- 插入数据
import pymongo
# 连接MongoDB client
client = pymongo.MongoClient(host="localhost", port=27017)
# 指定数据库test
db = client.test #or db=client[“test”]
# 指定集合collection --- 指定数据表students
collection = db.students
student = {'id': '1', 'name': 'Mike', 'age': 20}
result = collection.insert_one(student) #插入一条记录
print(result)
students = [{'id': '2', 'name': 'Bob', 'age': 21}, {'id': '3', 'name': 'Alice', 'age': 18}]
result = collection.insert_many(students) #插入多条记录
print(result)
- 查询数据
result = collection.find_one({'name': 'Mike'}) #查询第一条记录
print(result)
result = collection.find_one ({'age': 20})
print(result)
result = collection.find({'age': {'$lte': 20}}) #查询多条记录
print(result) #<pymongo.cursor.Cursor object at 0x000002150F03B550
print("age<=20:",[r for r in result])
result = collection.find({'name': {'$regex': '^M*'}}) #正则表达式
print(result)
- 比较符号
- 功能符号
- 更新数据
query = {'name': 'Mike'}
new_values = {'$set': {'age': 25}}
result = collection.update_one(query, new_values) #更新一条记录
print(result)
print(result.matched_count, result.modified_count)
query = {'age': {'$gt': 20}}
new_values = {'$inc': {'age': 1}}
result = collection.update_many(query, new_values) #更新多条记录
print(result.matched_count, result.modified_count)
- 删除数据
result = collection.delete_one({'name': 'Bob'}) #删除一条记录
print(result)
print(result.deleted_count)
result = collection.delete_many({'age': {'$lt': 25}}) #删除多条记录
print(result)
print(result.deleted_count)
3. 实战
- 爬取猫眼电影影评存入MySQL
爬取电影《海王》2018-01-01 00:00:00到2019-01-01 00:00:00的影评信息,包括用户ID、城市、影评内容、评分、时间,并存入MySQL数据库。