Python3網絡爬蟲實戰-3三、數據存儲:非關係型數據庫存儲:MongoDB

NoSQL,全稱 Not Only SQL,意爲不只僅是 SQL,泛指非關係型的數據庫。NoSQL 是基於鍵值對的,並且不須要通過 SQL 層的解析,數據之間沒有耦合性,性能很是高。正則表達式

非關係型數據庫又能夠細分以下:mongodb

  • 鍵值存儲數據庫,表明有 Redis, Voldemort, Oracle BDB 等。
  • 列存儲數據庫,表明有 Cassandra, HBase, Riak 等。
  • 文檔型數據庫,表明有 CouchDB, MongoDB 等。
  • 圖形數據庫,表明有 Neo4J, InfoGrid, Infinite Graph等。

對於爬蟲的數據存儲來講,一條數據可能存在某些字段提取失敗而缺失的狀況,並且數據可能隨時調整,另外數據之間能還存在嵌套關係。若是咱們使用了關係型數據庫存儲,一是須要提早建表,二是若是存在數據嵌套關係的話須要進行序列化操做才能夠存儲,比較不方便。若是用了非關係數據庫就能夠避免一些麻煩,簡單高效。數據庫

本節咱們主要介紹一下 MongoDB 和 Redis 的數據存儲操做。數組

MongoDB存儲

MongoDB 是由 C++ 語言編寫的非關係型數據庫,是一個基於分佈式文件存儲的開源數據庫系統,其內容存儲形式相似 Json 對象,它的字段值能夠包含其餘文檔,數組及文檔數組,很是靈活,在這一節咱們來看一下 Python3 下 MongoDB 的存儲操做。分佈式

1. 準備工做

在本節開始以前請確保已經安裝好了 MongoDB 並啓動了其服務,另外安裝好了 Python 的 PyMongo庫,如沒有安裝能夠參考第一章的安裝過程。ide

2. 鏈接MongoDB

鏈接 MongoDB 咱們須要使用 PyMongo 庫裏面的 MongoClient,通常來講傳入 MongoDB 的 IP 及端口便可,第一個參數爲地址 host,第二個參數爲端口 port,端口若是不傳默認是 27017。性能

import pymongo
client = pymongo.MongoClient(host='localhost', port=27017)

這樣咱們就能夠建立一個 MongoDB 的鏈接對象了。學習

另外 MongoClient 的第一個參數 host 還能夠直接傳MongoDB 的鏈接字符串,以 mongodb 開頭,例如:3d

client = MongoClient('mongodb://localhost:27017/')

能夠達到一樣的鏈接效果。code

3. 指定數據庫

MongoDB 中還分爲一個個數據庫,咱們接下來的一步就是指定要操做哪一個數據庫,在這裏我以 test 數據庫爲例進行說明,因此下一步咱們須要在程序中指定要使用的數據庫。

db = client.test

調用 client 的 test 屬性便可返回 test 數據庫,固然也能夠這樣來指定:

db = client['test']

兩種方式是等價的。

4. 指定集合

MongoDB 的每一個數據庫又包含了許多集合 Collection,也就相似與關係型數據庫中的表,下一步咱們須要指定要操做的集合,在這裏咱們指定一個集合名稱爲 students,學生集合,仍是和指定數據庫相似,指定集合也有兩種方式:

collection = db.students

collection = db['students']

這樣咱們便聲明瞭一個 Collection 對象。

5. 插入數據

接下來咱們即可以進行數據插入了,對於 students 這個Collection,咱們新建一條學生數據,以字典的形式表示:

student = {
    'id': '20170101',
    'name': 'Jordan',
    'age': 20,
    'gender': 'male'
}
Python資源分享qun 784758214 ,內有安裝包,PDF,學習視頻,這裏是Python學習者的彙集地,零基礎,進階,都歡迎

在這裏咱們指定了學生的學號、姓名、年齡和性別,而後接下來直接調用 collection 的 insert() 方法便可插入數據,代碼以下:

result = collection.insert(student)
print(result)

在 MongoDB 中,每條數據其實都有一個 _id 屬性來惟一標識,若是沒有顯式指明 _id,MongoDB 會自動產生一個 ObjectId 類型的 _id 屬性。insert() 方法會在執行後返回的 _id 值。

