爬虫(二)-笔记

反爬手段和解决思路

1 服务器反爬的原因

  • 爬虫占总PV(PV是指页面的访问次数,每打开或刷新一次页面,就算做一个pv)比例较高,这样浪费资源(尤其是三月份爬虫(每年的三月份会迎接一次爬虫高峰期,有大量的论文写作时候会选择爬取一些往网站,并进行舆情分析。))。

  • 公司可免费查询的资源被批量抓走,丧失竞争力

  • 状告爬虫成功的几率小

2 反爬虫领域常见的一些概念

因为反爬虫暂时是个较新的领域,因此有些定义要自己下:

  • 爬虫:使用任何技术手段,批量获取网站信息的一种方式。关键在于批量。

  • 反爬虫:使用任何技术手段,阻止别人批量获取自己网站信息的一种方式。关键也在于批量。

  • 误伤:在反爬虫的过程中,错误的将普通用户识别为爬虫。误伤率高的反爬虫策略,效果再好也不能用。

  • 拦截:成功地阻止爬虫访问。这里会有拦截率的概念。通常来说,拦截率越高的反爬虫策略,误伤的可能性就越高。因此需要做个权衡。

  • 资源:机器成本与人力成本的总和。

这里要切记,人力成本也是资源,而且比机器更重要。因为,根据摩尔定律,机器越来越便宜。而根据IT行业的发展趋势,程序员工资越来越贵。因此,通常服务器反爬就是让爬虫工程师加班才是王道,机器成本并不是特别值钱。

3 反爬的三个方向

  • 基于身份识别进行反爬

  • 基于爬虫行为进行反爬

  • 基于数据加密进行反爬

4 常见基于身份识别进行反爬

1 通过headers字段来反爬

headers中有很多字段,这些字段都有可能会被对方服务器拿过来进行判断是否为爬虫

1.1 通过headers中的User-Agent字段来反爬

  • 反爬原理:爬虫默认情况下没有User-Agent,而是使用模块默认设置
  • 解决方法:请求之前添加User-Agent即可;更好的方式是使用User-Agent池来解决(收集一堆User-Agent的方式,或者是随机生成User-Agent)

1.2 通过referer字段或者是其他字段来反爬

  • 反爬原理:爬虫默认情况下不会带上referer字段,服务器端通过判断请求发起的源头,以此判断请求是否合法
  • 解决方法:添加referer字段

1.3 通过cookie来反爬

  • 反爬原因:通过检查cookies来查看发起请求的用户是否具备相应权限,以此来进行反爬
  • 解决方案:进行模拟登陆,成功获取cookies之后在进行数据爬取
2 通过请求参数来反爬

请求参数的获取方法有很多,向服务器发送请求,很多时候需要携带请求参数,通常服务器端可以通过检查请求参数是否正确来判断是否为爬虫

2.1 通过从html静态文件中获取请求数据(github登录数据)

  • 反爬原因:通过增加获取请求参数的难度进行反爬
  • 解决方案:仔细分析抓包得到的每一个包,搞清楚请求之间的联系

2.2 通过发送请求获取请求数据

  • 反爬原因:通过增加获取请求参数的难度进行反爬
  • 解决方案:仔细分析抓包得到的每一个包,搞清楚请求之间的联系,搞清楚请求参数的来源

2.3 通过js生成请求参数

  • 反爬原理:js生成了请求参数
  • 解决方法:分析js,观察加密的实现过程,通过js2py获取js的执行结果,或者使用selenium来实现

2.4 通过验证码来反爬

  • 反爬原理:对方服务器通过弹出验证码强制验证用户浏览行为
  • 解决方法:打码平台或者是机器学习的方法识别验证码,其中打码平台廉价易用,更值得推荐

5 常见基于爬虫行为进行反爬

1 基于请求频率或总请求数量

爬虫的行为与普通用户有着明显的区别,爬虫的请求频率与请求次数要远高于普通用户

1.1 通过请求ip/账号单位时间内总请求数量进行反爬

  • 反爬原理:正常浏览器请求网站,速度不会太快,同一个ip/账号大量请求了对方服务器,有更大的可能性会被识别为爬虫
  • 解决方法:对应的通过购买高质量的ip的方式能够解决问题/购买个多账号(代理)

1.2 通过同一ip/账号请求之间的间隔进行反爬

  • 反爬原理:正常人操作浏览器浏览网站,请求之间的时间间隔是随机的,而爬虫前后两个请求之间时间间隔通常比较固定同时时间间隔较短,因此可以用来做反爬
  • 解决方法:请求之间进行随机等待,模拟真实用户操作,在添加时间间隔后,为了能够高速获取数据,尽量使用代理池,如果是账号,则将账号请求之间设置随机休眠

1.3 通过对请求ip/账号每天请求次数设置阈值进行反爬

  • 反爬原理:正常的浏览行为,其一天的请求次数是有限的,通常超过某一个值,服务器就会拒绝响应
  • 解决方法:对应的通过购买高质量的ip的方法/多账号,同时设置请求间随机休眠
2 根据爬取行为进行反爬,通常在爬取步骤上做分析

2.1 通过js实现跳转来反爬

  • 反爬原理:js实现页面跳转,无法在源码中获取下一页url
  • 解决方法: 多次抓包获取条状url,分析规律

2.2 通过蜜罐(陷阱)获取爬虫ip(或者代理ip),进行反爬

  • 反爬原理:在爬虫获取链接进行请求的过程中,爬虫会根据正则,xpath,css等方式进行后续链接的提取,此时服务器端可以设置一个陷阱url,会被提取规则获取,但是正常用户无法获取,这样就能有效的区分爬虫和正常用户
  • 解决方法: 完成爬虫的编写之后,使用代理批量爬取测试/仔细分析响应内容结构,找出页面中存在的陷阱

2.3 通过假数据反爬

  • 反爬原理:向返回的响应中添加假数据污染数据库,通常假数据不会被正常用户看到
  • 解决方法: 长期运行,核对数据库中数据同实际页面中数据对应情况,如果存在问题/仔细分析响应内容

2.4 阻塞任务队列

  • 反爬原理:通过生成大量垃圾url,从而阻塞任务队列,降低爬虫的实际工作效率
  • 解决方法: 观察运行过程中请求响应状态/仔细分析源码获取垃圾url生成规则,对URL进行过滤

2.5 阻塞网络IO

  • 反爬原理:发送请求获取响应的过程实际上就是下载的过程,在任务队列中混入一个大文件的url,当爬虫在进行该请求时将会占用网络io,如果是有多线程则会占用线程
  • 解决方法: 观察爬虫运行状态/多线程对请求线程计时/发送请求钱

2.6 运维平台综合审计

  • 反爬原理:通过运维平台进行综合管理,通常采用复合型反爬虫策略,多种手段同时使用
  • 解决方法: 仔细观察分析,长期运行测试目标网站,检查数据采集速度,多方面处理

6 常见基于数据加密进行反爬

1 对响应中含有的数据进行特殊化处理

通常的特殊化处理主要指的就是css数据偏移/自定义字体/数据加密/数据图片/特殊编码格式等

1.1 通过自定义字体来反爬
下图来自猫眼电影电脑版

在这里插入图片描述

  • 反爬思路: 使用自有字体文件
  • 解决思路:切换到手机版/解析字体文件进行翻译

1.2 通过css来反爬
下图来自猫眼去哪儿电脑版

在这里插入图片描述

  • 反爬思路:源码数据不为真正数据,需要通过css位移才能产生真正数据
  • 解决思路:计算css的偏移

1.3 通过js动态生成数据进行反爬

  • 反爬原理:通过js动态生成
  • 解决思路:解析关键js,获得数据生成流程,模拟生成数据

