存储库MongoDB
存储在硬盘的非关系型数据库,即表与表之间没有关联关系,提高了伸缩性和可扩展性,查找速度更快。
简介:
MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统。
在高负载的情况下,添加更多的节点,可以保证服务器性能。
MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案。
MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。
1、易用性
MongoDB是一个面向文档(document-oriented)的数据库,而不是关系型数据库。
不采用关系型主要是为了获得更好得扩展性。当然还有一些其他好处,与关系数据库相比,面向文档的数据库不再有“行“(row)的概念取而代之的是更为灵活的“文档”(document)模型。
通过在文档中嵌入文档和数组,面向文档的方法能够仅使用一条记录来表现复杂的层级关系,这与现代的面向对象语言的开发者对数据的看法一致。
另外,不再有预定义模式(predefined schema):文档的键(key)和值(value)不再是固定的类型和大小。由于没有固定的模式,根据需要添加或删除字段变得更容易了。
通常由于开发者能够进行快速迭代,所以开发进程得以加快。
而且,实验更容易进行。开发者能尝试大量的数据模型,从中选一个最好的。
2、丰富的功能
MongoDB作为一款通用型数据库,除了能够创建、读取、更新和删除数据之外,还提供了一系列不断扩展的独特功能 #1、索引 支持通用二级索引,允许多种快速查询,且提供唯一索引、复合索引、地理空间索引、全文索引 #2、聚合 支持聚合管道,用户能通过简单的片段创建复杂的集合,并通过数据库自动优化 #3、特殊的集合类型 支持存在时间有限的集合,适用于那些将在某个时刻过期的数据,如会话session。类似地,MongoDB也支持固定大小的集合,用于保存近期数据,如日志 #4、文件存储 支持一种非常易用的协议,用于存储大文件和文件元数据。MongoDB并不具备一些在关系型数据库中很普遍的功能,如链接join和复杂的多行事务。省略 这些的功能是处于架构上的考虑,或者说为了得到更好的扩展性,因为在分布式系统中这两个功能难以高效地实现
MongoDB基础知识
类比mysql来对比学习,有相同之处
安装
MongoDB 提供了可用于 32 位和 64 位系统的预编译二进制包,你可以从MongoDB官网下载安装,MongoDB 预编译二进制包下载地址:https://www.mongodb.com/download-center#community
安装过程中,你可以通过点击 "Custom(自定义)" 按钮来设置你的安装目录。
下一步安装 "install mongoDB compass" 不勾选(当然你也可以选择安装它,可能需要更久的安装时间),MongoDB Compass 是一个图形界面管理工具,我们可以在后面自己到官网下载安装,下载地址:https://www.mongodb.com/download-center/compass。
也可以安装可视化工具来操作数据库,类似mysql的NaVicat,推荐使用Robo 3T
命令行操作
集合操作:表的增删改查
CRUD操作:增删改查
1、数据库操作
#1、增 use config #如果数据库不存在,则创建数据库,否则切换到指定数据库。 #2、查 show dbs #查看所有 可以看到,我们刚创建的数据库config并不在数据库的列表中, 要显示它,我们需要向config数据库插入一些数据。 db.table1.insert({'a':1}) #3、删 use config #先切换到要删的库下 db.dropDatabase() #删除当前库
2、集合操作
#1、增 当第一个文档插入时,集合就会被创建 > use database1 switched to db database1 > db.table1.insert({'a':1}) WriteResult({ "nInserted" : 1 }) > db.table2.insert({'b':2}) WriteResult({ "nInserted" : 1 }) #2、查 > show tables table1 table2 #3、删 > db.table1.drop() true > show tables table2
3、文档操作
增
#1、没有指定_id则默认ObjectId,_id不能重复,且在插入后不可变 #2、插入单条 user0={ "name":"egon", "age":10, 'hobbies':['music','read','dancing'], 'addr':{ 'country':'China', 'city':'BJ' } } db.test.insert(user0) db.test.find() #3、插入多条 user1={ "_id":1, "name":"alex", "age":10, 'hobbies':['music','read','dancing'], 'addr':{ 'country':'China', 'city':'weifang' } } user2={ "_id":2, "name":"wupeiqi", "age":20, 'hobbies':['music','read','run'], 'addr':{ 'country':'China', 'city':'hebei' } } user3={ "_id":3, "name":"yuanhao", "age":30, 'hobbies':['music','drink'], 'addr':{ 'country':'China', 'city':'heibei' } } user4={ "_id":4, "name":"jingliyang", "age":40, 'hobbies':['music','read','dancing','tea'], 'addr':{ 'country':'China', 'city':'BJ' } } user5={ "_id":5, "name":"jinxin", "age":50, 'hobbies':['music','read',], 'addr':{ 'country':'China', 'city':'henan' } } db.user.insertMany([user1,user2,user3,user4,user5])
查
比较运算
# SQL:=,!=,>,<,>=,<= # MongoDB:{key:value}代表什么等于什么,"$ne","$gt","$lt","gte","lte",其中"$ne"能用于所有数据类型 #1、select * from db1.user where name = "alex"; db.user.find({'name':'alex'}) #2、select * from db1.user where name != "alex"; db.user.find({'name':{"$ne":'alex'}}) #3、select * from db1.user where id > 2; db.user.find({'_id':{'$gt':2}}) #4、select * from db1.user where id < 3; db.user.find({'_id':{'$lt':3}}) #5、select * from db1.user where id >= 2; db.user.find({"_id":{"$gte":2,}}) #6、select * from db1.user where id <= 2; db.user.find({"_id":{"$lte":2}})
逻辑运算
# SQL:and,or,not # MongoDB:字典中逗号分隔的多个条件是and关系,"$or"的条件放到[]内,"$not" #1、select * from db1.user where id >= 2 and id < 4; db.user.find({'_id':{"$gte":2,"$lt":4}}) #2、select * from db1.user where id >= 2 and age < 40; db.user.find({"_id":{"$gte":2},"age":{"$lt":40}}) #3、select * from db1.user where id >= 5 or name = "alex"; db.user.find({ "$or":[ {'_id':{"$gte":5}}, {"name":"alex"} ] }) #4、select * from db1.user where id % 2=1; db.user.find({'_id':{"$mod":[2,1]}}) #5、上题,取反 db.user.find({'_id':{"$not":{"$mod":[2,1]}}})
成员运算
# SQL:in,not in # MongoDB:"$in","$nin" #1、select * from db1.user where age in (20,30,31); db.user.find({"age":{"$in":[20,30,31]}}) #2、select * from db1.user where name not in ('alex','yuanhao'); db.user.find({"name":{"$nin":['alex','yuanhao']}})
正则匹配
# SQL: regexp 正则 # MongoDB: /正则表达/i #1、select * from db1.user where name regexp '^j.*?(g|n)$'; db.user.find({'name':/^j.*?(g|n)$/i})
取消指定的字段
#1、select name,age from db1.user where id=3; db.user.find({'_id':3},{'_id':0,'name':1,'age':1})
查询数组
#1、查看有dancing爱好的人 db.user.find({'hobbies':'dancing'}) #2、查看既有dancing爱好又有tea爱好的人 db.user.find({ 'hobbies':{ "$all":['dancing','tea'] } }) #3、查看第4个爱好为tea的人 db.user.find({"hobbies.3":'tea'}) #4、查看所有人最后两个爱好 db.user.find({},{'hobbies':{"$slice":-2},"age":0,"_id":0,"name":0,"addr":0}) #5、查看所有人的第2个到第3个爱好 db.user.find({},{'hobbies':{"$slice":[1,2]},"age":0,"_id":0,"name":0,"addr":0}) > db.blog.find().pretty() { "_id" : 1, "name" : "alex意外死亡的真相", "comments" : [ { "name" : "egon", "content" : "alex是谁???", "thumb" : 200 }, { "name" : "wxx", "content" : "我去,真的假的", "thumb" : 300 }, { "name" : "yxx", "content" : "吃喝嫖赌抽,欠下两个亿", "thumb" : 40 }, { "name" : "egon", "content" : "xxx", "thumb" : 0 } ] } db.blog.find({},{'comments':{"$slice":-2}}).pretty() #查询最后两个 db.blog.find({},{'comments':{"$slice":[1,2]}}).pretty() #查询1到2
排序
# 排序:--1代表升序,-1代表降序 db.user.find().sort({"name":1,}) db.user.find().sort({"age":-1,'_id':1})
分页
# 分页:--limit代表取多少个document,skip代表跳过前多少个document。 db.user.find().sort({'age':1}).limit(1).skip(2)
获取数量
# 获取数量 db.user.count({'age':{"$gt":30}}) --或者 db.user.find({'age':{"$gt":30}}).count()
改
update() 方法用于更新已存在的文档。语法格式如下: db.collection.update( <query>, <update>, { upsert: <boolean>, multi: <boolean>, writeConcern: <document> } ) 参数说明:对比update db1.t1 set name='EGON',sex='Male' where name='egon' and age=18; query : 相当于where条件。 update : update的对象和一些更新的操作符(如$,$inc...等,相当于set后面的 upsert : 可选,默认为false,代表如果不存在update的记录不更新也不插入,设置为true代表插入。 multi : 可选,默认为false,代表只更新找到的第一条记录,设为true,代表更新找到的全部记录。 writeConcern :可选,抛出异常的级别。 更新操作是不可分割的:若两个更新同时发送,先到达服务器的先执行,然后执行另外一个,不会破坏文档。
覆盖式
#注意:除非是删除,否则_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 db.user.update({"_id":2},obj)
设置:$set
通常文档只会有一部分需要更新。可以使用原子性的更新修改器,指定对文档中的某些字段进行更新。 更新修改器是种特殊的键,用来指定复杂的更新操作,比如修改、增加后者删除 #1、update db1.user set name="WXX" where id = 2 db.user.update({'_id':2},{"$set":{"name":"WXX",}}) #2、没有匹配成功则新增一条{"upsert":true} db.user.update({'_id':6},{"$set":{"name":"egon","age":18}},{"upsert":true}) #3、默认只改匹配成功的第一条,{"multi":改多条} db.user.update({'_id':{"$gt":4}},{"$set":{"age":28}}) db.user.update({'_id':{"$gt":4}},{"$set":{"age":38}},{"multi":true}) #4、修改内嵌文档,把名字为alex的人所在的地址国家改成Japan db.user.update({'name':"alex"},{"$set":{"addr.country":"Japan"}}) #5、把名字为alex的人的地2个爱好改成piao db.user.update({'name':"alex"},{"$set":{"hobbies.1":"piao"}}) #6、删除alex的爱好,$unset db.user.update({'name':"alex"},{"$unset":{"hobbies":""}})
删
复制代码 #1、删除多个中的第一个 db.user.deleteOne({ 'age': 8 }) #2、删除国家为China的全部 db.user.deleteMany( {'addr.country': 'China'} ) #3、删除全部 db.user.deleteMany({})
聚合
如果你有数据存储在MongoDB中,你想做的可能就不仅仅是将数据提取出来那么简单了;你可能希望对数据进行分析并加以利用。MongoDB提供了以下聚合工具: #1、聚合框架 #2、MapReduce(详见MongoDB权威指南) #3、几个简单聚合命令:count、distinct和group。(详见MongoDB权威指南) #聚合框架: 可以使用多个构件创建一个管道,上一个构件的结果传给下一个构件。 这些构件包括(括号内为构件对应的操作符):筛选($match)、投射($project)、分组($group)、排序($sort)、限制($limit)、跳过($skip) 不同的管道操作符可以任意组合,重复使用
可视化工具
pymongo使用
官网:http://api.mongodb.com/python/current/tutorial.html
from pymongo import MongoClient #1、链接 client=MongoClient('mongodb://root:123@localhost:27017/') # client = MongoClient('localhost', 27017) #2、use 数据库 db=client['db2'] #等同于:client.db1 #3、查看库下所有的集合 print(db.collection_names(include_system_collections=False)) #4、创建集合 table_user=db['userinfo'] #等同于:db.user #5、插入文档 import datetime user0={ "_id":1, "name":"egon", "birth":datetime.datetime.now(), "age":10, 'hobbies':['music','read','dancing'], 'addr':{ 'country':'China', 'city':'BJ' } } user1={ "_id":2, "name":"alex", "birth":datetime.datetime.now(), "age":10, 'hobbies':['music','read','dancing'], 'addr':{ 'country':'China', 'city':'weifang' } } # res=table_user.insert_many([user0,user1]).inserted_ids # print(res) # print(table_user.count()) #6、查找 # from pprint import pprint#格式化细 # pprint(table_user.find_one()) # for item in table_user.find(): # pprint(item) # print(table_user.find_one({"_id":{"$gte":1},"name":'egon'})) #7、更新 table_user.update({'_id':1},{'name':'EGON'}) #8、传入新的文档替换旧的文档 table_user.save( { "_id":2, "name":'egon_xxx' } )
scrapy框架
简介:
Scrapy一个开源和协作的框架,其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的,使用它可以以快速、简单、可扩展的方式从网站中提取所需的数据。
但目前Scrapy的用途十分广泛,可用于如数据挖掘、监测和自动化测试等领域,也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。
Scrapy 是基于twisted框架开发而来,twisted是一个流行的事件驱动的python网络框架。因此Scrapy使用了一种非阻塞(又名异步)的代码来实现并发。整体架构大致如下
解释:
- 引擎(EGINE)
引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。有关详细信息,请参见上面的数据流部分。
- 调度器(SCHEDULER)
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址 - 下载器(DOWLOADER)
用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的 - 爬虫(SPIDERS)
SPIDERS是开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求 - 项目管道(ITEM PIPLINES)
在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作 - 下载器中间件(Downloader Middlewares)
位于Scrapy引擎和下载器之间,主要用来处理从EGINE传到DOWLOADER的请求request,已经从DOWNLOADER传到EGINE的响应response,你可用该中间件做以下几件事- process a request just before it is sent to the Downloader (i.e. right before Scrapy sends the request to the website);
- change received response before passing it to a spider;
- send a new Request instead of passing received response to a spider;
- pass response to a spider without fetching a web page;
- silently drop some requests.
- 爬虫中间件(Spider Middlewares)
位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)
官网链接:https://docs.scrapy.org/en/latest/topics/architecture.html
安装
#Windows平台 1、pip3 install wheel #安装后,便支持通过wheel文件安装软件,wheel文件官网:https://www.lfd.uci.edu/~gohlke/pythonlibs 3、pip3 install lxml 4、pip3 install pyopenssl 5、下载并安装pywin32:https://sourceforge.net/projects/pywin32/files/pywin32/ 6、下载twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted 7、执行pip3 install 下载目录Twisted-17.9.0-cp36-cp36m-win_amd64.whl 8、pip3 install scrapy #Linux平台 1、pip3 install scrapy
命令行工具
复制代码 #1 查看帮助 scrapy -h scrapy <command> -h #2 有两种命令:其中Project-only必须切到项目文件夹下才能执行,而Global的命令则不需要 Global commands: startproject #创建项目 genspider #创建爬虫程序 settings #如果是在项目目录下,则得到的是该项目的配置 runspider #运行一个独立的python文件,不必创建项目 shell #scrapy shell url地址 在交互式调试,如选择器规则正确与否 fetch #独立于程单纯地爬取一个页面,可以拿到请求头 view #下载完毕后直接弹出浏览器,以此可以分辨出哪些数据是ajax请求 version #scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依赖库的版本 Project-only commands: crawl #运行爬虫,必须创建项目才行,确保配置文件中ROBOTSTXT_OBEY = False check #检测项目中有无语法错误 list #列出项目中所包含的爬虫名 edit #编辑器,一般不用 parse #scrapy parse url地址 --callback 回调函数 #以此可以验证我们的回调函数是否正确 bench #scrapy bentch压力测试 #3 官网链接 https://docs.scrapy.org/en/latest/topics/commands.html
Spiders
#1、Spiders是由一系列类(定义了一个网址或一组网址将被爬取)组成,具体包括如何执行爬取任务并且如何从页面中提取结构化的数据。 #2、换句话说,Spiders是你为了一个特定的网址或一组网址自定义爬取和解析页面行为的地方
Spiders会循环做如下事情
1、生成初始的Requests来爬取第一个URLS,并且标识一个回调函数 第一个请求定义在start_requests()方法内默认从start_urls列表中获得url地址来生成Request请求,默认的回调函数是parse方法。回调函数在下载完成返回response时自动触发 #2、在回调函数中,解析response并且返回值 返回值可以4种: 包含解析数据的字典 Item对象 新的Request对象(新的Requests也需要指定一个回调函数) 或者是可迭代对象(包含Items或Request) #3、在回调函数中解析页面内容 通常使用Scrapy自带的Selectors,但很明显你也可以使用Beutifulsoup,lxml或其他你爱用啥用啥。 #4、最后,针对返回的Items对象将会被持久化到数据库 通过Item Pipeline组件存到数据库:https://docs.scrapy.org/en/latest/topics/item-pipeline.html#topics-item-pipeline) 或者导出到不同的文件(通过Feed exports:https://docs.scrapy.org/en/latest/topics/feed-exports.html#topics-feed-exports
命令行安装scrapy项目和爬虫程序
scrapy的项目结构
项目结构以及爬虫应用简介
""" -新建项目 - scrapy startproject 项目名字 -新建爬虫 -scrapy genspider 爬虫名 爬取的域名 -项目目录介绍 -spiders -所有的爬虫程序 -items.py -类似于django的model类 -middlewares.py -中间件 -pipelines.py -持久化相关 -settings.py -配置文件 -scrapy.cfg -部署相关 -运行爬虫 -scrapy crawl cnblogs - -nolog -爬取数据 -//*[@id="post_list"]/div[1] """
文件说明:
- scrapy.cfg 项目的主配置信息,用来部署scrapy时使用,爬虫相关的配置信息在settings.py文件中。
- items.py 设置数据存储模板,用于结构化数据,如:Django的Model
- pipelines 数据处理行为,如:一般结构化的数据持久化
- settings.py 配置文件,如:递归的层数、并发数,延迟下载等。强调:配置文件的选项必须大写否则视为无效,正确写法USER_AGENT='xxxx'
- spiders 爬虫目录,如:创建文件,编写爬虫规则。
注意:一般创建爬虫文件时,以网站域名命名
在settings.py里设置:爬虫协议
爬虫程序实例,爬取博客cnllogs主页
用css选择器和xpath选取标签
# -*- coding: utf-8 -*- import scrapy from scrapy import Request class CnblogsSpider(scrapy.Spider): name = 'cnblogs' #爬虫名,必须唯一 allowed_domains = ['cnblogs.com'] #允许的域名, start_urls = ['https://www.cnblogs.com/'] #起始的url #深度优先,广度优先 #爬虫去重:好多策略 #爬虫起始入口 start_requests
def parse_detail(self,response): print(len(response.text)) def parse(self, response): # print('--------4444-------',response) # print(response.text) #混搭 # from bs4 import BeautifulSoup # soup=BeautifulSoup(response.text,"lxml") # soup.find(name='div') #解析 div_list=response.css('.post_item') #取出class为post_item的所有 # print(len(div_list)) #//a[contains(@class,"li")] # div_list=response.xpath('//div[contains(@class,"post_item")]') # div_list=response.xpath('//*[@id="post_list"]/div') # print(len(div_list)) for div in div_list: #extract_first()表示取列表中的第一个 url=div.css('.post_item_body a::attr(href)').extract_first() print(url) yield Request(url,callback=self.parse_detail) next_url=response.css('.pager a:last-child::attr(href)').extract_first() print('https://www.cnblogs.com'+next_url) yield Request('https://www.cnblogs.com'+next_url)
命令行启动,python提供的方法,可以右键启动
运行后爬取的页面:
pycharm中配置main.py,可以右键运行run
#在项目目录下新建:entrypoint.py from scrapy.cmdline import execute execute(['scrapy', 'crawl', 'xiaohua'])
# execute(['scrapy', 'crawl', 'cnblogs','--nolog'])
获取下一页分析,利用css类选择器,选取最后一个元素
爬虫入口
# 入口:start_requests ——————》yield Request(url,callbanck=parser)