在Python 2中,鏈接MySQL的庫大可能是使用MySQLdb,可是此庫的官方並不支持Python 3,因此這裏推薦使用的庫是PyMySQL。
mysql
本節中,咱們就來說解使用PyMySQL操做MySQL數據庫的方法。sql
在開始以前,請確保已經安裝好了MySQL數據庫並保證它能正常運行,並且須要安裝好PyMySQL庫。
數據庫
這裏,首先嚐試鏈接一下數據庫。假設當前的MySQL運行在本地,用戶名爲root,密碼爲123456,運行端口爲3306。這裏利用PyMySQL先鏈接MySQL,而後建立一個新的數據庫,名字叫做spiders,代碼以下:
數組
import pymysql
db = pymysql.connect(host='localhost',user='root', password='123456', port=3306)
cursor = db.cursor()
cursor.execute('SELECT VERSION()')
data = cursor.fetchone()
print('Database version:', data)
cursor.execute("CREATE DATABASE spiders DEFAULT CHARACTER SET utf8")
db.close()
運行結果以下:併發
Database version: ('5.6.22',)
這裏經過PyMySQL的connect()
方法聲明一個MySQL鏈接對象db
,此時須要傳入MySQL運行的host
(即IP)。因爲MySQL在本地運行,因此傳入的是localhost
。若是MySQL在遠程運行,則傳入其公網IP地址。後續的參數user
即用戶名,password
即密碼,port
即端口(默認爲3306
)。app
鏈接成功後,須要再調用cursor()
方法得到MySQL的操做遊標,利用遊標來執行SQL語句。這裏咱們執行了兩句SQL,直接用execute()
方法執行便可。第一句SQL用於得到MySQL的當前版本,而後調用fetchone()
方法得到第一條數據,也就獲得了版本號。第二句SQL執行建立數據庫的操做,數據庫名叫做spiders,默認編碼爲UTF-8。因爲該語句不是查詢語句,因此直接執行後就成功建立了數據庫spiders。接着,再利用這個數據庫進行後續的操做。ide
通常來講,建立數據庫的操做只須要執行一次就行了。固然,咱們也能夠手動建立數據庫。之後,咱們的操做都在spiders數據庫上執行。
fetch
建立數據庫後,在鏈接時須要額外指定一個參數db
。編碼
接下來,新建立一個數據表students,此時執行建立表的SQL語句便可。這裏指定3個字段,結構以下所示。atom
字段名 | 含義 | 類型 |
---|---|---|
id |
學號 | varchar |
name |
姓名 | varchar |
age |
年齡 | int |
建立該表的示例代碼以下:
import pymysql
db = pymysql.connect(host='localhost', user='root', password='123456', port=3306, db='spiders')
cursor = db.cursor()
sql = 'CREATE TABLE IF NOT EXISTS students (id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, age INT NOT NULL, PRIMARY KEY (id))'
cursor.execute(sql)
db.close()
運行以後,咱們便建立了一個名爲students的數據表。
固然,爲了演示,這裏只指定了最簡單的幾個字段。實際上,在爬蟲過程當中,咱們會根據爬取結果設計特定的字段。
下一步就是向數據庫中插入數據了。例如,這裏爬取了一個學生信息,學號爲20120001,名字爲Bob,年齡爲20,那麼如何將該條數據插入數據庫呢?示例代碼以下:
import pymysql
id = '20120001'
user = 'Bob'
age = 20
db = pymysql.connect(host='localhost', user='root', password='123456', port=3306, db='spiders')
cursor = db.cursor()
sql = 'INSERT INTO students(id, name, age) values(%s, %s, %s)'
try:
cursor.execute(sql, (id, user, age))
db.commit()
except:
db.rollback()
db.close()
這裏首先構造了一個SQL語句,其Value
值沒有用字符串拼接的方式來構造,如:
sql = 'INSERT INTO students(id, name, age) values(' + id + ', ' + name + ', ' + age + ')'
這樣的寫法煩瑣並且不直觀,因此咱們選擇直接用格式化符%s
來實現。有幾個Value
寫幾個%s
,咱們只須要在execute()
方法的第一個參數傳入該SQL語句,Value
值用統一的元組傳過來就行了。這樣的寫法既能夠避免字符串拼接的麻煩,又能夠避免引號衝突的問題。
以後值得注意的是,須要執行db
對象的commit()
方法纔可實現數據插入,這個方法纔是真正將語句提交到數據庫執行的方法。對於數據插入、更新、刪除操做,都須要調用該方法才能生效。
接下來,咱們加了一層異常處理。若是執行失敗,則調用rollback()
執行數據回滾,至關於什麼都沒有發生過。
這裏涉及事務的問題。事務機制能夠確保數據的一致性,也就是這件事要麼發生了,要麼沒有發生。好比插入一條數據,不會存在插入一半的狀況,要麼所有插入,要麼都不插入,這就是事務的原子性。另外,事務還有3個屬性——一致性、隔離性和持久性。這4個屬性一般稱爲ACID特性,具體以下表所示。
屬性 | 解釋 |
---|---|
原子性(atomicity) | 事務是一個不可分割的工做單位,事務中包括的諸操做要麼都作,要麼都不作 |
一致性(consistency) | 事務必須使數據庫從一個一致性狀態變到另外一個一致性狀態。一致性與原子性是密切相關的 |
隔離性(isolation) | 一個事務的執行不能被其餘事務干擾,即一個事務內部的操做及使用的數據對併發的其餘事務是隔離的,併發執行的各個事務之間不能互相干擾 |
持久性(durability) | 持續性也稱永久性(permanence),指一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。接下來的其餘操做或故障不該該對其有任何影響 |
插入、更新和刪除操做都是對數據庫進行更改的操做,而更改操做都必須爲一個事務,因此這些操做的標準寫法就是:
try:
cursor.execute(sql)
db.commit()
except:
db.rollback()
這樣即可以保證數據的一致性。這裏的commit()
和rollback()
方法就爲事務的實現提供了支持。
上面數據插入的操做是經過構造SQL語句實現的,可是很明顯,這有一個極其不方便的地方,好比忽然增長了性別字段gender
,此時SQL語句就須要改爲:
INSERT INTO students(id, name, age, gender) values(%s, %s, %s, %s)
相應的元組參數則須要改爲:
(id, name, age, gender)
這顯然不是咱們想要的。在不少狀況下,咱們要達到的效果是插入方法無需改動,作成一個通用方法,只須要傳入一個動態變化的字典就行了。好比,構造這樣一個字典:
{
'id': '20120001',
'name': 'Bob',
'age': 20
}
而後SQL語句會根據字典動態構造,元組也動態構造,這樣才能實現通用的插入方法。因此,這裏咱們須要改寫一下插入方法:
data = {
'id': '20120001',
'name': 'Bob',
'age': 20
}
table = 'students'
keys = ', '.join(data.keys())
values = ', '.join(['%s'] * len(data))
sql = 'INSERT INTO {table}({keys}) VALUES ({values})'.format(table=table, keys=keys, values=values)
try:
if cursor.execute(sql, tuple(data.values())):
print('Successful')
db.commit()
except:
print('Failed')
db.rollback()
db.close()
這裏咱們傳入的數據是字典,並將其定義爲data
變量。表名也定義成變量table
。接下來,就須要構造一個動態的SQL語句了。
首先,須要構造插入的字段id
、name
和age
。這裏只須要將data
的鍵名拿過來,而後用逗號分隔便可。因此', '.join(data.keys())
的結果就是id, name, age
,而後須要構造多個%s
看成佔位符,有幾個字段構造幾個便可。好比,這裏有三個字段,就須要構造%s, %s, %s
。這裏首先定義了長度爲1的數組['%s']
,而後用乘法將其擴充爲['%s', '%s', '%s']
,再調用join()
方法,最終變成%s, %s, %s
。最後,咱們再利用字符串的format()
方法將表名、字段名和佔位符構造出來。最終的SQL語句就被動態構形成了:
INSERT INTO students(id, name, age) VALUES (%s, %s, %s)
最後,爲execute()
方法的第一個參數傳入sql
變量,第二個參數傳入data
的鍵值構造的元組,就能夠成功插入數據了。
如此以來,咱們便實現了傳入一個字典來插入數據的方法,不須要再去修改SQL語句和插入操做了。
數據更新操做實際上也是執行SQL語句,最簡單的方式就是構造一個SQL語句,而後執行:
sql = 'UPDATE students SET age = %s WHERE name = %s'
try:
cursor.execute(sql, (25, 'Bob'))
db.commit()
except:
db.rollback()
db.close()
這裏一樣用佔位符的方式構造SQL,而後執行execute()
方法,傳入元組形式的參數,一樣執行commit()
方法執行操做。若是要作簡單的數據更新的話,徹底可使用此方法。
可是在實際的數據抓取過程當中,大部分狀況下須要插入數據,可是咱們關心的是會不會出現重複數據,若是出現了,咱們但願更新數據而不是重複保存一次。另外,就像前面所說的動態構造SQL的問題,因此這裏能夠再實現一種去重的方法,若是數據存在,則更新數據;若是數據不存在,則插入數據。另外,這種作法支持靈活的字典傳值。示例以下:
data = {
'id': '20120001',
'name': 'Bob',
'age': 21
}
table = 'students'
keys = ', '.join(data.keys())
values = ', '.join(['%s'] * len(data))
sql = 'INSERT INTO {table}({keys}) VALUES ({values}) ON DUPLICATE KEY UPDATE'.format(table=table, keys=keys, values=values)
update = ','.join([" {key} = %s".format(key=key) for key in data])
sql += update
try:
if cursor.execute(sql, tuple(data.values())*2):
print('Successful')
db.commit()
except:
print('Failed')
db.rollback()
db.close()
這裏構造的SQL語句實際上是插入語句,可是咱們在後面加了ON DUPLICATE KEY UPDATE
。這行代碼的意思是若是主鍵已經存在,就執行更新操做。好比,咱們傳入的數據id
仍然爲20120001
,可是年齡有所變化,由20變成了21,此時這條數據不會被插入,而是直接更新id
爲20120001
的數據。完整的SQL構造出來是這樣的:
INSERT INTO students(id, name, age) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE id = %s, name = %s, age = %s
這裏就變成了6個%s
。因此在後面的execute()
方法的第二個參數元組就須要乘以2變成原來的2倍。
如此一來,咱們就能夠實現主鍵不存在便插入數據,存在則更新數據的功能了。
刪除操做相對簡單,直接使用DELETE
語句便可,只是須要指定要刪除的目標表名和刪除條件,並且仍然須要使用db
的commit()
方法才能生效。示例以下:
table = 'students'
condition = 'age > 20'
sql = 'DELETE FROM {table} WHERE {condition}'.format(table=table, condition=condition)
try:
cursor.execute(sql)
db.commit()
except:
db.rollback()
db.close()
由於刪除條件有多種多樣,運算符有大於、小於、等於、LIKE
等,條件鏈接符有AND
、OR
等,因此再也不繼續構造複雜的判斷條件。這裏直接將條件看成字符串來傳遞,以實現刪除操做。
說完插入、修改和刪除等操做,還剩下很是重要的一個操做,那就是查詢。查詢會用到SELECT
語句,示例以下:
sql = 'SELECT * FROM students WHERE age >= 20'
try:
cursor.execute(sql)
print('Count:', cursor.rowcount)
one = cursor.fetchone()
print('One:', one)
results = cursor.fetchall()
print('Results:', results)
print('Results Type:', type(results))
for row in results:
print(row)
except:
print('Error')
運行結果以下:
Count: 4
One: ('20120001', 'Bob', 25)
Results: (('20120011', 'Mary', 21), ('20120012', 'Mike', 20), ('20120013', 'James', 22))
Results Type: <class 'tuple'>
('20120011', 'Mary', 21)
('20120012', 'Mike', 20)
('20120013', 'James', 22)
這裏咱們構造了一條SQL語句,將年齡20歲及以上的學生查詢出來,而後將其傳給execute()
方法。注意,這裏再也不須要db
的commit()
方法。接着,調用cursor
的rowcount
屬性獲取查詢結果的條數,當前示例中是4條。
而後咱們調用了fetchone()
方法,這個方法能夠獲取結果的第一條數據,返回結果是元組形式,元組的元素順序跟字段一一對應,即第一個元素就是第一個字段id
,第二個元素就是第二個字段name
,以此類推。隨後,咱們又調用了fetchall()
方法,它能夠獲得結果的全部數據。而後將其結果和類型打印出來,它是二重元組,每一個元素都是一條記錄,咱們將其遍歷輸出出來。
可是這裏須要注意一個問題,這裏顯示的是3條數據而不是4條,fetchall()
方法不是獲取全部數據嗎?這是由於它的內部實現有一個偏移指針用來指向查詢結果,最開始偏移指針指向第一條數據,取一次以後,指針偏移到下一條數據,這樣再取的話,就會取到下一條數據了。咱們最初調用了一次fetchone()
方法,這樣結果的偏移指針就指向下一條數據,fetchall()
方法返回的是偏移指針指向的數據一直到結束的全部數據,因此該方法獲取的結果就只剩3個了。
此外,咱們還能夠用while
循環加fetchone()
方法來獲取全部數據,而不是用fetchall()
所有一塊兒獲取出來。fetchall()
會將結果以元組形式所有返回,若是數據量很大,那麼佔用的開銷會很是高。所以,推薦使用以下方法來逐條取數據:
sql = 'SELECT * FROM students WHERE age >= 20'
try:
cursor.execute(sql)
print('Count:', cursor.rowcount)
row = cursor.fetchone()
while row:
print('Row:', row)
row = cursor.fetchone()
except:
print('Error')
這樣每循環一次,指針就會偏移一條數據,隨用隨取,簡單高效。
本節中,咱們介紹瞭如何使用PyMySQL操做MySQL數據庫以及一些SQL語句的構造方法。