Class 15 - 2 关系型数据库存储 - MySQL存储

关系型数据库是基于关系模型的数据库,而关系模型是经过二维表来保存的,因此它的存储方式 就是行列组成的表,每一列是一个字段, 每一行是一条记录。 表能够看做某个实体的集合,而实体之 间存在联系,这就须要表与表之间的关联关系来体现, 如主键外键的关联关系。 多个表组成一个数据 库,也就是关系型数据库。
关系型数据库有多种,如 SQLite、 MySQL、 Oracle、 SQL Server、 DB2 等。mysql

  1. MySQL 存储
    • Python 2中,链接 MySQL 的库大可能是使用 MySQLdb,可是此库的官方不支持 Python 3 ,因此推荐使用的库是 PyMySQL。
    1. 连接数据库,
      • 首先链接数据库,假设当前的 MySQL 运行在本地,用户名为root ,密码为123456, 行端口为3306。 利用 PyMySQL 先连 MySQL,再建立一个新数据库,名字叫做 spiders。代码:
        import pymysql
        db = pymysql.connect(host='localhost', user='root' , password='mysql', port=3306)
        cursor = db.cursor()
        cursor. execute('SELECT VERSION()')
        data = cursor. fetchone()
        print('Database version:', data)
        cursor.execute ("CREATE DATABASE spiders DEFAULT CHARACTER SET utf-8")
        db.close()
        输出:
        ('Database version:', ('5.7.24-0ubuntu0.16.04.1',))

        经过 PyMySQL的connect()方法声明一个 MySQL 链接对象db,此时须要传人 MySQL 运行 host(即 IP)。因为 MySQL 在本地运行,因此传人的是 localhost。若是 MySQL 在远程运行,则传入其公网 IP 地址。后续的参数 user 即用户名, password 即密码, port 即端口(默认为 3306)。  链接成功后,须要再调用 cursor ()方法得到 MySQL 操做游标利用游标来执行 SQL 语句。这里执行了两句 SQL,直接用execute()方法执行便可。第一句 SQL 用于得到 MySQL 的当前版本,而后调用 fetchone()方法得到第一条数据,也就获得了版本号。第二句 SQL 执行建立数据库的操做,数据库名叫做 piders 默认编码为 UTF-8。因为该语句不是查询语句,因此直接执行后就成功建立了数据库 spiders 。接着,再利用这个数据库进行后续的操做。sql

    2. 建立表
      • 通常来讲,建立数据库的操做只须要执行一次。也能够手动建立数据库。之后的操做都在 piders 数据库上执行。
      • 建立数据库后,在链接时须要额外指定一个参数 db。
      • 新建立一个数据表 students,此时执行建立表的 SQL 语句便可。 这里指定3个字段(字段名,含义,类型)。示例:
        import pymysql
        db = pymysql.connect(host='localhost',user='root',password='mysql',port=3306,db='spiders')
        cursor= db.cursor()
        sql= 'CREATE TABLE IF NOT EXISTS student (id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, age INT NOT NULL, PRIMARY KEY(id))'
        cursor.execute(sql)
        db.close()

        运行后,咱们便建立了一个名为 students 的数据表。   在爬虫过程当中,会根据爬取结 果设计特定的字段  数据库

    3. 插入数据
      • 爬取了一个学生信息,学号: 20120001 ,名字: Bob,年龄: 20,插入这条数据进数据库。示例代码以下:ubuntu

        import pymysql
        id= '20120001'
        user = 'BOb'
        age = 20
        db = pymysql.connect(host='localhost',user='root',password='mysql',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 值用统一的元组传过来。这样的写法既能够避免字符串拼接的麻烦,又能够避免引号冲突的问题。ide

        注意:须要执行 db 象的 commit()方法才可实现数据插入,这个方法才是真正将 语句提交到数据库执行的方法。对于数据插入、更新、删除操做,都须要调用该方法才能生效。接下来,加一层异常处理。若是执行失败,则调用 rollback()执行数据回滚,至关于什么都没有发生过。 fetch

      • 涉及事务的问题。事务机制能够确保数据的一致性, 就是这件事要么发生了,要么没有发 生。 好比插入一条数据,不会存在插入一半的状况 要么所有插入,要么都不插入,这就是事务的原子性。另外,事务还有3个属性一一 一致性、隔离性和持久性。这4个属性一般称为 ACID 特性.
        • 事物的4个属性:
        • 插入、更新和删除操做都是对数据库进行更改的操做,而更改操做都必须为一个事务,这些操做的标准写法: 
          try:
              cursor.execute(sql)
              db.commit()
          except:
              db.rollback()

          这样即可以保证数据的一致性。这里的 commit()和 rollback()方法就为事务的实现提供支持。编码

        • 数据插入的操做是经过构造 SQL 语句实现。要达到的效果是插入方法无需改动,作成 通用方法,只需传入一个动态变化字典就好。例:spa

          {
              'id': '20120001',
              'name': 'Bob',
              'age': 20
          }

          SQL 语句会根据字典动态构造,元组也动态构造,这样才能实现通用插入方法。因此,须要改写插入方法:设计

          import pymysql
          
          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)
          db = pymysql.connect(host='localhost',user='root',password='mysql',port=3306,db='spiders')
          cursor = db.cursor()
          
          try:
              if cursor.execute(sql,tuple(data.values())):
                  print('Successful')
                  db.commit()
          except:
              print('Failed')
              db.rollback()
          
          db.close()

          注意:若是前面没有建立students表格,将会插入失败。这里传人的数据是字典,并将其定义为 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 语句和插入操做。

    4. 更新数据(此处不熟)

      • 数据更新操做也是执行 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() 方法执行操做。简单的数据更新,彻底可使用此方法

        可是在实际的数据抓取过程当中,大部分状况下须要插入数据,为避免出现1+1类型数据重复,动态构造 SQL 的问题,能够再实现一种去重的方法,若是数据存在, 则更新数据;若是数据不存在,则插 入数据。这种作法支持灵活的字典传值。示例:

        data = {
            'id': '20120001',
            'name': 'Bob',
            'age': 24
        }
        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 id=%s, name=%s,age=%s

        这里变成了 6个%s。因此在后面的 execute()方法的第二个参数元组就须要乘以 2 变成原来的 2倍。此时,就能够实现主键不存在便插入数据,存在则更新数据的功能了。

    5. 删除数据

      • 直接使用 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.roolback()
        
        db.close()

        删除条件有多种多样,运算符有大于、小于、等于、LIKE 等,条件链接符有 AND、OR 等。 这里直接将条件看成字符串来传递,以实现删除操做。

    6. 查询数据

      • 查询会用到 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: 3
        One: ('20120001', 'Bob', 20)
        results: (('20120012', 'Mike', 21), ('20120013', 'James', 22))
        results Type: <class 'tuple'>
        ('20120012', 'Mike', 21)
        ('20120013', 'James', 22)
        View Code

        这里构造了一条 SQL 语句,将年龄 20 岁及以上的学生查询出来,将其传给 execute() 方法。注意,这里再也不需 db 的 commit()方法。接着,调用 cursor rowcount 属性获取查询结果的条数。

        而后调用了 fetchone ()方法,这个方法能够获取结果的第一条数据,返回结果是元组形式,元组的元素顺序跟字段一一对应,即第一个元素就是第一个字段 id 第二个元素就是第二个字段 name 以此类推。 随后,咱们又调用了 fetchall()方法,它能够获得结果的全部数据。 而后将其结果和类型打印出来,它是二重元组,每一个元素都是一条记录,咱们将其遍历输出出来。      

        注意:显示的是 n 条数据而不是 n+1 条, fetchall()方法的内部实现有一个偏移指针用来指向查询结果,最开始偏移指针指向第一条数据,取一次以后,指针偏移到下一条数据,这样再取的话,就会取到下一条数据了。咱们最初调用了 一次 fetchone()方法,这样结果的偏移指针就指向下一条数据, fetchall()方法返回的是偏移指针指 向的数据一直到结束的全部数据,因此该方法获取的结果就只剩 n 个了。

        还能够用 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')

        每循环一次,指针就会偏移一条数据,随用随取,简单高效。 

相关文章
相关标签/搜索