Python3编写网络爬虫12-数据存储方式五-非关系型数据库存储
非关系型数据库存储
NoSQL 全称 Not Only SQL 意为非SQL 泛指非关系型数据库。
基于键值对 不需要经过SQL层解析 数据之间没有耦合性 性能非常高。
非关系型数据库可细分如下:
键值存储数据库: 代表有Redis、Voldemort、和Oracle BDB等。
列存储数据库:代表有Cassandra、HBase、和Riak等。
文档型数据库:代表有CouchDB、Mongodb等。
图形数据库:代表有Neo4J、InfoGrid、Infinite、Graph等。
对于爬虫的数据存储来说,一条数据可能存在某些字段提取失败而缺失的情况,而且数据可能随时调整。
如果使用关系型数据库 需要提前建表 如果存在数据嵌套关系 需要序列化操作才可以存储。
如果使用非关系型数据库就可以避免一些麻烦,更简单高效。
1.MongoDB存储
简介:MongoDB 是由c++ 语言编写的非关系型数据库 是一个基于分布式文件存储的开源数据库系统,
其内容存储形式类似JSON对象,字段值可以包含文档、数组、非常灵活。
安装 : 安装MongoDB 并确保已经运行
官网 https:www.mongodb.com 下载安装
windows : https://www.mongodb.com/download-center/community
安装后 进入MongoDB安装目录 在bin目录下新建同级目录data 进入data 新建子文件夹db 用于存储数据目录
打开命令行 进入bin目录 运行MongoDB服务 : mongod --dbpath "安装路径\data\db"
输出信息 证明启动了mongodb服务(缺点不能关闭命令行)
想办法设置成可以管理的
1.在bin目录下新建同级目录logs 进入目录新建文件mongodb.log 用于保存mongodb日志
(如不过能在目录新建可以在外新建复制进去)
2.打开管理员运行命令行 输入如下内容:
mongod --bind_ip 0.0.0.0 --logpath "mongodb.log文件路径+文件名" --logappend --dbpath "db目录路径" --port 27017 --serviceName "MongoDB" --serviceDisplayName "MongoDB" --install
(含义:绑定IP为任意ip均可访问 指定日志路径,数据库路径和端口 指定服务名) 无报错信息表示服务安装成功。
3. 打开计算机管理 -> 服务 找到MongoDB 启动服务。
4. 添加环境变量 mongo.exe
5. 打开命令行 输入mongo 进入交互模式。
安装: python的PyMongo库
pip install pymongo
验证:
import pymongo pymongo.version
输出版本信息
证明安装成功
2.连接MongoDB
示例:
import pymongo client = pymongo.MongoClient(host='localhost',port=27017)#创建mongodb对象 #也可以写成 client = MongoClient('mongodb://localhost:27017/')
3.指定数据库
db = client.test #也可以写成 db = client['test']
4.指定集合
collection = db.students collection = db['students']
5.插入数据
student = { 'id':'20180001', 'name':'Bod', 'age':20, 'gender':'male' } result = collection.insert(student) print(result)#返回_id值
在mongodb中 每一条数据都会有一个_id属性来唯一标识 如果没有会自动生成一个Object类型的_id属性。
insert() 方法会在执行后返回_id值
插入多条数据
student1 = { 'id':'20180001', 'name':'Bod', 'age':20, 'gender':'male' } student2 = { 'id':'20180002', 'name':'Mike', 'age':22, 'gender':'male' } result = collection.insert([student1,student2]) print(result)#返回对应的_id集合列表
实际上 在3.X版本中 官方不推荐使用了 但是使用也没什么问题
官方推荐 insert_one() 和 insert_many() 区分插入单条数据和多条数据
示例:
result = collection.insert_one(student) print(result)#返回InsertOneResult对象 print(result.inserted_id)#利用inserted_id 返回_id result = collection.insert_many([student1,student2]) print(result)#返回InsertManyResult对象 print(result.inserted_ids)#利用inserted_ids 返回_id列表
6.查询数据
find_one() 返回单个结果 查询不到返回None
find() 返回一个生成器对象
示例:
result = collection.find_one({'name':'Mike'}) print(type(result))#dict print(result)
拓展:
也可以根据ObjectId 来查询 需要使用bson库 objectid
from bson import ObjectId result = collection.find_one({'_id':ObjectId('5c126cdf11bede10f06c6184')}) print(result)
示例:
results = collection.find({'age':20}) print(results)#结果为cursor类型 相当于生成器 for result in results: print(result)
如果要查询年龄大于20
示例:
results = collection.find({'age':{'$gt':20}})
比较符号
$lt 大于 {'age':{'$lt':20}} $gt 小于 .. $lte 小于等于 .. $gte 大于等于 .. $ne 不等于 .. $in 在范围内 {'age':{'$in':[20,25]}} $nin 不再范围 {'age':{'$nin':[20,25]}}
也可以用正则匹配查询
例如查询名字以M开头的学生数据
results = collection.find({'name':{'$regex':'^M.*'}})
功能符号归类
符号 含义 示例 示例含义
$regex 匹配正则 {'name':{'$regex':'^M.*'}} name以M开头 $exists 属性是否存在 {'name':{'$exists':True}} name属性存在 $type 类型判断 {'name':{'$type':'int'}} age的类型为int $mod 数字模操作 {'name':{'$mod':[5,0]}} 年龄模5余0 $text 文本查询 {'$text':{'$search':'Mike'}} text类型的属性中包含Mike字符串 $where 高级条件查询 {'$where':'obj.fans_count == obj.follows_count'} 自身粉丝数等于关注数
更多 https://docs.mongodb.com/manual/reference/operator/query/
7.计数
count() 统计查询结果有多少条数据
示例: 查询所有数据条数
count = collection.find().count() print(count)
异或 加条件
8.排序
sort() 传入排序的字段 及 升降序标志
示例:
results = collection.find().sort('name',pymongo.ASCENDING)#升序 降序可以使用DESCENDING # print(results) print([result['name'] for result in results])
9.偏移
有时我们可能只想获取某几个元素 可以利用 skip() 方法偏移几个位置
示例:偏移2 忽略前两个元素,得到第三个及以后的元素
results = collection.find().sort('name',pymongo.ASCENDING).skip(2) print([result['name'] for result in results])
另外也可以使用 limit() 方法指定要取得结果个数
示例:
results = collection.find().sort('name',pymongo.ASCENDING).skip(2).limit(2) print([result['name'] for result in results])
注意:在数据库数据量非常庞大的时候 如千万、亿级别 最好不要使用大的偏移量来查询数据 很可能会导致内存溢出
此时可以使用类似如下操作来查询
from bson.objectid import ObjectId collection.find({'_id':{'$gt':ObjectId('5c126cdf11bede10f06c6184')}})
10.更新
update() 方法 指定更新的条件和更新后的数据
示例:
condition = {'name':'Mike'} student = collection.find_one(condition) sutdent['age'] = 25 result = collection.update(condition,student) print(result)#ok 执行成功 nModified 影响数据条数
另外也可以使用$set 操作符对数据进行更新
result = collection.update(condition,{'$set':student})
官方推荐 update_one he update_many
result = collection.update_one(condition,{'$set':student}) print(result.matched_count,result.modified_count)#获得匹配数据条数和影响数据条数
11.删除
remove() 指定删除条件 符合条件的所有数据都会被删除
示例:
result = collection.remove({'name':'Bod'}) print(result)
官方推荐:
delete_one() 删除一条符合条件的数据 delete_many() 删除所有符合条件的数据
示例:
result = collection.delete_one({'name':'Mike'}) print(result)#DeleteResult类型 print(result.deleted_cout)#删除条数 results = collection.delete_many({'age':20}) print(result) print(results.deleted_cout)#删除条数
12.其他操作
PyMongo 还提供了其他组合方法如:
find_one_and_delete() 查找后删除
find_one_and_replace() 查找后替换
find_one_and_update() 查找后更新 用法与上面方法基本一致
详细用法参考官方文档 此处不再赘述
http://api.mongodb.com/python/current/api/pymongo/collection.html
数据库与集合操作
http://api.mongodb.com/python/current/api/pymongo/
2.Redis 存储
简介:Redis 是一个基于内存的高效的键值型非关系型数据库,存取效率极高,而且支持多种存储数据结构,使用也非常简单。
安装 Redis 地址:https://github.com/MicrosoftArchive/redis/releases
完成后到计算机管理 -> 服务 查看 redis服务
另外也可以下载可视化管理工具 地址官网:https://redisdesktop.com/download 或者github:https://github.com/uglide/RedisDesktopManager/releases
安装redis-py
pip install redis
验证:
import redis redis.VERSION #输出版本信息 表示安装成功
如果要做数据导入导出 还需要安装RedisDump
RedisDump是一个用于redis数据导入导出的工具 是基于Ruby实现的 所以要安装redisdump 要先安装Ruby
Ruby 安装地址参考:http://www.ruby-lang.org/zh_cn/documentation/installation 根据平台不同选择不同安装方式
安装完成后 执行gem命令(类似于python中的pip) gem install redis-dump
验证:
redis-dump -V
可以调用表示安装成功
redis 和 strictredis
redis-py库提供两个类Redis 和 Strictredis 来实现Redis的命令操作
Strictredis 实现绝大部分官方命令 例如 set()对应Redis命令的set方法
Redis是Strictredis 的子类 主要功能是向后兼容旧版本库里的方法 例如 Item() 将value 和 num 参数位置互换
官方推荐 Strictredis
2.1 连接Redis
确保本地已经安装Redis 运行在6379端口 密码设置为 foobared
示例:
from redis import StrictRedis redis = StrictRedis(host='localhost',port=6379,db=0,password='foobared') redis.set('name','Bob')#设置键值对 print(redis.get('name'))#输出结果b'Bob' #也可以使用ConnectionPool from redis import StrictRedis,ConnectionPool pool = ConnectionPool(host='localhost',port='6379',db=0,password='foobared') redis = StrictRedis(connection_pool=pool) print(redis.get('name'))
同样可以获取到
另外ConnectionPool 还支持三种URL来构造 password没有可以省略
redis://[:password]@host:port/db #TCP连接 rediss://[:password]@host:port/db#TCP+SSL连接 unix://[:password]@/path/to/socket.sock?db=db#UNIX socket连接
示例:
from redis import StrictRedis,ConnectionPool url = 'redis://:foobared@localhost:6379/0' pool = ConnectionPool.from_url(url) redis = StrictRedis(connection_pool=pool) print(redis.get('name'))
2.2 键操作
键的一些判断和操作方法
方法 作用
exists(name) #判断一个键是否存在 delete(name) #删除一个键 type(name) #判断键类型 keys(pattern) #获取所有符合规则的键 randomkey() #获取随机的一个键 rename(src,dst) #重命名键 dbsize() #获取当前数据库中键的数目 expire(name,time) #设键的过期时间 单位是秒 ttl(name) #获取键的过期时间 -1为永不过期 move(name,db)#将键移动到其他数据库 flushdb() #删除当前选择数据库中所有键 flushall() #删除所有数据库中的所有键
2.3 字符串操作 最基本的键值对形式存储
set(name,value) #给数据库中键为name的string赋值为value get(name) #返回数据库中键为name的string的value getset(name,value) #返回name的string的赋值value 和 上次的value mget(keys,*args) #返回多个键对应的value keys为列表 setnx(name,value) #如果不存在这个键值对 则更新value 否则不变 setex(name,time,value) #设置对应的值为string类型的value并指定有效期 setrange(name,offset,value) #设置指定键的value值为子字符串 mset(mapping) #批量赋值 mapping为字典 msetnx(mapping) #键均不存在时才批量赋值 incr(name,amount=1) #键为name的value增值操作默认为1,键不存在则被创建并设为amount decr(name,amount=1) #键为name的value减值操作 默认为1,键不存在则创建并将value设置为-amount append(key,value) #键为name的string的值附加为value 追加内容 substr(name,start,end=-1) #返回键为name的string的子串 截取索引 getrange(key,start,end) #获取键的value值从start到end的子字符串 截取索引
2.4 列表操作 列表内的元素可以重复 而且可以从两端存储
rpush(name,*vaules) #在键为name的列表末尾添加值为value的元素,value:值可以传多个 lpush(name,*values) #在键为name的列表头添加值为value的元素,value:值可以传多个 llen(name) #返回键为name的列表的长度 lrange(name,start,end) #返回键为name的列表中start至end之间的元素 ltrim(name,start,end)# 截取键为name的列表保留索引为start到end的内容 lindex(name,index) #返回键为name的列表中index位置的元素 lset(name,index,value) #给键为name的列表中index位置的元素赋值,越界则报错 lrem(name,count,value) #删除count个键的列表中值为value的元素 lpop(name) #返回并删除键为name的列表中的首元素 rpop(name) #返回并删除键为name的列表中的尾元素 blpop(keys,timeout=0) #返回并删除名称在keys中的list中的首个元素,如果列表为空,则会一直阻塞等待 brpop(keys,timeout=0) #返回并删除名称在keys中的list中的尾元素,如果列表为空,则会一直阻塞等待 rpoplpush(src,dst) #返回并删除名称为src的列表的尾元素,并将该元素添加到名称为dst的列表头部
2.5 集合操作 集合内的元素都是不重复的
sadd(name,*values) #向键为name的集合中添加元素 srem(name,*values) #从键为name的元素中删除元素 spop(name) #随机返回并删除键为name的集合中的一个元素 smove(src,dst,value) #从src对应的集合中移除元素并将其添加到dst对应的集合中 scard(name) #返回键为name的集合的元素个数 sismember(name,value) #测试member是否是键为name的集合的元素 sinter(keys,*args) #返回所有给定键keys的集合 sinterstore(dest,keys,*args) #求交集并将交集保存到dest的集合 sunion(keys,*args) #返回所有给定键的集合的并集 sunionstore(dest,keys,*args) #求并集并将并集保存到dest的集合 sdiff(keys,*args) #返回所有给定键的集合的差集 sdiffstore(dest,keys,*args) #求差集并将差集保存到dest集合 smembers(name) #返回键为name的集合的所有元素 srandmember(name) #随机返回键为name的集合中的一个元素,但不删除元素
2.6 有序集合操作
有序集合比集合多了一个分数字段 利用它可以对集合中的数据进行排序
zadd(name,*args,**kwargs) #向键为name的zset中添加元素member,score用于排序。如果该元素存在则更新其顺序。 zrem(name,*values) #删除键为name的zset中的元素 zincrby(name,value,amount=1) #如果在键为name的zset中已经存在元素value,则将该元素的score增加amount否则向该元素集合中添加该元素,其score的值为amount zrank(name,value) #返回键为name的zset中元素的排名,按score从小到大排序 zrevrange(name,start,end,withscores=False) #返回键为name的zset(按score从大到小)中index从start到end的所有元素 zrangebyscore(name,min,max,start=None,num=None,withscores=False) #返回键为name的zset中score在给定区间的元素 zcount(name,min,max) #返回键为name的zset中score在给定区间的数量 zcard(name) #返回键为name的zset的元素个数 zremrangebyrank(name,min,max) #删除键为name的zset中排名在给定区间的元素 zremrangebyscore(name,min,max) #删除键为name的zset中score在给定区间的元素
2.7 散列操作 可以用name指定一个散列表的名称,表内存储了各个键值对
hset(name,key,value) #向键为name的散列表中添加映射 hsetnx(name,key,value) #如果映射键名不存在,则向键为name的散列表中添加映射 hget(name,key) #返回键为name的散列表中key对应的值 hmget(name,keys,*args) #返回键为name的散列表中各个键对应的值 hmset(name,mapping) #向键为name的散列表中批量添加映射 hincrby(name,key,amount=1) #将键为name的散列表中映射的值增加amount hexists(name,key) #键为name的散列表中是否存在键名为键的映射 hdel(name,*keys) #在键为name的散列表中删除键名为键的映射 hlen(name) #从键为name的散列表中获取映射个数 hkeys(name) #从键为name的散列表中获取所有映射键名 hvals(name) #从键为name的散列表中获取所有映射键值 hgetall(name) #从键为name的散列表中获取所有映射键值对
2.8 RedisDump
(若执行redis-dump报错 :Error connecting to Redis on localhost:6379 (Redis::TimeoutError))
到 'C:\Ruby25-x64\lib\ruby\gems\2.5.0\gems\redis-dump-0.4.0\lib\redis' 目录下记事本打开dump.rb
ps -o rss= -p #{Process.pid}.to_i 加注释#
执行
redis-dump -h
-u 代表redis连接字符串 -d 代表数据库代号 -a 密码 -s 代表导出之后的休眠时间 -b 将键值编码为base64(对二进制值有用) -c 代表分块大小 默认是10000 -f 代表导出时的过滤器 -O 代表禁用运行时优化 -V 显示版本 -D 表示开启调试
执行命令
redis-dump -u 127.0.0.1:6379
打印出redis库所有数据
导出为json行文件
redis-dump -u 127.0.0.1:6379 > ./redis_data.jl
也可以指定库 -d 1 空格 | -d1 表示1号数据库
-f 过滤 例如adsl开头 -f adsl:*
redis-load
查看帮助
redis-load -h
-u 代表redis连接字符串 -d 代表数据库代号 -a 密码 -s 代表导出之后的休眠时间 -b 将键值编码为base64(对二进制值有用) -n 不检测UTF-8编码 -V 显示版本 -D 表示开启调试
导入json行文件
< ./redis_data.jl redis-load -u 127.0.0.1:6379
总结 :
了解redis-py对redis数据库的一些基本操作 还演示了RedisDump对数据的导入导出操作