運行結果:

5932a68615c2606814c91f3d

固然咱們也能夠同時插入多條數據,只須要以列表形式傳遞便可,示例以下:

student1 = {
    'id': '20170101',
    'name': 'Jordan',
    'age': 20,
    'gender': 'male'
}

student2 = {
    'id': '20170202',
    'name': 'Mike',
    'age': 21,
    'gender': 'male'
}

result = collection.insert([student1, student2])
print(result)

返回的結果是對應的 _id 的集合,運行結果:

[ObjectId('5932a80115c2606a59e8a048'), ObjectId('5932a80115c2606a59e8a049')]

實際上在 PyMongo 3.X 版本中,insert() 方法官方已經不推薦使用了,固然繼續使用也沒有什麼問題,官方推薦使用 insert_one() 和 insert_many() 方法將插入單條和多條記錄分開。

student = {
    'id': '20170101',
    'name': 'Jordan',
    'age': 20,
    'gender': 'male'
}

result = collection.insert_one(student)
print(result)
print(result.inserted_id)

運行結果:

<pymongo.results.InsertOneResult object at 0x10d68b558>
5932ab0f15c2606f0c1cf6c5

返回結果和 insert() 方法不一樣,此次返回的是InsertOneResult 對象,咱們能夠調用其 inserted_id 屬性獲取 _id。

對於 insert_many() 方法,咱們能夠將數據以列表形式傳遞便可,示例以下:

student1 = {
    'id': '20170101',
    'name': 'Jordan',
    'age': 20,
    'gender': 'male'
}

student2 = {
    'id': '20170202',
    'name': 'Mike',
    'age': 21,
    'gender': 'male'
}

result = collection.insert_many([student1, student2])
print(result)
print(result.inserted_ids)

insert_many() 方法返回的類型是 InsertManyResult,調用inserted_ids 屬性能夠獲取插入數據的 _id 列表,運行結果:

<pymongo.results.InsertManyResult object at 0x101dea558>
[ObjectId('5932abf415c2607083d3b2ac'), ObjectId('5932abf415c2607083d3b2ad')]

6. 查詢

插入數據後咱們能夠利用 find_one() 或 find() 方法進行查詢,find_one() 查詢獲得是單個結果,find() 則返回一個生成器對象。

result = collection.find_one({'name': 'Mike'})
print(type(result))
print(result)

在這裏咱們查詢 name 爲 Mike 的數據,它的返回結果是字典類型,運行結果:

<class 'dict'>
{'_id': ObjectId('5932a80115c2606a59e8a049'), 'id': '20170202', 'name': 'Mike', 'age': 21, 'gender': 'male'}

能夠發現它多了一個 _id 屬性,這就是 MongoDB 在插入的過程當中自動添加的。

咱們也能夠直接根據 ObjectId 來查詢,這裏須要使用 bson 庫裏面的 ObjectId。

from bson.objectid import ObjectId

result = collection.find_one({'_id': ObjectId('593278c115c2602667ec6bae')})
print(result)

其查詢結果依然是字典類型,運行結果:

{'_id': ObjectId('593278c115c2602667ec6bae'), 'id': '20170101', 'name': 'Jordan', 'age': 20, 'gender': 'male'}

固然若是查詢結果不存在則會返回 None。

對於多條數據的查詢,咱們可使用 find() 方法,例如在這裏查找年齡爲 20 的數據,示例以下:

results = collection.find({'age': 20})
print(results)
for result in results:
    print(result)

運行結果:

<pymongo.cursor.Cursor object at 0x1032d5128>
{'_id': ObjectId('593278c115c2602667ec6bae'), 'id': '20170101', 'name': 'Jordan', 'age': 20, 'gender': 'male'}
{'_id': ObjectId('593278c815c2602678bb2b8d'), 'id': '20170102', 'name': 'Kevin', 'age': 20, 'gender': 'male'}
{'_id': ObjectId('593278d815c260269d7645a8'), 'id': '20170103', 'name': 'Harden', 'age': 20, 'gender': 'male'}
Python資源分享qun 784758214 ,內有安裝包,PDF,學習視頻,這裏是Python學習者的彙集地,零基礎,進階,都歡迎

返回結果是 Cursor 類型,至關於一個生成器,咱們須要遍歷取到全部的結果,每個結果都是字典類型。