1.4 通过数据图片化反爬

  • 58同城短租](https://baise.58.com/duanzu/38018718834984x.shtml)
  • 解决思路:通过使用图片解析引擎从图片中解析数据

1.5 通过编码格式进行反爬

  • 反爬原理: 不适用默认编码格式,在获取响应之后通常爬虫使用utf-8格式进行解码,此时解码结果将会是乱码或者报错
  • 解决思路:根据源码进行多格式解码,或者真正的解码格式

验证码处理

1.图片验证码

1.1 什么是图片验证码

  • 验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序。

1.2 验证码的作用

  • 防止恶意破解密码、刷票、论坛灌水、刷页。有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登录尝试,实际上使用验证码是现在很多网站通行的方式(比如招商银行的网上个人银行,百度社区),我们利用比较简易的方式实现了这个功能。虽然登录麻烦一点,但是对网友的密码安全来说这个功能还是很有必要,也很重要。

1.3 图片验证码在爬虫中的使用场景

  • 注册
  • 登录
  • 频繁发送请求时,服务器弹出验证码进行验证

1.4 图片验证码的处理方案

  • 手动输入(input)
    这种方法仅限于登录一次就可持续使用的情况
  • 图像识别引擎解析
    使用光学识别引擎处理图片中的数据,目前常用于图片数据提取,较少用于验证码处理
  • 打码平台
    爬虫常用的验证码解决方案

2.图片识别引擎

OCR(Optical Character Recognition)是指使用扫描仪或数码相机对文本资料进行扫描成图像文件,然后对图像文件进行分析处理,自动识别获取文字信息及版面信息的软件。

2.1 什么是tesseract
  • Tesseract,一款由HP实验室开发由Google维护的开源OCR引擎,特点是开源,免费,支持多语言,多平台。
  • 项目地址:https://github.com/tesseract-ocr/tesseract
2.2 图片识别引擎环境的安装

1 引擎的安装

  • mac环境下直接执行命令
brew install --with-training-tools tesseract
sudo apt-get install tesseract-ocr

2 Python库的安装

# PIL用于打开图片文件
pip/pip3 install pillow

# pytesseract模块用于从图片中解析数据
pip/pip3 install pytesseract
2.3 图片识别引擎的使用
  • 通过pytesseract模块的 image_to_string 方法就能将打开的图片文件中的数据提取成字符串数据,具体方法如下
from PIL import Image
import pytesseract

# 打开一个图片文件
im = Image.open(r'./test.png')

result = pytesseract.image_to_string(im)

print(result)

python -使用pytesseract识别验证码中遇到tesseract is not installed or it’s not in your path解决方案
在这里插入图片描述
在这里插入图片描述

2.4 图片识别引擎的使用扩展
    微软Azure 图像识别:https://azure.microsoft.com/zh-cn/services/cognitive-services/computer-vision/
    有道智云文字识别:http://aidemo.youdao.com/ocrdemo
    阿里云图文识别:https://www.aliyun.com/product/cdi/
    腾讯OCR文字识别:https://cloud.tencent.com/product/ocr

3 打码平台

1.为什么需要了解打码平台的使用

现在很多网站都会使用验证码来进行反爬,所以为了能够更好的获取数据,需要了解如何使用打码平台爬虫中的验证码

2 常见的打码平台
  1. 云打码:http://sb.daredian.cn/

    能够解决通用的验证码识别

  2. 极验验证码智能识别辅助:https://www.geetest.com/

    能够解决复杂验证码的识别

3 云打码的使用

下面以云打码为例,了解打码平台如何使用
云打码开发文档

import base64
import json
import requests
# 一、图片文字类型(默认 3 数英混合):
# 1 : 纯数字
# 1001:纯数字2
# 2 : 纯英文
# 1002:纯英文2
# 3 : 数英混合
# 1003:数英混合2
#  4 : 闪动GIF
# 7 : 无感学习(独家)
# 11 : 计算题
# 1005:  快速计算题
# 16 : 汉字
# 32 : 通用文字识别(证件、单据)
# 66:  问答题
# 49 :recaptcha图片识别 参考 https://shimo.im/docs/RPGcTpxdVgkkdQdY
# 二、图片旋转角度类型:
# 29 :  旋转类型
#
# 三、图片坐标点选类型:
# 19 :  1个坐标
# 20 :  3个坐标
# 21 :  3 ~ 5个坐标
# 22 :  5 ~ 8个坐标
# 27 :  1 ~ 4个坐标
# 48 : 轨迹类型
#
# 四、缺口识别
# 18:缺口识别
# 五、拼图识别
# 53:拼图识别
def base64_api(uname, pwd, img, typeid):
    with open(img, 'rb') as f:
        base64_data = base64.b64encode(f.read())
        b64 = base64_data.decode()
    data = {"username": uname, "password": pwd, "typeid": typeid, "image": b64}
    result = json.loads(requests.post("http://api.ttshitu.com/predict", json=data).text)
    if result['success']:
        return result["data"]["result"]
    else:
        return result["message"]
    return ""


if __name__ == "__main__":
    img_path = "C:/Users/Administrator/Desktop/file.jpg"
    result = base64_api(uname='你的账号', pwd='你的密码', img=img_path, typeid=3)
    print(result)
3.1 云打码官方接口

下面代码是云打码平台提供,做了个简单修改,实现了两个方法:

  1. indetify:传入图片的响应二进制数即可
  2. indetify_by_filepath:传入图片的路径即可识别

其中需要自己配置的地方是:

username = 'whoarewe' # 用户名

password = '***' # 密码

appid = 4283 # appid

appkey = '02074c64f0d0bb9efb2df455537b01c3' # appkey

codetype = 1004 # 验证码类型

云打码官方提供的api如下:

#yundama.py
import requests
import json
import time

class YDMHttp:
    apiurl = 'http://api.yundama.com/api.php'
    username = ''
    password = ''
    appid = ''
    appkey = ''

    def __init__(self, username, password, appid, appkey):
        self.username = username
        self.password = password
        self.appid = str(appid)
        self.appkey = appkey

    def request(self, fields, files=[]):
        response = self.post_url(self.apiurl, fields, files)
        response = json.loads(response)
        return response

    def balance(self):
        data = {'method': 'balance', 'username': self.username, 'password': self.password, 'appid': self.appid,
                'appkey': self.appkey}
        response = self.request(data)
        if (response):
            if (response['ret'] and response['ret'] < 0):
                return response['ret']
            else:
                return response['balance']
        else:
            return -9001

    def login(self):
        data = {'method': 'login', 'username': self.username, 'password': self.password, 'appid': self.appid,
                'appkey': self.appkey}
        response = self.request(data)
        if (response):
            if (response['ret'] and response['ret'] < 0):
                return response['ret']
            else:
                return response['uid']
        else:
            return -9001

    def upload(self, filename, codetype, timeout):
        data = {'method': 'upload', 'username': self.username, 'password': self.password, 'appid': self.appid,
                'appkey': self.appkey, 'codetype': str(codetype), 'timeout': str(timeout)}
        file = {'file': filename}
        response = self.request(data, file)
        if (response):
            if (response['ret'] and response['ret'] < 0):
                return response['ret']
            else:
                return response['cid']
        else:
            return -9001

    def result(self, cid):
        data = {'method': 'result', 'username': self.username, 'password': self.password, 'appid': self.appid,
                'appkey': self.appkey, 'cid': str(cid)}
        response = self.request(data)
        return response and response['text'] or ''

    def decode(self, filename, codetype, timeout):
        cid = self.upload(filename, codetype, timeout)
        if (cid > 0):
            for i in range(0, timeout):
                result = self.result(cid)
                if (result != ''):
                    return cid, result
                else:
                    time.sleep(1)
            return -3003, ''
        else:
            return cid, ''

    def post_url(self, url, fields, files=[]):
        # for key in files:
        #     files[key] = open(files[key], 'rb');
        res = requests.post(url, files=files, data=fields)
        return res.text 
        
username = 'whoarewe' # 用户名

password = '***' # 密码

appid = 4283 # appid

appkey = '02074c64f0d0bb9efb2df455537b01c3' # appkey

filename = 'getimage.jpg' # 文件位置

codetype = 1004 # 验证码类型

# 超时
timeout = 60

def indetify(response_content):
    if (username == 'username'):
        print('请设置好相关参数再测试')
    else:
        # 初始化
        yundama = YDMHttp(username, password, appid, appkey)

        # 登陆云打码
        uid = yundama.login();
        print('uid: %s' % uid)

        # 查询余额
        balance = yundama.balance();
        print('balance: %s' % balance)

        # 开始识别,图片路径,验证码类型ID,超时时间(秒),识别结果
        cid, result = yundama.decode(response_content, codetype, timeout)
        print('cid: %s, result: %s' % (cid, result))
        return result

def indetify_by_filepath(file_path):
    if (username == 'username'):
        print('请设置好相关参数再测试')
    else:
        # 初始化
        yundama = YDMHttp(username, password, appid, appkey)

        # 登陆云打码
        uid = yundama.login();
        print('uid: %s' % uid)

        # 查询余额
        balance = yundama.balance();
        print('balance: %s' % balance)

        # 开始识别,图片路径,验证码类型ID,超时时间(秒),识别结果
        cid, result = yundama.decode(file_path, codetype, timeout)
        print('cid: %s, result: %s' % (cid, result))
        return result

if __name__ == '__main__':
    pass

4 常见的验证码的种类

4.1 url地址不变,验证码不变

这是验证码里面非常简单的一种类型,对应的只需要获取验证码的地址,然后请求,通过打码平台识别即可

4.2 url地址不变,验证码变化

这种验证码的类型是更加常见的一种类型,对于这种验证码,大家需要思考:

在登录的过程中,假设我输入的验证码是对的,对方服务器是如何判断当前我输入的验证码是显示在我屏幕上的验证码,而不是其他的验证码呢?

在获取网页的时候,请求验证码,以及提交验证码的时候,对方服务器肯定通过了某种手段验证我之前获取的验证码和最后提交的验证码是同一个验证码,那这个手段是什么手段呢?

很明显,就是通过cookie来实现的,所以对应的,在请求页面,请求验证码,提交验证码的到时候需要保证cookie的一致性,对此可以使用requests.session来解决

chrome浏览器使用方法介绍

1 新建隐身窗口

浏览器中直接打开网站,会自动带上之前网站时保存的cookie,但是在爬虫中首次获取页面是没有携带cookie的,这种情况如何解决呢?

使用隐身窗口,首次打开网站,不会带上cookie,能够观察页面的获取情况,包括对方服务器如何设置cookie在本地

在这里插入图片描述

2 chrome中network的更多功能

在这里插入图片描述

2.1 Perserve log

默认情况下,页面发生跳转之后,之前的请求url地址等信息都会消失,勾选perserve log后之前的请求都会被保留
在这里插入图片描述

2.2 filter过滤

在url地址很多的时候,可以在filter中输入部分url地址,对所有的url地址起到一定的过滤效果,具体位置在上面第二幅图中的2的位置

2.3 观察特定种类的请求

在上面第二幅图中的3的位置,有很多选项,默认是选择的all,即会观察到所有种类的请求

很多时候处于自己的目的可以选择all右边的其他选项(多选用ctrl),比如常见的选项:

  • XHR:大部分情况表示ajax请求
  • JS:js请求
  • CSS:css请求

但是很多时候我们并不能保证我们需要的请求是什么类型,特别是我们不清楚一个请求是否为ajax请求的时候,直接选择all,从前往后观察即可,其中js,css,图片等不去观察即可

不要被浏览器中的一堆请求吓到了,这些请求中除了js,css,图片的请求外,其他的请求并没有多少个

3 寻找登录接口

http://www.renren.com

3.1 寻找action对的url地址

在这里插入图片描述

可以发现,这个地址就是在登录的form表单中action对应的url地址,进行表单提交的地址,对应的,提交的数据,仅仅需要:用户名的input标签中,name的值作为键,用户名作为值,密码的input标签中,name的值作为键,密码作为值即可

思考:

如果action对应的没有url地址的时候可以怎么做?

3.2 通过抓包寻找登录的url地址

在这里插入图片描述

通过抓包可以发现,在这个url地址和请求体中均有参数,比如uniqueTimestamprkey以及加密之后的password

这个时候我们可以观察手机版的登录接口,是否也是一样的

在这里插入图片描述

可以发现在手机版中,依然有参数,但是参数的个数少一些,这个时候,我们可以使用手机版作为参考,下一节来学习如何分析js

JS的解析

1 确定js的位置

对于前面人人网的案例,我们知道了url地址中有部分参数,但是参数是如何生成的呢?

毫无疑问,参数肯定是js生成的,那么如何获取这些参数的规律呢?通过下面的学习来了解

1.1 观察按钮的绑定js事件

在这里插入图片描述

通过点击按钮,然后点击Event Listener,部分网站可以找到绑定的事件,对应的,只需要点击即可跳转到js的位置

1.2 通过search all file 来搜索

部分网站的按钮可能并没有绑定js事件监听,那么这个时候可以通过搜索请求中的关键字来找到js的位置,比如livecell

在这里插入图片描述

点击美化输出选项

在这里插入图片描述

可以继续在其中搜索关键字

在这里插入图片描述

1.3 通过initiator定位到js文件

在这里插入图片描述
在这里插入图片描述

2 观察js的执行过程

找到js的位置之后,我们可以来通过观察js的位置,找到js具体在如何执行,后续我们可以通过python程序来模拟js的执行,或者是使用类似js2py直接把js代码转化为python程序去执行

观察js的执行过程最简单的方式是添加断点

在这里插入图片描述

添加断点的方式:在左边行号点击即可添加,对应的右边BreakPoints中会出现现有的所有断点

添加断点之后继续点击登录,每次程序在断点位置都会停止,通过如果该行有变量产生,都会把变量的结果展示在Scoope中

在上图的右上角有1,2,3三个功能,分别表示:
- 1:继续执行到下一个断点
- 2:进入调用的函数中
- 3:从调用的函数中跳出来

3 js2py的使用

在知道了js如何生成我们想要的数据之后,那么接下来我们就需要使用程序获取js执行之后的结果了

3.1 js2py的介绍

js2py是一个js的翻译工具,也是一个通过纯python实现的js的解释器,github上源码与示例
js2py的使用

3.2 js的执行思路

js的执行方式大致分为两种:

  1. 在了解了js内容和执行顺序之后,通过python来完成js的执行过程,得到结果
  2. 在了解了js内容和执行顺序之后,使用类似js2py的模块来执js代码,得到结果

但是在使用python程序实现js的执行时候,需要观察的js的每一个步骤,非常麻烦,所以更多的时候我们会选择使用类似js2py的模块去执行js,接下来我们来使用js2py实现人人网登录参数的获取

3.3 具体的实现

定位进行登录js代码

formSubmit: function() {
        var e, t = {};
        $(".login").addEventListener("click", function() {
            t.phoneNum = $(".phonenum").value,
            t.password = $(".password").value,
            e = loginValidate(t),
            t.c1 = c1 || 0,
            e.flag ? ajaxFunc("get", "http://activity.renren.com/livecell/rKey", "", function(e) {
                var n = JSON.parse(e).data;
                if (0 == n.code) {
                    t.password = t.password.split("").reverse().join(""),
                    setMaxDigits(130);
                    var o = new RSAKeyPair(n.e,"",n.n)
                      , r = encryptedString(o, t.password);
                    t.password = r,
                    t.rKey = n.rkey
                } else
                    toast("公钥获取失败"),
                    t.rKey = "";
                ajaxFunc("post", "http://activity.renren.com/livecell/ajax/clog", t, function(e) {
                    var e = JSON.parse(e).logInfo;
                    0 == e.code ? location.href = localStorage.getItem("url") || "" : toast(e.msg || "登录出错")
                })
            }) : toast(e.msg)
        })
    }
从代码中我们知道:
  1. 我们要登录需要对密码进行加密和获取rkey字段的值
  2. rkey字段的值我们直接发送请求rkey请求就可以获得
  3. 密码是先反转然后使用RSA进行加密, js代码很复杂, 我们希望能通过在python中执行js来实现
实现思路:
  1. 使用session发送rKey获取登录需要信息

    • url: http://activity.renren.com/livecell/rKey
    • 方法: get
  2. 根据获取信息对密码进行加密
    2.1 准备用户名和密码

    2.2 使用js2py生成js的执行环境:context

    2.3 拷贝使用到js文件的内容到本项目中

    2.4 读取js文件的内容,使用context来执行它们

    2.5 向context环境中添加需要数据

    2.6 使用context执行加密密码的js字符串

    2.7 通过context获取加密后密码信息

  3. 使用session发送登录请求

    • URL: http://activity.renren.com/livecell/ajax/clog

    • 请求方法: POST

    • 数据:

      phoneNum: xxxxxxx
      password: (加密后生产的)
      c1: 0
      rKey: rkey请求获取的
      
具体代码

需要提前下载几个js文件到本地:

BigInt.js

RSA.js

Barrett.js

若不下载到本地可参考如下方式:

import js2py
import requests

# 创建js执行环境
context = js2py.EvalJs()

headers = {
    "User-Agent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Mobile Safari/537.36"
}
# 加载js文件
big_js = requests.get('js_path', headers = headers).content.decode()
context.execute(big_js)
# 执行js中函数
# context.execute('setMaxDigits(130);')

# 添加变量
# context.n = {'class':'python'}
# print(type(context.n))  # dict->js object

通过js逆向实现人人网登陆

import requests
import json
import js2py

# - 实现思路:
#   - 使用session发送rKey获取登录需要信息
#     - url: http://activity.renren.com/livecell/rKey
#     - 方法: get
#  获取session对象
session = requests.session()
headers = {
    "User-Agent": "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Mobile Safari/537.36",
    "X-Requested-With": "XMLHttpRequest",
    "Content-Type":"application/x-www-form-urlencoded"
}
# 设置session的请求头信息
session.headers = headers
# 获取公钥
response = session.get("http://activity.renren.com/livecell/rKey")
# print(response.content.decode())
n = json.loads(response.content)['data']

#   - 根据获取信息对密码进行加密
#     - 准备用户名和密码
phoneNum = "131..."
password = "****"
#     - 使用js2py生成js的执行环境:context
context = js2py.EvalJs()
#     - 拷贝使用到js文件的内容到本项目中
#     - 读取js文件的内容,使用context来执行它们
with open("BigInt.js", 'r', encoding='utf8') as f:
    context.execute(f.read())

with open("RSA.js", 'r', encoding='utf8') as f:
    context.execute(f.read())
with open("Barrett.js", 'r', encoding='utf8') as f:
    context.execute(f.read())

# - 向context环境中添加需要数据
context.t = {'password': password}
context.n = n
#     - 执行加密密码的js字符
js = '''
       t.password = t.password.split("").reverse().join(""),
       setMaxDigits(130);
       var o = new RSAKeyPair(n.e,"",n.n)
        , r = encryptedString(o, t.password);
      '''
context.execute(js)
# - 通过context获取加密后密码信息
# print(context.r)
password = context.r
# 构建form_data 发送post请求模拟登录
#   - 使用session发送登录请求
#     - URL: http://activity.renren.com/livecell/ajax/clog
#     - 请求方法: POST
#     - 数据:
#       - phoneNum: 15565280933
#       - password: (加密后生产的)
#       - c1: 0
#       - rKey: rkey请求获取的
data = {
    'phoneNum': '131....',
    'password': password,
    'c1':'-100',
    'rKey':n['rkey']
}

# 发送post 请求模拟登陆
response = session.post("http://activity.renren.com/livecell/ajax/clog", data=data)
# 验证
print(response.content.decode())

模拟人人网登陆(目前网页无法访问,但是运行还是有结果)

3.4 有道翻译

参考地址
抓包分析
在这里插入图片描述

根据initiator定位js
在这里插入图片描述
在这里插入图片描述
找到generateSaltSign的声明
在这里插入图片描述
在这里插入图片描述
md5()为hash

import hashlib


# hash 计算字符串的特征值,相同字符串,特征值相同,类似于指纹
# 作用:地址去重(url,url-hash,布隆过滤器) 文本内容去重(编辑距离、simhash)
data = 'python37'
# 创建hash对象
md5 = hashlib.md5()
# print(md5) # <md5 HASH object @ 0x00000260329033C8>
# 向hash对象中添加需要做hash运算的字符串
# updata 中接收bytes,因此encode()
md5.update(data.encode())
# 获取字符串的hash值
res = md5.hexdigest()
print(res)  # 21793ecc33ebcdc2141151352aa4c02e

在这里插入图片描述
python 中

time.time() # 1623159874.3041332

为满足(new Date).getTime()

str(int(time.time() * 1000)) # 1623160110238

完整代码

import requests
import hashlib
import time
import random
import json


class Youdao(object):
    def __init__(self, word):
        self.url = 'https://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule'
        self.headers = {
            'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36',
            'Cookie': 'OUTFOX_SEARCH_USER_ID=-56556802@117.136.0.255; OUTFOX_SEARCH_USER_ID_NCOO=2031042927.8885891; _ga=GA1.2.1642276281.1603282499; _ntes_nnid=00023bed97821e27e55753bf784b22bc,1606659167990; DICT_UGC=be3af0da19b5c5e6aa4e17bd8d90b28a|; JSESSIONID=abcJ1UUXVzQMp5QUrTRNx; ___rl__test__cookies=1623157313766',
            'Referer':'https://fanyi.youdao.com/'
        }
        self.form_data = None
        self.word = word


    def generate_formdata(self):
        """
        var r = function(e) {
        var t = n.md5(navigator.appVersion)
          , r = "" + (new Date).getTime()
          , i = r + parseInt(10 * Math.random(), 10);
        return {
            ts: r,
            bv: t,
            salt: i,
            sign: n.md5("fanyideskweb" + e + i + "Tbh5E8=q6U3EXe+&L[4c@")
        }

        核心为:
        ts: "" + (new Date).getTime(),
        salt: ts + parseInt(10 * Math.random(), 10),
        sign: n.md5("fanyideskweb" + e + i + "Tbh5E8=q6U3EXe+&L[4c@")
        """

        # 对目标js进行翻译
        # ts = time.time()
        # print(ts) # 1623159874.3041332
        ts = str(int(time.time() * 1000))
        # print(ts) # 1623160110238
        salt = ts + str(random.randint(0,9))
        tempstr = "fanyideskweb" + self.word + salt + "Tbh5E8=q6U3EXe+&L[4c@"
        # 创建hash对象
        md5 = hashlib.md5()
        # hash对象中添加需要计算的字符串
        md5.update(tempstr.encode())
        # 获取字符串hash值
        sign = md5.hexdigest()
        self.form_data = {
            'i': self.word, # 要被翻译的数据
            'from': 'AUTO',
            'to': 'AUTO',
            'smartresult': 'dict',
            'client': 'fanyideskweb',
            'salt': salt, # 以毫秒为单位的时间戳 + 随机数字
            'sign': sign, # 未知的js加密后的数据
            'lts': ts, # 以毫秒为单位的时间戳
            'bv': '24ecb70ba6203e4453baed50aa26b78e',
            'doctype': 'json',
            'version': '2.1',
            'keyfrom': 'fanyi.web',
            'action': 'FY_BY_REALTlME',
        }


    def get_data(self):
        response = requests.post(self.url, data = self.form_data, headers = self.headers)
        return response.content


    def parse_data(self, data):
        json_data = json.loads(data)
        res = json_data['translateResult'][0][0]['tgt']
        return res

    def run(self):
        # url
        # headers
        # form_data
        self.generate_formdata()
        # print(self.form_data)
        # 发送请求获取响应
        data = self.get_data()
        # print(data)
        # 解析数据
        res = self.parse_data(data)
        print('翻译结果为>>:', res)


if __name__ == '__main__':
    while True:
        try:
            input_data = input('输入要翻译的词>>:')
            yd = Youdao(input_data)
            yd.run()
            time.sleep(1)
        except:
            print('Error')
            break

在这里插入图片描述

Mongodb的介绍和安装

1. mongodb的介绍

mongodb 是一种非关系型数据库,主要用于海量存储,主要用于数据采集项目中

1.1 什么是mongodb
  • mongodb 是一个功能最丰富的NoSQL非关系数据库。由 C++ 语言编写。
  • mongodb 本身提供S端存储数据,即server;也提供C端操作处理(如查询等)数据,即client。
1.2 SQL和NoSQL的主要区别
  • 在SQL中层级关系: 数据库>表>数据
  • 而在NoSQL中则是: 数据库>集合>文档
1.2.1 数据之间无关联性
  • SQL中如何需要增加外部关联数据的话,规范化做法是在原表中增加一个外键,关联外部数据表。
  • NoSQL则可以把外部数据直接放到原数据集中,以提高查询效率。缺点也比较明显,对关联数据做更新时会比较麻烦。
  • SQL中在一个表中的每条数据的字段是固定的。而NoSQL中的一个集合(表)中的每条文档(数据)的key(字段)可以是互不相同的。
  • 通常,如果有复杂业务要求使用SQL,单纯做海量存储则NoSQL;一般web项目采用mysql、redis,数据采集项目采用mongodb,redis
1.2.2 拓展阅读

SQL 和 NoSQL 的区别

1.3 mongodb作为非关系型数据库相较于关系型数据库的优势

易扩展: NoSQL数据库种类繁多, 但是一个共同的特点都是去掉关系数据库的关系型特性。 数据之间无关系, 这样就非常容易扩展

大数据量,高性能: NoSQL数据库都具有非常高的读写性能, 尤其在大数据量下表现优秀。 这得益于它的非关系性,数据库的结构简单

灵活的数据模型: NoSQL无需事先为要存储的数据建立字段, 随时可以存储自定义的数据格式。 而在关系数据库中, 增删字段是一件非常麻烦的事情。 如果是非常大数据量的表, 增加字段简直就是一个噩梦

2. mongodb的安装

以win10为例

下载地址
在这里插入图片描述
除了以下几步,全部next
自定义安装
在这里插入图片描述
下图一定要取消安装,(compass客户端可视化界面。要是选中了,那就是安装了一天也完成不了)
在这里插入图片描述

配置环境变量
我的电脑-属性-高级系统设置-环境变量
在这里插入图片描述
启动
在这里插入图片描述

以ubuntu18.04为例

mongodb具有两种安装方式:命令安装 或 源码安装

2.1 命令安装

在ubuntu中使用apt-get工具安装

sudo apt-get install -y mongodb-org  # -y 对提示进行默认操作(可选)

或参考官方文档 https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/

2.2 源码安装
2.2.1 选择相应版本和操作系统并下载

https://www.mongodb.com/download-center/community?jmp=docs

2.2.2 解压

tar -zxvf mongodb-linux-x86_64-ubuntu1804-4.0.3.tgz

2.2.3 移动到/usr/local/目录下

sudo mv -r mongodb-linux-x86_64-ubuntu1804-4.0.3/ /usr/local/mongodb

2.2.4 在shell的初始化脚本.bashrc中添加mongodb可执行文件到环境变量PATH中

a. 进入.bashrc文件中

cd ~
sudo vi .bashrc

b. 在.bashrc文件的最后添加:

export PATH=/usr/local/mongodb/bin:$PATH

3. mongodb的官方文档

https://docs.mongodb.com/manual/introduction/

mongodb的简单使用

1. mongodb服务端的启动

  • 默认端口:27017
  • 默认配置文件的位置:/etc/mongod.conf
  • 默认日志的位置:/var/log/mongodb/mongod.log

mongodb服务端启动分别两种方式:

  • 本地测试方式的启动(只具有本地数据增删改查的功能)-验证数据库是否正常运行
  • 生产环境启动(具有完整的全部功能)-部署启动
1.1 测试方式启动
  • 启动: sudo service mongod start (sudo service mongod start)
  • 停止: sudo service mongod stop
  • 重启: sudo service mongod restart
1.2 生产环境正式的启动方式

启动: sudo mongod [–auth --dbpath=dbpath --logpath=logpath --append --fork] [-–f logfile ]

  • 只以 sudo mongod 命令启动时,默认将数据存放在了 /data/db 目录下,需要手动创建
    在这里插入图片描述

  • –dbpath: 指定数据库的存放路径
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • –logpath: 指定日志的存放路径

  • –append: 或–logappend 设置日志的写入形式为追加模式

  • –fork: 或-fork 开启新的进程运行mongodb服务即新开一个子进程而非占用当前进程
    在这里插入图片描述

  • –f: 或-f 配置文件路径(可以将上述配置信息写入文件然后通过该文件中的参数进行加载启动)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • –auth: 以权限认证的方式启动

1.3 查看是否启动成功

ps aux | grep mongod

在这里插入图片描述

grep 查找命令的使用

在这里插入图片描述

-v 进行取反

在这里插入图片描述

2. 启动mongodb的客户端:进入mongo shell

  • 启动本地客户端: mongo
  • 查看帮助:mongo –help
  • 退出:exit或者ctrl+c

3. mongodb的简单使用

开启mongodb server的情况下,在进入mongo shell后,就可以做简单的使用了

3.1 mongodb数据库的命令
  • 查看当前的数据库:db(没有切换数据库的情况下默认使用test数据库)
  • 查看所有的数据库:show dbs /show databases(查看磁盘中的db,而非内存中(通过命令创建的db))
  • 切换数据库:use db_name
    • db_name为show dbs后返回的数据库名
  • 删除当前的数据库:db.dropDatabase()
3.2 mongodb集合的命令
  • 无需手动创建集合:
    向不存在的集合中第一次添加数据时,集合会自动被创建出来
  • 手动创建集合:
    • db.createCollection(name,options)
    • db.createCollection(“stu”)
    • db.createCollection(“sub”, { capped : true, size : 10 } )
    • 参数capped:默认值为false表示不设置上限,值为true表示设置上限
    • 参数size:集合所占用的字节数。 当capped值为true时,需要指定此参数,表示上限大小,当文档达到上限时, 会将之前的数据覆盖,单位为字节(<256,默认为256;>256,为设置值;且若是固定大小集合,若固定容量满了,插入会提示成功,但查询时,最早插入一条数据不会被查询到,被自动删除;固定容量集合一般用于存储日志信息;固定容量集合数据插入后不允许被修改)
  • 查看集合:show collections
  • 删除集合:db.集合名称.drop()
  • 检查集合是否设定上限: db.集合名.isCapped()
3.3 简单练习

在mongo shell中输入下列命令,查看结果

show dbs
use test
show collections
db
db.stu.insert({'name':'郭靖', 'age':22})
show dbs
show collections
db.stu.find()
db.stu.drop()
show collections
db.dropDatabase()
show dbs
exit
3.3 mongodb中常见的数据类型
3.3.1 常见类型
  • Object ID: 文档ID/数据的ID,数据的主键
  • String: 字符串,最常用,必须是有效的UTF-8
  • Boolean: 存储一个布尔值,true或false
  • Integer: 整数可以是32位或64位,这取决于服务器
  • Double: 浮点数
  • Arrays: 数组/列表
  • Object: mongodb中的一条数据/文档,即文档嵌套文档
  • Null: 存储null值
  • Timestamp: 时间戳,表示从1970-1-1到现在的总秒数
  • Date: 存储当前日期或时间的UNIX时间格式
3.3.2 注意点
  • 每个文档都有一个属性,为_id,保证每个文档的唯一性,mongodb默认使用_id作为主键

    • 可以手动设置_id的值,如果没有提供,那么MongoDB为每个文档提供了一个独特的_id, 类型为objectID
  • objectID是一个12字节的十六进制数,每个字节两位,一共是24位的字符串:

    • 前4个字节(每个字节为2个字符)为当前时间戳
    • 接下来3个字节的机器ID
    • 接下来的2个字节中MongoDB的服务进程id
    • 最后3个字节是简单的增量值

Mongodb的的增删改查

1. mongodb插入数据

命令:db.集合名称.insert(document)

db.stu.insert({name:'gj', gender:1})
db.stu.insert({_id:"20170101", name:'gj', gender:1})

插文档时,如果不指定_id参数,MongoDB会为文档自动分配一个唯一的ObjectId

2. mongodb的保存

命令:db.集合名称.save(document)

# 先进行_id匹配,若能匹配,则检查后面的属性是否相同,若相同,不做任何操作,若不同,则修改属性;若_id不能匹配,则将数据作为新数据直接插入
db.stu.save({_id:'20170101', name:'gj', gender:2})
db.stu.save({name:'gj', gender:2})
db.stu.find()

如果文档的_id已经存在则修改,如果_id不存在则添加

3 mongodb的查询

命令:db.集合名称.find()

可以使用以下数据进行练习

db.stu.insert([{"name" : "郭靖", "hometown" : "蒙古", "age" : 20, "gender" : true },
{"name" : "黄蓉", "hometown" : "桃花岛", "age" : 18, "gender" : false },
{"name" : "华筝", "hometown" : "蒙古", "age" : 18, "gender" : false },
{"name" : "黄药师", "hometown" : "桃花岛", "age" : 40, "gender" : true },
{"name" : "段誉", "hometown" : "大理", "age" : 16, "gender" : true },
{"name" : "段王爷", "hometown" : "大理", "age" : 45, "gender" : true },
{"name" : "洪七公", "hometown" : "华筝", "age" : 18, "gender" : true }])
3.1 简单查询
  • 方法find(): 查询

    db.集合名称.find({条件文档})

  • 方法findOne():查询,只返回第一个

    db.集合名称.findOne({条件文档})

  • 方法pretty(): 将结果格式化;不能和findOne()一起使用!

    db.集合名称.find({条件文档}).pretty()

3.2 比较运算符
  • 等于: 默认是等于判断, 没有运算符
  • 小于:$lt (less than)
  • 小于等于:$lte (less than equal)
  • 大于:$gt (greater than)
  • 大于等于:$gte
  • 不等于:$ne
查询年龄大于18的所有学生
db.stu.find({age:{$gte:18}})
3.3 逻辑运算符

逻辑运算符主要指与、或逻辑

  • and:在json中写多个条件即可
查询年龄大于或等于18, 并且性别为true的学生
db.stu.find({age:{$gte:18},gender:true})
# 或者
db.stu.find({$and:[{age:18},{gender:true}]})
  • or:使用$or, 值为数组, 数组中每个元素为json
查询年龄大于18, 或性别为false的学生
db.stu.find({$or:[{age:{$gt:18}},{gender:false}]})

查询年龄大于18或性别为男生, 并且姓名是郭靖
db.stu.find({$or:[{age:{$gte:18}},{gender:true}],name:'gj'})
3.4 范围运算符

使用$in$nin 判断数据是否在某个数组内

查询年龄为18、 28的学生
db.stu.find({age:{$in:[18,28,38]}})
3.5 支持正则表达式

使用$regex编写正则表达式

查询name以'黄'开头的数据
db.stu.find({name:{$regex:'^黄'}})
3.6 自定义查询

mongo shell 是一个js的执行环境
使用$where 写一个函数, 返回满足条件的数据

查询年龄大于30的学生
db.stu.find({
 $where:function() {
     return this.age>30;}
})
3.7 skip和limit
  • 方法limit(): 用于读取指定数量的文档
db.集合名称.find().limit(NUMBER)
查询2条学生信息
db.stu.find().limit(2)
  • 方法skip(): 用于跳过指定数量的⽂档
db.集合名称.find().skip(NUMBER)
db.stu.find().skip(2)
  • 同时使用
db.stu.find().limit(4).skip(5)
db.stu.find().skip(5).limit(4)
> db.stu.find().skip(0).limit(2)
{ "_id" : ObjectId("60c9f4a941d5d5803918eed6"), "name" : "郭靖", "hometown" : "蒙古", "age" : 20, "gender" : true }
{ "_id" : ObjectId("60c9f4a941d5d5803918eed7"), "name" : "黄蓉", "hometown" : "桃花岛", "age" : 18, "gender" : false }
> db.stu.find().skip(2).limit(2)
{ "_id" : ObjectId("60c9f4a941d5d5803918eed8"), "name" : "华筝", "hometown" : "蒙古", "age" : 18, "gender" : false }
{ "_id" : ObjectId("60c9f4a941d5d5803918eed9"), "name" : "黄药师", "hometown" : "桃花岛", "age" : 40, "gender" : true }
> db.stu.find().skip(4).limit(2)
{ "_id" : ObjectId("60c9f4a941d5d5803918eeda"), "name" : "段誉", "hometown" : "大理", "age" : 16, "gender" : true }
{ "_id" : ObjectId("60c9f4a941d5d5803918eedb"), "name" : "段王爷", "hometown" : "大理", "age" : 45, "gender" : true }

注意:先使用skip再使用limit的效率要高于前者(skip优先级高于limit)

3.8 投影

在查询到的返回结果中, 只选择必要的字段

命令:db.集合名称.find({},{字段名称:1,...})

参数为字段与值, 值为1表示显示, 值为0不显示
特别注意:

  • 对于_id列默认是显示的, 如果不显示需要明确设置为0
  • 对于其他不显示的字段不能设置为0(除了_id,其余字段不能设置0,1共存)

db.stu.find({},{_id:0,name:1,gender:1})

> db.stu.find({},{name:1,age:1})
{ "_id" : ObjectId("60c9f4a941d5d5803918eed6"), "name" : "郭靖", "age" : 20 }
{ "_id" : ObjectId("60c9f4a941d5d5803918eed7"), "name" : "黄蓉", "age" : 18 }
{ "_id" : ObjectId("60c9f4a941d5d5803918eed8"), "name" : "华筝", "age" : 18 }
{ "_id" : ObjectId("60c9f4a941d5d5803918eed9"), "name" : "黄药师", "age" : 40 }
{ "_id" : ObjectId("60c9f4a941d5d5803918eeda"), "name" : "段誉", "age" : 16 }
{ "_id" : ObjectId("60c9f4a941d5d5803918eedb"), "name" : "段王爷", "age" : 45 }
{ "_id" : ObjectId("60c9f4a941d5d5803918eedc"), "name" : "洪七公", "age" : 18 }
#_id可以与其他字段0,1共存
> db.stu.find({},{name:1,age:1,_id:0})
{ "name" : "郭靖", "age" : 20 }
{ "name" : "黄蓉", "age" : 18 }
{ "name" : "华筝", "age" : 18 }
{ "name" : "黄药师", "age" : 40 }
{ "name" : "段誉", "age" : 16 }
{ "name" : "段王爷", "age" : 45 }
{ "name" : "洪七公", "age" : 18 }
# 其他字段不能0,1共存
> db.stu.find({},{name:1,age:1,gender:0})
Error: error: {
        "ok" : 0,
        "errmsg" : "Cannot do exclusion on field gender in inclusion projection",
        "code" : 31254,
        "codeName" : "Location31254"
}
> db.stu.find({},{age:0,gender:0})
{ "_id" : ObjectId("60c9f4a941d5d5803918eed6"), "name" : "郭靖", "hometown" : "蒙古" }
{ "_id" : ObjectId("60c9f4a941d5d5803918eed7"), "name" : "黄蓉", "hometown" : "桃花岛" }
{ "_id" : ObjectId("60c9f4a941d5d5803918eed8"), "name" : "华筝", "hometown" : "蒙古" }
{ "_id" : ObjectId("60c9f4a941d5d5803918eed9"), "name" : "黄药师", "hometown" : "桃花岛" }
{ "_id" : ObjectId("60c9f4a941d5d5803918eeda"), "name" : "段誉", "hometown" : "大理" }
{ "_id" : ObjectId("60c9f4a941d5d5803918eedb"), "name" : "段王爷", "hometown" : "大理" }
{ "_id" : ObjectId("60c9f4a941d5d5803918eedc"), "name" : "洪七公", "hometown" : "华筝" }
3.9 排序

方法sort(), 用于对查询结果按照指定的字段进行排序

命令:db.集合名称.find().sort({字段:1,...})

参数1为升序排列
参数-1为降序排列

根据性别降序, 再根据年龄升序
db.stu.find().sort({gender:-1,age:1})
3.10 统计个数

方法count()用于统计结果集中文档条数

命令:db.集合名称.find({条件}).count()
命令:db.集合名称.count({条件})

db.stu.find({gender:true}).count()
db.stu.count({age:{$gt:20},gender:true})
> db.stu.find().count()
7
> db.stu.count()
7

> db.stu.count({age:18})
3
> db.stu.find({age:18}).count()
3
3.11 去重

方法distinct()用于对指定字段进行去重
命令:db.集合名称.distinct(去重字段)

> db.stu.distinct('hometown')
[ "华筝", "大理", "桃花岛", "蒙古" ]
> db.stu.distinct('age')
[ 16, 18, 20, 40, 45 ]

对指定条件进行去重
命令:db.集合名称.distinct(去重字段,{条件})

> db.stu.distinct('hometown',{age:18})
[ "华筝", "桃花岛", "蒙古" ]
> db.stu.find({age:18})
{ "_id" : ObjectId("60c9f4a941d5d5803918eed7"), "name" : "黄蓉", "hometown" : "桃花岛", "age" : 18, "gender" : false }
{ "_id" : ObjectId("60c9f4a941d5d5803918eed8"), "name" : "华筝", "hometown" : "蒙古", "age" : 18, "gender" : false }
{ "_id" : ObjectId("60c9f4a941d5d5803918eedc"), "name" : "洪七公", "hometown" : "华筝", "age" : 18, "gender" : true }

4 mongodb的更新

db.集合名称.update({query}, {update}, {multi: boolean})
  • 参数query:查询条件
  • 参数update:更新操作符
  • 参数multi:可选,默认是false,表示只更新找到的第一条数据,值为true表示把满足条件的数据全部更新
db.stu.update({name:'hr'},{name:'mnc'})           # 全文档进行覆盖更新
db.stu.update({name:'hr'},{$set:{name:'hys'}})    # 指定键值更新操作
db.stu.update({},{$set:{gender:0}},{multi:true})  # 更新全部
> db.nor_col.find()
{ "_id" : ObjectId("60c9f25441d5d5803918eed4"), "name" : "liu", "age" : 35, "class" : 2, "num" : 3 }
# 除了主键_id外,其余数据均被覆盖更新
> db.nor_col.update({num:3},{name:'Liu'})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.nor_col.find()
{ "_id" : ObjectId("60c9f25441d5d5803918eed4"), "name" : "Liu" }

注意:“multi update only works with $ operators”

  • multi参数必须和$set一起使用!
> db.nor_col.update({num:2},{num:3},{multi:true})
WriteResult({
        "nMatched" : 0,
        "nUpserted" : 0,
        "nModified" : 0,
        "writeError" : {
                "code" : 9,
                "errmsg" : "multi update is not supported for replacement-style update"
        }
})
> db.nor_col.find()
{ "_id" : ObjectId("60c9f1c441d5d5803918eed0"), "name" : "wang", "age" : 40, "class" : 2, "num" : 2 }
{ "_id" : ObjectId("60c9f1d041d5d5803918eed1"), "name" : "wang", "age" : 50, "class" : 2, "num" : 2 }
{ "_id" : ObjectId("60c9f1d741d5d5803918eed2"), "name" : "wang", "age" : 45, "class" : 2, "num" : 2 }
{ "_id" : ObjectId("60c9f25441d5d5803918eed3"), "name" : "wang", "age" : 45, "class" : 2, "num" : 2 }
{ "_id" : ObjectId("60c9f25441d5d5803918eed5"), "name" : "zhang", "age" : 40, "class" : 2, "num" : 2 }
...
# 无multi,默认更新第一条
> db.nor_col.update({num:2},{$set:{age:99}})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.nor_col.find()
{ "_id" : ObjectId("60c9f1c441d5d5803918eed0"), "name" : "wang", "age" : 99, "class" : 2, "num" : 2 }
{ "_id" : ObjectId("60c9f1d041d5d5803918eed1"), "name" : "wang", "age" : 50, "class" : 2, "num" : 2 }
{ "_id" : ObjectId("60c9f1d741d5d5803918eed2"), "name" : "wang", "age" : 45, "class" : 2, "num" : 2 }
{ "_id" : ObjectId("60c9f25441d5d5803918eed3"), "name" : "wang", "age" : 45, "class" : 2, "num" : 2 }
{ "_id" : ObjectId("60c9f25441d5d5803918eed5"), "name" : "zhang", "age" : 40, "class" : 2, "num" : 2 }
...

> db.nor_col.update({num:2},{$set:{age:99}},{multi:true})
WriteResult({ "nMatched" : 5, "nUpserted" : 0, "nModified" : 4 })
> db.nor_col.find()
{ "_id" : ObjectId("60c9f1c441d5d5803918eed0"), "name" : "wang", "age" : 99, "class" : 2, "num" : 2 }
{ "_id" : ObjectId("60c9f1d041d5d5803918eed1"), "name" : "wang", "age" : 99, "class" : 2, "num" : 2 }
{ "_id" : ObjectId("60c9f1d741d5d5803918eed2"), "name" : "wang", "age" : 99, "class" : 2, "num" : 2 }
{ "_id" : ObjectId("60c9f25441d5d5803918eed3"), "name" : "wang", "age" : 99, "class" : 2, "num" : 2 }
{ "_id" : ObjectId("60c9f25441d5d5803918eed5"), "name" : "zhang", "age" : 99, "class" : 2, "num" : 2 }
...

全文档更新

> db.nor_col.update({},{$set:{school:'Mongo'}},{multi:true})
WriteResult({ "nMatched" : 9, "nUpserted" : 0, "nModified" : 9 })

> db.nor_col.find()
{ "_id" : ObjectId("60c9f19b41d5d5803918eecf"), "name" : "yang", "age" : 18, "class" : 1, "num" : 1, "school" : "Mongo" }
{ "_id" : ObjectId("60c9f1c441d5d5803918eed0"), "multi" : true, "school" : "Mongo" }
{ "_id" : ObjectId("60c9f1d041d5d5803918eed1"), "name" : "wang", "age" : 99, "class" : 2, "num" : 2, "school" : "Mongo" }
{ "_id" : ObjectId("60c9f1d741d5d5803918eed2"), "name" : "wang", "age" : 99, "class" : 2, "num" : 2, "school" : "Mongo" }
{ "_id" : ObjectId("60c9f25441d5d5803918eed3"), "name" : "wang", "age" : 99, "class" : 2, "num" : 2, "school" : "Mongo" }
{ "_id" : ObjectId("60c9f25441d5d5803918eed4"), "name" : "Liu", "school" : "Mongo" }
...

5 mongodb的删除

db.集合名称.remove({query}, {justOne: boolean})
  • 参数query:可选,删除的⽂档的条件
  • 参数justOne:可选, 如果设为true或1,则只删除一条,默认false,表示删除全部

mongodb的聚合操作

1 mongodb的聚合是什么

聚合(aggregate)是基于数据处理的聚合管道,每个文档通过一个由多个阶段(stage)组成的管道,可以对每个阶段的管道进行分组、过滤等功能,然后经过一系列的处理,输出相应的结果。

语法:db.集合名称.aggregate({管道:{表达式}})

在这里插入图片描述

2 mongodb的常用管道和表达式

2.1 常用管道命令

在mongodb中,⽂档处理完毕后, 通过管道进⾏下⼀次处理
常用管道命令如下:

  • $group: 将集合中的⽂档分组, 可⽤于统计结果
  • $match: 过滤数据, 只输出符合条件的⽂档
  • $project: 修改输⼊⽂档的结构, 如重命名、 增加、 删除字段、 创建计算结果
  • $sort: 将输⼊⽂档排序后输出
  • $limit: 限制聚合管道返回的⽂档数
  • $skip: 跳过指定数量的⽂档, 并返回余下的⽂档
2.2 常用表达式

表达式:处理输⼊⽂档并输出
语法:表达式:'$列名'
常⽤表达式:

  • $sum: 计算总和, $sum:1 表示以⼀倍计数
  • $avg: 计算平均值
  • $min: 获取最⼩值
  • $max: 获取最⼤值
  • $push: 在结果⽂档中插⼊值到⼀个数组中

3 管道命令之$group

3.1 按照某个字段进行分组

$group是所有聚合命令中用的最多的一个命令,用来将集合中的文档分组,可用于统计结果

使用示例如下

db.stu.aggregate(
    {$group:
        {
        	# 此处_id代表分组结果中的键
            _id:"$gender",
            counter:{$sum:1}
        }
    }
)
> db.stu.aggregate({$group:{_id:'$gender',counter:{$sum:1} }})
{ "_id" : true, "counter" : 5 }
{ "_id" : false, "counter" : 2 }

> db.stu.aggregate({$group:{_id:'$gender',counter:{$sum:2}}})
{ "_id" : true, "counter" : 10 }
{ "_id" : false, "counter" : 4 }

> db.stu.aggregate({$group:{_id:'$gender',counter:{$sum:0} }})
{ "_id" : true, "counter" : 0 }
{ "_id" : false, "counter" : 0 }

# 按性别分组后,求年龄总和
> db.stu.aggregate({$group:{_id:'$gender',number:{$sum:1},sum_age:{$sum:'$age'}}})
{ "_id" : true, "number" : 5, "sum_age" : 139 }
{ "_id" : false, "number" : 2, "sum_age" : 36 }

# 按性别分组后,求年龄平均值
> db.stu.aggregate({$group:{_id:'$gender', avg:{$avg:'$age'}}})
{ "_id" : true, "avg" : 27.8 }
{ "_id" : false, "avg" : 18 }

# 通过push构建姓名列表 
> db.stu.aggregate({$group:{_id:'$gender', counter:{$sum:1}, sum_age:{$sum:'$age'}, avg_age:{$avg:'$age'}, name_list:{$push:'$name'}}})
{ "_id" : true, "counter" : 5, "sum_age" : 139, "avg_age" : 27.8, "name_list" : [ "郭靖", "黄药师", "段誉", "段王爷", " 洪七公" ] }
{ "_id" : false, "counter" : 2, "sum_age" : 36, "avg_age" : 18, "name_list" : [ "黄蓉", "华筝" ] }

其中注意点:

  • db.db_name.aggregate是语法,所有的管道命令都需要写在其中
  • _id 表示分组的依据,按照哪个字段进行分组,需要使用$gender表示选择这个字段进行分组
  • $sum:1 表示把每条数据作为1进行统计,统计的是该分组下面数据的条数
3.2 group by null

当我们需要统计整个文档的时候,$group 的另一种用途就是把整个文档分为一组进行统计

使用实例如下:

db.stu.aggregate(
    {$group:
        {
            _id:null,
            counter:{$sum:1}
        }
    }
)

其中注意点:

  • _id:null 表示不指定分组的字段,即统计整个文档,此时获取的counter表示整个文档的个数
3.3 数据透视

正常情况在统计的不同性别的数据的时候,需要知道所有的name,需要逐条观察,如果通过某种方式把所有的name放到一起,那么此时就可以理解为数据透视

使用示例如下:

  1. 统计不同性别的学生

    db.stu.aggregate(
        {$group:
            {
                _id:null,
                name:{$push:"$name"}
            }
        }
    )
    
  2. 使用$$ROOT可以将整个文档放入数组中

    db.stu.aggregate(
        {$group:
            {
                _id:null,
                name:{$push:"$$ROOT"}
            }
        }
    )
    
3.4 练习

对于如下数据,需要统计出每个country/province下的userid的数量(同一个userid只统计一次)

{ "country" : "china", "province" : "sh", "userid" : "a" }  
{  "country" : "china", "province" : "sh", "userid" : "b" }  
{  "country" : "china", "province" : "sh", "userid" : "a" }  
{  "country" : "china", "province" : "sh", "userid" : "c" }  
{  "country" : "china", "province" : "bj", "userid" : "da" }  
{  "country" : "china", "province" : "bj", "userid" : "fa" }

参考答案

db.tv3.aggregate(
  {$group:{_id:{country:'$country',province:'$province',userid:'$userid'}}},
  {$group:{_id:{country:'$_id.country',province:'$_id.province'},count:{$sum:1}}}

4 管道命令之$match

$match用于进行数据的过滤,是在能够在聚合操作中使用的命令,和find区别在于$match 操作可以把结果交给下一个管道处理,而find不行

使用示例如下:

  1. 查询年龄大于20的学生

    db.stu.aggregate(
        {$match:{age:{$gt:20}}
        )
    
  2. 查询年龄大于20的男女学生的人数

    db.stu.aggregate(
        {$match:{age:{$gt:20}}
        {$group:{_id:"$gender",counter:{$sum:1}}}
        )
    
# match和group组合
> db.stu.aggregate({$match:{age:18}},{$group:{_id:'$gender', name:{$push:'$name'}}})
{ "_id" : false, "name" : [ "黄蓉", "华筝" ] }
{ "_id" : true, "name" : [ "洪七公" ] }

5 管道命令之$project

$project用于修改文档的输入输出结构,例如重命名,增加,删除字段

使用示例如下:

  1. 查询学生的年龄、姓名,仅输出年龄姓名

    db.stu.aggregate(
        {$project:{_id:0,name:1,age:1}}
        )
    
  2. 查询男女生人生,输出人数

    db.stu.aggregate(
        {$group:{_id:"$gender",counter:{$sum:1}}}
        {$project:{_id:0,counter:1}}
        )
    
> db.stu.aggregate({$match:{name:/黄/}},{$group:{_id:'$gender',name:{$push:'$name'}, avg:{$avg:'$age'}}},{$project:{name:1,avg:1}})))))
{ "_id" : true, "name" : [ "黄药师" ], "avg" : 40 }
{ "_id" : false, "name" : [ "黄蓉" ], "avg" : 18 }
5.1 练习

