Python操做MySQL存儲,這些你都會了嗎?

在Python 2中,鏈接MySQL的庫大可能是使用MySQLdb,可是此庫的官方並不支持Python 3,因此這裏推薦使用的庫是PyMySQL。
mysql

本節中,咱們就來說解使用PyMySQL操做MySQL數據庫的方法。sql

1. 準備工做

在開始以前,請確保已經安裝好了MySQL數據庫並保證它能正常運行,並且須要安裝好PyMySQL庫。
數據庫

2. 鏈接數據庫

這裏,首先嚐試鏈接一下數據庫。假設當前的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

3. 建立表

通常來講,建立數據庫的操做只須要執行一次就行了。固然,咱們也能夠手動建立數據庫。之後,咱們的操做都在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的數據表。

固然,爲了演示,這裏只指定了最簡單的幾個字段。實際上,在爬蟲過程當中,咱們會根據爬取結果設計特定的字段。

4. 插入數據

下一步就是向數據庫中插入數據了。例如,這裏爬取了一個學生信息,學號爲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語句了。

首先,須要構造插入的字段idnameage。這裏只須要將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語句和插入操做了。

5. 更新數據

數據更新操做實際上也是執行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,此時這條數據不會被插入,而是直接更新id20120001的數據。完整的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倍。

如此一來,咱們就能夠實現主鍵不存在便插入數據,存在則更新數據的功能了。

6. 刪除數據

刪除操做相對簡單,直接使用DELETE語句便可,只是須要指定要刪除的目標表名和刪除條件,並且仍然須要使用dbcommit()方法才能生效。示例以下:

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等,條件鏈接符有ANDOR等,因此再也不繼續構造複雜的判斷條件。這裏直接將條件看成字符串來傳遞,以實現刪除操做。

7. 查詢數據

說完插入、修改和刪除等操做,還剩下很是重要的一個操做,那就是查詢。查詢會用到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()方法。注意,這裏再也不須要dbcommit()方法。接着,調用cursorrowcount屬性獲取查詢結果的條數,當前示例中是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語句的構造方法。

相關文章
相關標籤/搜索