若是要查詢年齡大於 20 的數據,則寫法以下:

results = collection.find({'age': {'$gt': 20}})

在這裏查詢的條件鍵值已經不是單純的數字了,而是一個字典,其鍵名爲比較符號 $gt,意思是大於,鍵值爲 20,這樣即可以查詢出全部年齡大於 20 的數據。

在這裏將比較符號概括以下表:

符號 含義 示例
$lt 小於 {'age': {'$lt': 20}}
$gt 大於 {'age': {'$gt': 20}}
$lte 小於等於 {'age': {'$lte': 20}}
$gte 大於等於 {'age': {'$gte': 20}}
$ne 不等於 {'age': {'$ne': 20}}
$in 在範圍內 {'age': {'$in': [20, 23]}}
$nin 不在範圍內 {'age': {'$nin': [20, 23]}}

另外還能夠進行正則匹配查詢,例如查詢名字以 M 開頭的學生數據,示例以下:

results = collection.find({'name': {'$regex': '^M.*'}})

在這裏使用了 $regex 來指定正則匹配,^M.* 表明以 M 開頭的正則表達式,這樣就能夠查詢全部符合該正則的結果。

在這裏將一些功能符號再歸類以下:

符號 含義 示例 示例含義
$regex 匹配正則 {'name': {'$regex': '^M.*'}} name 以 M開頭
$exists 屬性是否存在 {'name': {'$exists': True}} name 屬性存在
$type 類型判斷 {'age': {'$type': 'int'}} age 的類型爲 int
$mod 數字模操做 {'age': {'$mod': [5, 0]}} 年齡模 5 餘 0
$text 文本查詢 {'$text': {'$search': 'Mike'}} text 類型的屬性中包含 Mike 字符串
$where 高級條件查詢 {'$where': 'obj.fans_count == obj.follows_count'} 自身粉絲數等於關注數

這些操做的更詳細用法在能夠在 MongoDB 官方文檔找到: https://docs.mongodb.com/manu...。

7. 計數

要統計查詢結果有多少條數據,能夠調用 count() 方法,如統計全部數據條數:

count = collection.find().count()
print(count)

或者統計符合某個條件的數據:

count = collection.find({'age': 20}).count()
print(count)

結果是一個數值,即符合條件的數據條數。

8. 排序

能夠調用 sort() 方法,傳入排序的字段及升降序標誌便可,示例以下:

results = collection.find().sort('name', pymongo.ASCENDING)
print([result['name'] for result in results])

運行結果:

['Harden', 'Jordan', 'Kevin', 'Mark', 'Mike']

在這裏咱們調用了 pymongo.ASCENDING 指定升序,若是要降序排列能夠傳入 pymongo.DESCENDING。

9. 偏移

在某些狀況下咱們可能想只取某幾個元素,在這裏能夠利用skip() 方法偏移幾個位置,好比偏移 2,就忽略前 2 個元素,獲得第三個及之後的元素。

results = collection.find().sort('name', pymongo.ASCENDING).skip(2)
print([result['name'] for result in results])

運行結果:

['Kevin', 'Mark', 'Mike']

另外還能夠用 limit() 方法指定要取的結果個數,示例以下:

results = collection.find().sort('name', pymongo.ASCENDING).skip(2).limit(2)
print([result['name'] for result in results])

運行結果:

['Kevin', 'Mark']

若是不加 limit() 本來會返回三個結果,加了限制以後,會截取 2 個結果返回。

值得注意的是,在數據庫數量很是龐大的時候,如千萬、億級別,最好不要使用大的偏移量來查詢數據,極可能會致使內存溢出,可使用相似以下操做來進行查詢:

from bson.objectid import ObjectId
collection.find({'_id': {'$gt': ObjectId('593278c815c2602678bb2b8d')}})

這時記錄好上次查詢的 _id。

10. 更新

對於數據更新可使用 update() 方法,指定更新的條件和更新後的數據便可,例如:

condition = {'name': 'Kevin'}
student = collection.find_one(condition)
student['age'] = 25
result = collection.update(condition, student)
print(result)
Python資源分享qun 784758214 ,內有安裝包,PDF,學習視頻,這裏是Python學習者的彙集地,零基礎,進階,都歡迎