对于如下数据:统计出每个country/province下的userid的数量(同一个userid只统计一次),结果中的字段为{country:"",province:"",counter:"*"}

{ "country" : "china", "province" : "sh", "userid" : "a" }  
{  "country" : "china", "province" : "sh", "userid" : "b" }  
{  "country" : "china", "province" : "sh", "userid" : "a" }  
{  "country" : "china", "province" : "sh", "userid" : "c" }  
{  "country" : "china", "province" : "bj", "userid" : "da" }  
{  "country" : "china", "province" : "bj", "userid" : "fa" }  

参考答案

db.tv3.aggregate(
  {$group:{_id:{country:'$country',province:'$province',userid:'$userid'}}},
  {$group:{_id:{country:'$_id.country',province:'$_id.province'},count:{$sum:1}}},
  {$project:{_id:0,country:'$_id.country',province:'$_id.province',counter:'$count'}}
  )

6 管道命令之$sort

$sort用于将输入的文档排序后输出

使用示例如下:

  1. 查询学生信息,按照年龄升序

    db.stu.aggregate({$sort:{age:1}})
    
  2. 查询男女人数,按照人数降序

    db.stu.aggregate(
        {$group:{_id:"$gender",counter:{$sum:1}}},
        {$sort:{counter:-1}}
    )
    

