爬虫流程:
1. 发送请求 -- 接收响应数据 -- 解析数据 -- 存储数据
1. requests: 专门用于处理HTTP请求
-- 对应HTTP的方法: GET,POST
-- GET 参数拼在地址栏 get(url,headers,params,cookies,timeout,proxies)
-- POST 参数放在请求体中,cookie and token post(url,headers,data)
-- user-agent 两种方式都需要
2. selenium模块: 可以模拟手动操作,点击,滑动等等(动态页面)
-- 下载对应的浏览器驱动
-- webDriver.Chrome("驱动路径")
-- 请求页面 driver.get("url")
-- 解析数据: css_seletor, xpath
-- 等待元素加载: 显式等待(有条件,元素是满足某种状态,例如标签可以被点击)
隐式等待(只设置时间)
-- 动作链: 点击,拖拽等
# 如果系统不带界面,需要配置无 ui模式 , 调入options
# 关键配置项: 无UI, 取消提示,不加载图片
3. 解析数据: re, bs4, (定位元素)xpath,css_selector
-- find_all
-- 点语法 封装了find_all
-- find(name="aaa",attr={"id":"xx"},id="xxx",text="",_class="aa")(name标签名)
-- 过滤器:字符串,正则,数据,函数(自定义过滤规则),布尔
4. 定位元素
-- css_selector 类名 标签名 id 子类 后代
-- xpath: //全文范围查找 / 跟标签开始查找 //* 查所有
谓语(限制条件) /a/p[@*] 有属性 /a/p/text()查文本 /a/p[@id='a' and @class]
多路径:/a/p/ | /a/m/
轴: 以某一个标签为起始,以相对方式查找标签 子代,兄弟
MongoDB注意事项
1. 文档中的键值对是有序的
2. 文档中的值不仅可以是字符串,还可以是其他几种数据类型, 键可以使用任意UTF-8字符
3. mongoDB区分类型和大小写
4. MongoDB文档不能有重复的键
文档键的命名规范:
1. 键不能含有 (空字符)。这个字符用来表示键的结尾。
2. . 和$有特别的以,只有在特定环境下才能使用
3. 以下划线"_" 开头的键是保留的
#下载地址: https://fastdl.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-4.0.8-signed.msi
MongoDB初识
爬虫的数据存储
mysql: 关系型数据库,基于硬盘存储
当并发量太大时,mysql无法支撑,mysql 也支持分布式,但不是是强项
MongoDB: 高并发,分布式 -- 被应用于大数据领域
是一款基于c++ 编写的分布式非关系型数据库
noSQL: not only SQL
最像关系型的非关系型数据库
Redis: 数据量小的话,效率高 (轻量级) --- 数据小,并发量高
Why MongoDB?? 数据量非常大
1. 安装mongodb
2. 初识MongoDB
--MongoDB: 集合(文档,类似字典)- 键值对
-- Mysql: 表(记录) - 字段
-- 默认端口: 27017
使用MongoDB
# 进入mongodb
-- 将mongo.exe 添加到环境变量
-- mongodb
# 创建账号
-- use admin
-- mongoDB 区分大小写(创建用户): db.createUser({"user":"root","pwd":"123",roles:[{"role":"root","db":"admin"}]}) # user和role也固定, role的value有几个值可以选择: root/readwrite/read
# mongod.cfg 配置文件修改
-- # 开启验证,必须先建账户,注意缩进
-- #security:
-- authorization: enabled
# 退出,重启服务,登录
-- 登录方式一: mongo -u "用户名" -p "密码"
-- 登录方式二: mongo (进入游客模式)
use admin (切换到用户所在的数据库)
db.auth("用户名","密码") 1 成功, 0 失败
# 小结:
mongodb 中用不同的数据库来区分权限,创建管理员在admin下创建
db 是一个全局变量,表示当前的数据库
db.createUser()是调用一个内部函数用于创建用户
每个账号可以具备多个角色
# 测试
1. 进入mongo,查看数据库
show dbs
2. 登录 mongo -u "root" -p "123"
方式二: use admin
db.auth("root","123")
3. 删除账号
db.dropUser('用户名')
4. 修改密码
db.changeUserPassword(用户名,新密码)
修改配置文档mongod.cfg
首先,将bind_ip改为0.0.0.0 (其他电脑可以访问,用于远程连接,如果bind_ip 是127.0.0.1,就只能本地访问)
基本数据类型
# 概念上,MongoDB的文档和Javascript 队形相近,类似JSON,JSON(http://www.json.org)是一种简单的数据表示方式:其规范仅用一段文字就能描述清楚(其官网证明了这点),且仅包含六种数据类型。
# JSON缺点:
-- 没有日期类型
-- 只有一种数字类型,无法区分浮点数和整数
-- 无法区分32 位和64位
--无法表示其他一些通用类型,如正则表达式或函数。
# MongoDB 保留了JSON 基本键/值 对特性的基础上,添加了其他一些数据类型。
MongoDB数据类型:
#1、null:用于表示空或不存在的字段
d={'x':null}
#2、布尔型:true和false
d={'x':true,'y':false}
#3、数值
d={'x':3,'y':3.1415926}
#4、字符串
d={'x':'egon'}
#5、日期
d={'x':new Date()}
d.x.getHours()
#6、正则表达式
d={'pattern':/^egon.*?nb$/i}
正则写在//内,后面的i代表:
i 忽略大小写
m 多行匹配模式
x 忽略非转义的空白字符
s 单行匹配模式
#7、数组
d={'x':[1,'a','v']}
#8、内嵌文档
user={'name':'jerry','addr':{'country':'China','city':'YT'}}
user.addr.country
#9、对象id:是一个12字节的ID,是文档的唯一标识,不可变
d={'x':ObjectId()}
#案例:
db.tb.insert({"a":null,"b":1.1,"c":true,"d":100,"e":"aaaaaa","f":new Date(),"g":/^jerry.*nice$/i,"h":[1,2],"j":{"name":"smallJerry"}})
库的操作
# 创建数据库,有则切换,无则创建
use 数据库名
# 删除数据库
db.dropDatebase()
# 查看数据库,如果没有数据,不显示数据库
show dbs
集合的操作
集合是一个存储元素的容器,类比mysql 中的表
# 首先进入库
use mydb
# 创建集合
db.user
# 查看集合,没有数据不显示
show tables 查看所有集合
show collections 所有集合
db.user.info.address
# 删除集合
db.user.drop() 删除
db.dropUser("username") 只能登录admin 才能删除
文档操作
# 没有指定_id 则默认objectID, 不能重复,插入后不可变
# use ttt -新建库 db.user --新建表 然后给表添加数据
user1 = {
"name":"iris",
"age":13,
"hobbies":["music","read","dance"],
"addr":{
"country":"China",
"city":"biejing"
}
}
# 插入单条
db.user.insert(user0)
# 插入多条
db.user.insertMany([user1,user2,user3])
# save 覆盖或插入
db.user.save(user0) -- 无则插入,有责覆盖
db.user.find()
查询数据
=============================比较运算=============================
find 查找所有匹配的数据, findone 查第一个
# = , != , > , < , >= , <=
# $ne, $gt,$lt, $gte,$lte
# {key:value} 代表相等
db.user.find({"name":"egon"})
# 查找除 egon以外
db.user.find({"name":{"$ne":"egon"}})
# id 大于2 的
db.user.find({"_id":{"$gt":2}})
# id 小于3
db.user.find("_id":{"$lt":3})
=============================逻辑运算=============================
# not and or
# MongoDB中: 多个条件是and 关系, "$or" 的条件放在[]内,"$not"
# and
db.user.find({"_id":{"$gte":2,"$lte":4}})
# or id>=5 or name="cxx" (外层$or 套列表)
db.user.find({
"$or":[
{"_id":{"$gte":5}},
{"name":"cxx"}
]
})
# 基数,偶数
db.user.find({"_id":{"$mod":[2,1]}}) -- 基数
db.user.find("_id":{"$mod":[2,0]}) -- 偶数
# not , 取反
db.user.find("_id":{"$not":{"$mod":[2,1]}})
=============================成员运算=============================
# in , not in
# $in $nin
# age in [20,29,30]
db.user.find({"age":{"$in":[20,29,30]}})
# not in
db.user.find({"age":{"nin":[20,29,30]}})
=============================正则匹配=============================
# 语法 /正则表达式/i
db.user.find({"name":/^e.*/i})
=============================指定字段显示========================
db.user.find({"_id":3},{"_id":0,"name":1,"age":1})
0 表示不显示, 1 为显示, 默认为0
=============================查询数组============================
# 多个兴趣爱好
# 有dancing爱好的人
db.user.find({'hobbies':'dancing '})
# 有dancing and tea 爱好的人
db.user.find({
'hobbies':{
"$all":['dancing','tea']
}
})
# 第四个爱好是tea的人
db.user.find({"hobbies.3":"tea"})
# 查看所有人最后两个爱好
db.user.find({},{"hobbies":{"$slice":-2},"age":0,"_id":0...})
# 查看所有人第二个到第三个爱好
db.user.find({},{"hobbies":"slice":[1,2],"age":0....})
# db.user.find().pretty() -- 格式化查询结果
===============================其他===============================
# 排序 sort 1 代表升序 -1代表降序
db.user.find().sort("name":1)
# 分页:limit代表取多少个文档,skip 代表跳过多少个文档
db.user.find().sort({"age":1}).limit(1).skip(2)
# 获取数量
db.user.find({"age":{"$gt":30}}).count()
db.user.count({"age":{"$gt":30}})
# 匹配key 的值为null 或者没有这个key
db.t2.insert({'a':10,'b':111})
db.t2.insert({'a':20})
db.t2.insert({'b':null})
db.t2.find({"b":null}) -- 查出后两个结果
修改数据
update() 用于更新已存在的文档,语法如下:
db.collection.update(
<query>, # 相当where条件
<update>, # update的对象和一些更新的操作符(相当set后面的内容)
{
upsert:<boolean>, #默认false,不存在时不更新也不插入
multi:<boolean>, # 默认false,只更新找到的第一条记录,true为更新所有
writeConcern: <document> # 抛出异常的级别
}
)
=========================================覆盖式=========================================
#注意:除非是删除,否则_id是始终不会变的
#1、覆盖式:
db.user.update({'age':20},{"name":"Wxx","hobbies_count":3})
是用{"_id":2,"name":"Wxx","hobbies_count":3}覆盖原来的记录
#2、用一个新的文档完全替换匹配的文档,适用于大规模式迁移的情况
var obj=db.user.findOne({"_id":2})
obj.username = obj.name + 'SB'
obj.hobbies_count++
delete obj.age
# 结果: {"_id":2,"name":"Wxx","hobbies_count":4,"username":"WxxSB"}
db.user.update({"_id":2},obj)
=========================================设置:$set=========================================
# 1. update db1.user set name='egon' where id=2
db.user.update({"_id":2},{"$set":{"name":"egon"}})
# 没匹配成功就新增一条,有就修改set 指定的字段
db.user.update({"_id":2},{"$set":{"name":"iris","age":38}},{"upsert":true})
# 匹配多条 {"multi":true}
db.user.update({"_id":{"$gt":4}},{"$set":{"age":38}},{"multi":true})
# 修改嵌入文档
db.user.update({"name":"alex"},{"$set":{"addr.country":"Japan"}})
# 修改名为alex 的人的第二个爱好成pingpang
db.user.update({"name":"alex"},{"$set":{"hobbies.1":'pingpang'}})
# 删除alex 的爱好, $unset
db.user.update({"name":"alex"},{"$unset":{"hobbies":""}})
=========================================增加和减$inc=====================================
# 1. 所有人年龄增加一岁
db.user.update({},
{
"$inc":{"age":1}
},{
"multi":true
})
#2. 所有人年龄减少2岁
db.user.update({},
{
"$inc":{"age":-2}
},
{
"multi":true
})
========================================= 添加,删除数组元素===============================
# 数据添加元素: $push
# 为alex 添加爱好 read
db.user.update({"name":"alex"},{"$push":{"hobbies":"read"}})
#添加多个爱好 tea,dancing ($push, $each)
db.user.update({"name":"alex"},{"$push":{"hobbies":{"$each":['tea','dancing']}}}
# 开头或结尾删除元素: $pop
{"$pop":{"key":1}} 从数组末尾删除一个元素, -1 从头部删除
db.user.update({"name":"egon"},{"$pop":{"hobbies":1}})
#按条件删除元素: $pull 把符号条件的统统删除,$pop只能从两端删除
db.user.update({"addr.country":"China"},{"$pull":{"hobbies":"read"}},{"multi":true})
=====================================避免添加重复:$addToSet===========================
db.urls.insert({"_id":1,"urls":[]})
# 更新一条,自动去除重复
db.urls.update({"_id":1},{"addToSet":{"urls":'http://www.baidu.com'}})
db.urls.update({"_id":1},{"addToSet":{"urls":'http://www.baidu.com'}})
# 更新添加多条
db.urls.update({"_id":1},{
"addToSet":{
"urls":{
"$each":[
'http://www.baidu.com',
'http://www.xxxx.com' # 出现重复,自动去重
]
}
}
})
=====================================其他========================================
#1、了解:限制大小"$slice",只留最后n个
db.user.update({"_id":5},{
"$push":{"hobbies":{
"$each":["read",'music','dancing'],
"$slice":-2
}
}
})
#2、了解:排序The $sort element value must be either 1 or -1"
db.user.update({"_id":5},{
"$push":{"hobbies":{
"$each":["read",'music','dancing'],
"$slice":-1,
"$sort":-1
}
}
})
#注意:不能只将"$slice"或者"$sort"与"$push"配合使用,且必须使用"$eah"
删除数据
# 删除多个中的一个
db.user.deleteOne({'age':19})
# 删除满足条件的多个
db.user.deleteMany({"addr.country":"China"})
# 删全部
db.user.deleteMany({})
Mongo DB 数据库
# 用户管理 --- 登录(先创建用户在修改配置文件)
--
MongoDB 命令使用:
# use admin: 切换数据库,如果不存在会自动创建
db: 当前数据库
db.user 相当建了一个表
db.user.insert({"_id":1,"name":"egon"})
show tables -- show dbs
exit 退出
db.help 查看帮助信息
db.dropDatabase() 删除数据库
记录的操作
db.user.insertMany([
{"_id":3,"name":"octivia"},
{"_id":4,"name":"lily"}
])
db.user.find() -- 查询所有 == select * from user
db.user.find({"name":"octivia","_id":3}) -- 条件可单可多
save()
db.user.save({"_id":6,"name":"clerk"}) # 如果id存在就覆盖,id不同就新增
mongoDB的语法就是JS 语法,所以json支持的数据类型mongo都支持
字符串,整型,浮点,列表,字典,布尔(小写), new Date(), /^a.*c$/i
_id和ObjectId
"""
MongoDB中存储的文档必须有一个"_id"键。这个键的值可以是任意类型,默认是个ObjectId对象。
在一个集合里,每个文档都有唯一的“_id”,确保集合里每个文档都能被唯一标识。
不同集合"_id"的值可以重复,但同一集合内"_id"的值必须唯一
#1、ObjectId
ObjectId是"_id"的默认类型。因为设计MongoDb的初衷就是用作分布式数据库,所以能够在分片环境中生成
唯一的标识符非常重要,而常规的做法:在多个服务器上同步自动增加主键既费时又费力,这就是MongoDB采用
ObjectId的原因。
ObjectId采用12字节的存储空间,是一个由24个十六进制数字组成的字符串
0|1|2|3| 4|5|6| 7|8 9|10|11
时间戳 机器 PID 计数器
如果快速创建多个ObjectId,会发现每次只有最后几位有变化。另外,中间的几位数字也会变化(要是在创建过程中停顿几秒)。
这是ObjectId的创建方式导致的,如上图
时间戳单位为秒,与随后5个字节组合起来,提供了秒级的唯一性。这个4个字节隐藏了文档的创建时间,绝大多数驱动程序都会提供
一个方法,用于从ObjectId中获取这些信息。
因为使用的是当前时间,很多用户担心要对服务器进行时钟同步。其实没必要,因为时间戳的实际值并不重要,只要它总是不停增加就好。
接下来3个字节是所在主机的唯一标识符。通常是机器主机名的散列值。这样就可以保证不同主机生成不同的ObjectId,不产生冲突
接下来2个字节确保了在同一台机器上并发的多个进程产生的ObjectId是唯一的
前9个字节确保了同一秒钟不同机器不同进程产生的ObjectId是唯一的。最后3个字节是一个自动增加的 计数器。确保相同进程的同一秒产生的
ObjectId也是不一样的。
#2、自动生成_id
如果插入文档时没有"_id"键,系统会自帮你创建 一个。可以由MongoDb服务器来做这件事。
但通常会在客户端由驱动程序完成。这一做法非常好地体现了MongoDb的哲学:能交给客户端驱动程序来做的事情就不要交给服务器来做。
这种理念背后的原因是:即便是像MongoDB这样扩展性非常好的数据库,扩展应用层也要比扩展数据库层容易的多。将工作交给客户端做就
减轻了数据库扩展的负担。
"""
0|1|2|3| 4|5|6| 7|8 9|10|11
时间戳 机器 PID 计数器
秒级的唯一性 主机的唯一标识符 不同进程 自动增加的 计数器