在這裏咱們將 name 爲 Kevin 的數據的年齡進行更新,首先指定查詢條件,而後將數據查詢出來,修改年齡,以後調用 update() 方法將原條件和修改後的數據傳入,便可完成數據的更新。

運行結果:

{'ok': 1, 'nModified': 1, 'n': 1, 'updatedExisting': True}

返回結果是字典形式,ok 即表明執行成功,nModified 表明影響的數據條數。

另外咱們也可使用 $set 操做符對數據進行更新,代碼改寫以下:

result = collection.update(condition, {'$set': student})

這樣能夠只更新 student 字典內存在的字段,若是其原先還有其餘字段則不會更新,也不會刪除。而若是不用 $set 的話則會把以前的數據所有用 student 字典替換,若是本來存在其餘的字段則會被刪除。

另外 update() 方法其實也是官方不推薦使用的方法,在這裏也分了 update_one() 方法和 update_many() 方法,用法更加嚴格,第二個參數須要使用 $ 類型操做符做爲字典的鍵名,咱們用示例感覺一下。

condition = {'name': 'Kevin'}
student = collection.find_one(condition)
student['age'] = 26
result = collection.update_one(condition, {'$set': student})
print(result)
print(result.matched_count, result.modified_count)

在這裏調用了 update_one() 方法,第二個參數不能再直接傳入修改後的字典,而是須要使用 {'$set': student} 這樣的形式,其返回結果是 UpdateResult 類型,而後調用 matched_count 和 modified_count 屬性分別能夠得到匹配的數據條數和影響的數據條數。

運行結果:

<pymongo.results.UpdateResult object at 0x10d17b678>
1 0

咱們再看一個例子:

condition = {'age': {'$gt': 20}}
result = collection.update_one(condition, {'$inc': {'age': 1}})
print(result)
print(result.matched_count, result.modified_count)

在這裏咱們指定查詢條件爲年齡大於 20,而後更新條件爲 {'$inc': {'age': 1}},也就是年齡加 1,執行以後會將第一條符合條件的數據年齡加 1。

運行結果:

<pymongo.results.UpdateResult object at 0x10b8874c8>
1 1

能夠看到匹配條數爲 1 條,影響條數也爲 1 條。

若是調用 update_many() 方法,則會將全部符合條件的數據都更新,示例以下:

condition = {'age': {'$gt': 20}}
result = collection.update_many(condition, {'$inc': {'age': 1}})
print(result)
print(result.matched_count, result.modified_count)

這時候匹配條數就再也不爲 1 條了,運行結果以下:

<pymongo.results.UpdateResult object at 0x10c6384c8>
3 3

能夠看到這時全部匹配到的數據都會被更新。

11. 刪除

刪除操做比較簡單,直接調用 remove() 方法指定刪除的條件便可,符合條件的全部數據均會被刪除,示例以下:

result = collection.remove({'name': 'Kevin'})
print(result)

運行結果:

{'ok': 1, 'n': 1}

另外依然存在兩個新的推薦方法,delete_one() 和 delete_many() 方法,示例以下:

result = collection.delete_one({'name': 'Kevin'})
print(result)
print(result.deleted_count)
result = collection.delete_many({'age': {'$lt': 25}})
print(result.deleted_count)

運行結果:

<pymongo.results.DeleteResult object at 0x10e6ba4c8>
1
4
Python資源分享qun 784758214 ,內有安裝包,PDF,學習視頻,這裏是Python學習者的彙集地,零基礎,進階,都歡迎

delete_one() 即刪除第一條符合條件的數據,delete_many() 即刪除全部符合條件的數據,返回結果是 DeleteResult 類型,能夠調用 deleted_count 屬性獲取刪除的數據條數。

12. 更多

另外 PyMongo 還提供了一些組合方法,如find_one_and_delete()、find_one_and_replace()、find_one_and_update(),就是查找後刪除、替換、更新操做,用法與上述方法基本一致。

另外還能夠對索引進行操做,如 create_index()、create_indexes()、drop_index() 等。

13. 結語

本節講解了 PyMongo 操做 MongoDB 進行數據增刪改查的方法,在後文咱們會在實戰案例中應用這些操做進行數據存儲。

相關文章
相關標籤/搜索