7 管道命令之$skip$limit

  • $limit限制返回数据的条数
  • $skip 跳过指定的文档数,并返回剩下的文档数
  • 同时使用时先使用skip在使用limit

使用示例如下:

  1. 查询2条学生信息

    db.stu.aggregate(
        {$limit:2}
    )
    
  2. 查询从第三条开始的学生信息

    db.stu.aggregate(
        {$skip:3}
    )
    
  3. 统计男女生人数,按照人数升序,返回第二条数据

    db.stu.aggregate(
        {$group:{_id:"$gender",counter:{$sum:1}}},
        {$sort:{counter:-1}},
        {$skip:1},
        {$limit:1}
    )
    
> db.stu.find()
{ "_id" : ObjectId("60c9f4a941d5d5803918eed6"), "name" : "郭靖", "hometown" : "蒙古", "age" : 20, "gender" : true }
{ "_id" : ObjectId("60c9f4a941d5d5803918eed7"), "name" : "黄蓉", "hometown" : "桃花岛", "age" : 18, "gender" : false }
{ "_id" : ObjectId("60c9f4a941d5d5803918eed8"), "name" : "华筝", "hometown" : "蒙古", "age" : 18, "gender" : false }
{ "_id" : ObjectId("60c9f4a941d5d5803918eed9"), "name" : "黄药师", "hometown" : "桃花岛", "age" : 40, "gender" : true }
{ "_id" : ObjectId("60c9f4a941d5d5803918eeda"), "name" : "段誉", "hometown" : "大理", "age" : 16, "gender" : true }
{ "_id" : ObjectId("60c9f4a941d5d5803918eedb"), "name" : "段王爷", "hometown" : "大理", "age" : 45, "gender" : true }
{ "_id" : ObjectId("60c9f4a941d5d5803918eedc"), "name" : "洪七公", "hometown" : "华筝", "age" : 18, "gender" : true }
> db.stu.aggregate({$limit:2})
{ "_id" : ObjectId("60c9f4a941d5d5803918eed6"), "name" : "郭靖", "hometown" : "蒙古", "age" : 20, "gender" : true }
{ "_id" : ObjectId("60c9f4a941d5d5803918eed7"), "name" : "黄蓉", "hometown" : "桃花岛", "age" : 18, "gender" : false }
> db.stu.aggregate({$skip:1})
{ "_id" : ObjectId("60c9f4a941d5d5803918eed7"), "name" : "黄蓉", "hometown" : "桃花岛", "age" : 18, "gender" : false }
{ "_id" : ObjectId("60c9f4a941d5d5803918eed8"), "name" : "华筝", "hometown" : "蒙古", "age" : 18, "gender" : false }
{ "_id" : ObjectId("60c9f4a941d5d5803918eed9"), "name" : "黄药师", "hometown" : "桃花岛", "age" : 40, "gender" : true }
{ "_id" : ObjectId("60c9f4a941d5d5803918eeda"), "name" : "段誉", "hometown" : "大理", "age" : 16, "gender" : true }
{ "_id" : ObjectId("60c9f4a941d5d5803918eedb"), "name" : "段王爷", "hometown" : "大理", "age" : 45, "gender" : true }
{ "_id" : ObjectId("60c9f4a941d5d5803918eedc"), "name" : "洪七公", "hometown" : "华筝", "age" : 18, "gender" : true }

# 注意:聚合中的limit 和 skip 先后顺序不同,执行结果不同
# find()后接limit 和 skip 先后顺序不同,执行结果相同,先skip后limit
> db.stu.aggregate({$skip:1}, {$limit:2})
{ "_id" : ObjectId("60c9f4a941d5d5803918eed7"), "name" : "黄蓉", "hometown" : "桃花岛", "age" : 18, "gender" : false }
{ "_id" : ObjectId("60c9f4a941d5d5803918eed8"), "name" : "华筝", "hometown" : "蒙古", "age" : 18, "gender" : false }
> db.stu.aggregate({$limit:2}, {$skip:1})
{ "_id" : ObjectId("60c9f4a941d5d5803918eed7"), "name" : "黄蓉", "hometown" : "桃花岛", "age" : 18, "gender" : false }

8 拆分管道命令$unwind

db.test3.insert([{'_id':1,'item':'a','size':['S','M','L']},{'_id':2,'item':'b','size':[]},{'_id':3,'item':'c','size':'M'},{'_id':4,'item':'d'},{'_id':5,'item':'e','size':null}])
> db.test3.find()
{ "_id" : 1, "item" : "a", "size" : [ "S", "M", "L" ] }
{ "_id" : 2, "item" : "b", "size" : [ ] }
{ "_id" : 3, "item" : "c", "size" : "M" }
{ "_id" : 4, "item" : "d" }
{ "_id" : 5, "item" : "e", "size" : null }
# 为空和null的字段拆分时候不显示数据
> db.test3.aggregate({$unwind:'$size'})
{ "_id" : 1, "item" : "a", "size" : "S" }
{ "_id" : 1, "item" : "a", "size" : "M" }
{ "_id" : 1, "item" : "a", "size" : "L" }
{ "_id" : 3, "item" : "c", "size" : "M" }

# 若想显示为空的数据
# path:要拆分字段
# preserveNullAndEmptyArrays:true 是否显示空字段
> db.test3.aggregate({$unwind:{path:'$size',preserveNullAndEmptyArrays:true}})
{ "_id" : 1, "item" : "a", "size" : "S" }
{ "_id" : 1, "item" : "a", "size" : "M" }
{ "_id" : 1, "item" : "a", "size" : "L" }
{ "_id" : 2, "item" : "b" }
{ "_id" : 3, "item" : "c", "size" : "M" }
{ "_id" : 4, "item" : "d" }
{ "_id" : 5, "item" : "e", "size" : null }

Mongodb的索引操作

1. 为什么mongdb需要创建索引

  • 加快查询速度
  • 进行数据的去重

2. mongodb创建简单的索引方法

  • 语法:db.集合名.ensureIndex({属性:1}),1表示升序, -1表示降序

3. 创建索引前后查询速度对比

测试:插入10万条数据到数据库中

插入数据:

for(i=0;i<100000;i++){db.t1.insert({name:'test'+i,age:i})}

创建索引前:

db.t1.find({name:'test10000'})
db.t1.find({name:'test10000'}).explain('executionStats') # 显示查询操作的详细信息

创建索引:

db.t1.ensureIndex({name:1})

创建索引后:

db.t1.find({name:'test10000'}).explain('executionStats')

前后速度对比

在这里插入图片描述

4. 索引的查看

默认情况下_id是集合的索引
查看方式:db.集合名.getIndexes()

5. 删除索引

语法:db.集合名.dropIndex({'索引名称':1})

db.t1.dropIndex({name:1})
db.t1.getIndexes()

6. mongodb创建唯一索引

在默认情况下mongdb的索引域的值是可以相同的,创建唯一索引之后,数据库会在插入数据的时候检查创建索引域的值是否存在,如果存在则不会插入该条数据,但是创建索引仅仅能够提高查询速度,同时降低数据库的插入速度。

6.1 添加唯一索引的语法:
db.集合名.ensureIndex({"字段名":1}, {"unique":true})
6.2 利用唯一索引进行数据去重

根据唯一索引指定的字段的值,如果相同,则无法插入数据

db.t1.ensureIndex({"name":1}, {"unique":true})
db.t1.insert({name: 'test10000'})

7. 建立复合索引

在进行数据去重的时候,可能用一个域来保证数据的唯一性,这个时候可以考虑建立复合索引来实现。

例如:抓全贴吧信息,如果把帖子的名字作为唯一索引对数据进行去重是不可取的,因为可能有很多帖子名字相同

建立复合索引的语法:db.collection_name.ensureIndex({字段1:1,字段2:1})

8. 建立索引注意点

  • 根据需要选择是否需要建立唯一索引

  • 索引字段是升序还是降序在单个索引的情况下不影响查询效率,但是带复合索引的条件下会有影响

  • 数据量巨大并且数据库的读出操作非常频繁的时候才需要创建索引,如果写入操作非常频繁,创建索引会影响写入速度

    例如:在进行查询的时候如果字段1需要升序的方式排序输出,字段2需要降序的方式排序输出,那么此时复合索引的建立需要把字段1设置为1,字段2设置为-1

Mongodb的权限管理

1. 为什么要进行权限管理的设置

刚安装完毕的mongodb默认不使用权限认证方式启动,与MySQL不同,mongodb在安装的时候并没有设置权限,然而公网运行系统需要设置权限以保证数据安全,所以要学习mongodb的权限管理

2. mongodb的权限管理方案

  • MongoDB是没有默认管理员账号,所以要先添加管理员账号,并且mongodb服务器需要在运行的时候开启验证模式
    • 用户只能在用户所在数据库登录(创建用户的数据库),包括管理员账号。
    • 管理员可以管理所有数据库,但是不能直接管理其他数据库,要先认证后才可以。

3. mongodb超级管理员账号的创建

3.1 创建超级用户

进入mongo shell

sudo mongod

使用admin数据库(超级管理员账号必须创建在该数据库上)

use admin

创建超级用户

db.createUser({"user":"账号","pwd":"密码","roles":["root"]})

创建成功会显示如下信息

Successfully added user: { "user" : "账号", "roles" : [ "root" ] }

退出mongo shell

exit
3.2 以权限认证的方式启动mongodb数据库
sudo mongod --auth

启动之后在启动信息中会有如下信息,说明mongodb以权限认证的方式启动成功

[initandlisten] options: { security: { authorization: "enabled" } }
3.3 登录验证

此时再使用数据库各命令的时候会报权限错误,需要认证才能执行相应操作、

use admin
db.auth('python','python')
  • python用户是创建在admin数据库上的所以必须来到admin数据库上进行认证
  • 认证成功会返回1,失败返回0

4. 创建普通用户

4.1 在使用的数据库上创建普通用户

1.选择需要创建用户的数据库

use test1
  1. 创建用户
db.createUser("user":"账号", "pwd":"密码", roles:["read"])
创建普通用户user1,该用户在test1上的权限是只读
db.createUser("user":"账号", "pwd":"密码", roles:["readWrite"])
创建普通用户user1,该用户在test1上的权限是读写
4.2 在admin用户数据库上创建普通用户
use admin
db.createUser({"user":"python1", "pwd":"python1", roles:[{"role":"read","db":"dbname1"},{"role":"readWrite","db":"dbname2"}
]})

在admin上创建python1用户,python1用户的权限有两个,一个再dbname1上的只读,另一个是在dbname2上的读写

5. 查看创建的用户

show users
{
	"_id" : "admin.python",
	"user" : "python",
	"db" : "admin",
	"roles" : [
		{
			"role" : "root",
			"db" : "admin"
		}
	]
}

6. 删除用户

6.1 进入账号数据所在的数据库
use db_name
6.2 删除用户
db.dropUser('python')

mongodb和python交互

1. mongdb和python交互的模块

pymongo 提供了mongdb和python交互的所有方法
安装方式: pip install pymongo

2. 使用pymongo

2.1 导入pymongo并选择要操作的集合

数据库和集合能够自动创建

2.1.1 无需权限认证的方式创建连接对象以及集合操作对象
from pymongo import MongoClient

client = MongoClient(host,port) # 如果是本地连接host,port参数可以省略

collection = client[db名][集合名]
# collection = client.db名.集合名 # 与上边用法相同
2.1.2 需要权限认证的方式创建连接对象以及集合操作对象
from pymongo import MongoClient
from urllib.parse import quote_plus

user = 'python' # 账号
password = 'python' # 密码
host = '127.0.0.1' # host
port = 27017 # port
uri = "mongodb://%s:%s@%s" % (quote_plus(user),
                              quote_plus(password),
                              host)
# quote_plus函数:对url进行编码
# uri = mongodb://python:python@127.0.0.1
client = MongoClient(uri, port=port)
collection = client.db名.集合名
from pymongo import MongoClient


# 创建数据库连接对象
client = MongoClient('127.0.0.1', 27017)

# 认证
db = client['admin']
db.authenticate('账号','密码')
# 选择一个数据库和集合
col = client['pydata']['test']
2.2 insert()添加数据

insert可以批量的插入数据列表,也可以插入一条数据

collection.insert({一条数据})
collection.insert([{数据一},{数据二}])
2.2.1 添加一条数据

返回插入数据的_id

ret = collection.insert({"name":"test10010","age":33})
print(ret)
2.2.2 添加多条数据

返回ObjectId对象构成的列表

item_list = [{"name":"test1000{}".format(i)} for i in range(10)]
rets = collection.insert(item_list)
print(rets)
for ret in rets:
    print(ret)
2.3 find_one()查找一条数据

接收一个字典形式的条件,返回字典形式的整条数据
如果条件为空,则返回第一条

ret = client.test.test.find_one({'name': 'test10001'})
print(ret) # 包含mongodb的ObjectId对象的字典
_ = ret.pop('_id') # 清除mongodb的ObjectId对象的k,v
print(ret) 
2.4 find()查找全部数据

返回所有满足条件的结果,如果条件为空,则返回全部
结果是一个Cursor游标对象,是一个可迭代对象,可以类似读文件的指针,但是只能够进行一次读取

rets = collection.find({"name":"test10005"})for ret in rets:
    print(ret)
for ret in rets: #此时rets中没有内容
    print(ret)
2.5 update()更新数据(全文档覆盖或指定键值,更新一条或多条)
  • 语法:collection.update({条件}, {’$set’:{指定的kv或完整的一条数据}}, multi=False/True, upsert=False/True)
  • multi参数:默认为False,表示更新一条; multi=True则更新多条; multi参数必须和$set一起使用
  • upsert参数:默认为False; upsert=True则先查询是否存在,存在则更新;不存在就插入
  • $set表示指定字段进行更新
2.5.1 更新一条数据;全文档覆盖;存在就更新,不存在就插入
data = {'msg':'这是一条完整的数据1','name':'哈哈'}
client.test.test.update({'haha': 'heihei'}, {'$set':data}, upsert=True)
2.5.2 更新多条数据;全文档覆盖;存在就更新,不存在就插入
data = {'msg':'这是一条完整的数据2','name':'哈哈'} # 该完整数据是先查询后获取的
client.test.test.update({}, {'$set':data}, multi=True, upsert=True)
2.5.3 更新一条数据;指定键值;存在就更新,不存在就插入
data = {'msg':'指定只更新msg___1'}
client.test.test.update({}, {'$set':data}, upsert=True)
2.5.4 更新多条数据;指定键值;存在就更新,不存在就插入
data = {'msg':'指定只更新msg___2'}
client.test.test.update({}, {'$set':data}, multi=True, upsert=True)
2.6 delete_one()删除一条数据
collection.delete_one({"name":"test10010"})
2.7 delete_many()删除全部数据
collection.delete_many({"name":"test10010"})

3. pymongo模块其他api

查看pymongo官方文档或源代码

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值