关于python:爬虫系列使用-MySQL-存储数据

38次阅读

共计 7447 个字符,预计需要花费 19 分钟才能阅读完成。

上一篇文章咱们解说了爬虫如何存储 CSV 文件,这篇文章,咱们解说如何将采集到的数据保留到 MySQL 数据库中。

MySQL 是目前最受欢迎的开源关系型数据库管理系统。一个开源我的项目具备如此之竞争力切实是令人意外,它的风行水平正在一直地靠近两外两个闭源的商业数据库系统:微软的 SQL Server 和甲骨文的 Oracle 数据库(MySQL 在 2010 年被甲骨文收买)。

它的风行水平名符其实。对于大多数利用来说,MySQL 都是不二抉择。他是一种非常灵活、稳固、功能齐全的 DBMS,许多顶级的网站都在应用它:Youtube、Twitter 和 Facebook 等。

因为它受众宽泛,收费,开箱即用,所以它是网络数据采集我的项目中罕用的数据库,这篇文章咱们介绍如何通过 MySQL 存储采集到的数据。

装置 MySQL

如果你第一次接触 MySQL,可能会感觉有点麻烦。其实,装置办法和装置其他软件一样简略。归根结底,MySQL 就是由一系列数据文件形成的,存储在你远端服务器或者本地电脑上,外面蕴含了数据库存储的所有信息。

Windows 装置 MySQL、Ubuntu 装置 MySQL、MAC 装置 MySQL 具体步骤在此:全平台装置 MySQL

在此不做过多阐明,依照视频操作就能够了。

根本命令

MySQL 服务器启动之后,有很多种办法能够与数据库服务器交互。因为有很多工具是图形界面,所以你能够不必 MySQL 的命令行(或是很少用命令行)也能治理数据库。像 phpMyAdmin 和 MySQL Workbench 这类工具能够很容易地实现数据库查看、排序和新建等工作。然而,把握命令行操作数据库还是很重要的。

除了用户自定义变量名,MySQL 是不辨别大小写的。例如,SELECT 和 select 是一样的,不过习惯上写 MySQL 语句的时候所有的 MySQL 关键词都用大写。大多数开发者还喜爱用小写字母示意数据库和数据表的名称。

首先登入 MySQL 数据库的时候,外面是没有数据库存放数据的。咱们须要创立一个数据库:

CREATE DATABASE scraping_article DEFAULT CHARACTER SET UTF8 COLLATE UTF8_GENERAL_CI;

因为每个 MySQL 实例能够有多个数据库,所以应用某个数据库之前须要指定数据库的名称:

USE scraping_article

从当初开始(直到敞开 MySQL 链接或切换到另一个数据库之前),所有的命令都运行在这个新的“scraping_article”数据库外面。

所有操作都看起来非常简单。那么在数据库外面新建表的操作方法也应该相似吧?咱们在库外面新建一张表来存储采集的网页文章数据:

CREATE TABLE articles;

结果显示谬误:

ERROR 1113 (42000): A table must have at least 1 column

和数据库不同,MySQL 数据表必须有一列,否则不能创立。为了在 MySQL 里定义字段(数据列),咱们还必须在 CREATE TABLE <tablename> 语句前面,把字段定义放进一个带括号的、外部由逗号分隔的列表中:

create table articles
(
    id                   int auto_increment
        primary key,
    title                varchar(64)                        null,
    body                 text                               null,
    summary              varchar(256)                       null,
    body_html            text                               null,
    create_time          datetime default CURRENT_TIMESTAMP null,
    time_updated         datetime                           null,
    link_text            varchar(128)                       null
);

每个字段定义由三局部组成:

  • 名称(id、title、body 等)
  • 数据类型(INT、VARCHAR、TEXT)
  • 其余可选属性(NOT NULL AUTO_INCREMENT)

在字段定义列表的最初,还要定义一个“主键”(key)。MySQL 用这个主键来组织表的内容,便于前面疾速查问。在当前的文章中,我将介绍如果通过这些主键以进步数据库的查问速度,然而当初,咱们应用表的 id 列作为主键就能够。

语句执行之后,咱们能够应用 DESCRIBE 查看数据表的构造:

+--------------+--------------+------+-----+-------------------+----------------+
| Field        | Type         | Null | Key | Default           | Extra          |
+--------------+--------------+------+-----+-------------------+----------------+
| id           | int(11)      | NO   | PRI | NULL              | auto_increment |
| title        | varchar(64)  | YES  |     | NULL              |                |
| body         | text         | YES  |     | NULL              |                |
| summary      | varchar(256) | YES  |     | NULL              |                |
| body_html    | text         | YES  |     | NULL              |                |
| create_time  | datetime     | YES  |     | CURRENT_TIMESTAMP |                |
| time_updated | datetime     | YES  |     | NULL              |                |
| link_text    | varchar(128) | YES  |     | NULL              |                |
+--------------+--------------+------+-----+-------------------+----------------+
8 rows in set (0.03 sec)

当初这张表是一张空表,咱们插入一下数据看看,如下所示:

INSERT INTO articles(title,body,summary,body_html,link_text) VALUES ("Test page title","Test page body.","Test page summary.","<p>Test page body.</p>","test-page");

这里须要咱们留神,尽管 articles 表有 8 个字段(id,title,body,summary,body_html,create_time,time_update,link_text),但实际上咱们这里只插入 5 个字段(title,body,summary,body_html,link_text)的数据即可。因为 id 字段是主动递增的(每次插入数据时 MySQL 默认减少 1),通常不必解决。另外 create_time 字段的类型是 current_timestamp,默认插入的是工夫戳。

当然咱们也能够自定义字段内容插入数据:

INSERT INTO articles(id,title,body,summary,body_html,create_time,link_text) VALUES (4,"Test page title","Test page body.","Test page summary.","<p>Test page body.</p>","2021-11-20 15:51:45","test-page");

只有你定义的整数在数据表的 id 字段里没有,他就能够插入到数据表。然而,这么做十分不好;除非万不得已(比方程序中漏了一行数据),否则让 MySQL 本人解决 id 和 timestamp 字段。

当初表外面有一些数据了,咱们能够通过很多办法查问这些数据。上面是几个 SELECT 语句的示例:

SELECT * FROM articles WHERE id=1;

这条语句通知 MySQL,“从 articles 表中把所有 id 等于 2 的数据全副筛选进去”。这个星号(*)是通配符,示意所有字段,这行语句会把满足条件(where id=1)的所有字段内容都显示进去。如果 id 这里没有任何一行等于 1,就会返回一个空集。例如,上面这个不辨别大小写的查问,会返回 title 字段里蕴含“test”的所有行(% 符号示意 MySQL 字符串通配符)的所有字段:

SELECT * FROM articles WHERE title LIKE "%test%"; 

然而如果你有很多字段,而你只想返回局部字段怎么办?你能够不要用星号,而是应用上面的形式:

SELECT title, body FROM articles WHERE body LIKE "%test%";

这样就只会返回 body 内容蕴含“test”所有行的 title 和 body 两个字段了。

DELETE 语句语法与 SELECT 语句相似:

DELETE FROM articles WHERE id=1;

因为数据库的数据删除不能复原,所以在执行 DELETE 语句之前,倡议应用 SELECT 确认一下须要删除的数据(下面的删除语句能够应用 SELECT * FROM articles WHERE id=1; 查看),而后把 SELECT * 换成 DELETE 就能够了,这会是一个好习惯。很多程序员都有过一些 DELETE 误操作的伤心往事,还有一些恐怖的故事就是有人慌乱中忘了在语句中放 WHERE,后果把所有的客户数据都删除了。别让这事件产生在你身上!

还有一个须要介绍的是 UPDATE 语句:

UPDATE articles SET title="A new title", body="Some new body." WHERE id=4;

以上只是应用了最根本的 MySQL 语句,做一些简略的数据查问、创立和更新等工作。

与 Python 整合

Python 没有内置的 MySQL 反对工具。不过,有很多开源的能够用来与 MySQL 做交互,Python 2.x 和 Python 3.x 版本都反对。最有名的一个就是 PyMySQL。

咱们能够应用 pip 装置,执行如下命令:

python3 -m pip install PyMySQL

装置实现咱们就能够应用 PyMySQL 包了。如果你的 MySQL 服务器处于运行状态,应该就能够胜利地执行上面命令:

import pymysql
import os
from dotenv import load_dotenv


class DataSaveToMySQL(object):
    def __init__(self):
        # loading env config file
        dotenv_path = os.path.join(os.getcwd(), '.env')
        if os.path.exists(dotenv_path):
            load_dotenv(dotenv_path)

    conn = pymysql.connect(host=os.environ.get('MYSQL_HOST'), port=os.environ.get('MYSQL_PORT'),
                           user=os.environ.get('MYSQL_USER'), password=os.environ.get('MYSQL_PASSWORD'),
                           db=os.environ.get('MYSQL_DATABASES'))
    cur = conn.cursor()
    cur.execute("SELECT * FROM articles WHERE id=4;")
    print(cur.fetchone())
    cur.close()
    conn.close()

这段程序有两个对象:连贯对象(conn)和游标对象(cur)。

连贯 / 游标模式是数据库编程中罕用的模式,在刚刚接触数据库的时候,有的时候很难辨别这两种模式的不同。连贯模式除了连贯数据库之外,还要发送数据库信息,解决回滚操作(当一个查问或一组查问被中断时,数据库就须要回到初始状态,个别应用事务实现状态回滚),创立新的游标等等。

而一个连贯能够有很多个游标,一个游标追踪一种状态(state)信息,比方追踪数据库的应用状态。如果有多个数据库,且须要向所有数据库写内容,就须要多个游标来解决。游标还能够蕴含最初一次查问执行后果。通过调用游标函数,比方 cur.fetchone(),能够获取查问后果。

用完连贯和游标之后千万记得敞开它们。如果不敞开就会导致连贯泄露(connection leak),造成一种敞开连贯景象,即连贯曾经不再应用,但数据库却不能敞开,因为数据库不确定你还要不要持续应用它。这种景象始终会消耗数据库资源,所以用完数据库之后记得敞开连贯!

刚开始的时候,你想做的事件就是把采集的数据保留到数据库。咱们持续采集博客文章的例子来演示如何实现数据存储。

import pymysql
import os
from dotenv import load_dotenv

from config import logger_config
from utils import connection_util


class DataSaveToMySQL(object):
    def __init__(self):
        # loading env config file
        dotenv_path = os.path.join(os.getcwd(), '.env')
        if os.path.exists(dotenv_path):
            load_dotenv(dotenv_path)
        # MySQL config
        self._host = os.environ.get('MYSQL_HOST')
        self._port = int(os.environ.get('MYSQL_PORT'))
        self._user = os.environ.get('MYSQL_USER')
        self._password = os.environ.get('MYSQL_PASSWORD')
        self._db = os.environ.get('MYSQL_DATABASES')

        self._target_url = 'https://www.scrapingbee.com/blog/'
        self._baseUrl = 'https://www.scrapingbee.com'
        self._init_connection = connection_util.ProcessConnection()
        logging_name = 'store_mysql'
        init_logging = logger_config.LoggingConfig()
        self._logging = init_logging.init_logging(logging_name)

    def scrape_data(self):
        get_content = self._init_connection.init_connection(self._target_url)
        if get_content:
            parent = get_content.findAll("section", {"class": "section-sm"})[0]
            get_row = parent.findAll("div", {"class": "col-lg-12 mb-5 mb-lg-0"})[0]
            get_child_item = get_row.findAll("div", {"class": "col-md-4 mb-4"})
            for item in get_child_item:
                # 获取题目文字
                get_title = item.find("a", {"class": "h5 d-block mb-3 post-title"}).get_text()
                # 获取公布工夫
                get_release_date = item.find("div", {"class": "mb-3 mt-2"}).findAll("span")[1].get_text()
                # 获取文章形容
                get_description = item.find("p", {"class": "card-text post-description"}).get_text()
                self.article_save_mysql(title=get_title, description=get_description, release_date=get_release_date)
        else:
            self._logging.warning('未获取到文章任何内容,请查看!')


    def article_save_mysql(self, title, description, release_date):
        connection = pymysql.connect(host=self._host, port=self._port, user=self._user, password=self._password,
                                     db=self._db, charset='utf-8')
        with connection.cursor() as cursor:
            # Create a new record
            sql = "INSERT INTO articles (title,summary,create_time) VALUES (%s,%s,%s);"
            cursor.execute(sql, (title, description, release_date))

        # connection is not autocommit by default. So you must commit to save
        # your changes.
        connection.commit()

这里有几点须要留神:首先,charset='utf-8' 要减少到连贯字符串里。这是让 conn 把所有发送数据库的信息都当成 utf-8 编码格局(当然,前提是数据库默认编码设置成 UTF-8)。

而后须要留神的是 article_save_mysql 函数。它有 3 个参数:title、description 和 release_date,并把这两个参数退出到一个 INSERT 语句中并用游标执行,而后应用游标进行确认。这是一个让游标与连贯拆散的好例子;当游标里存储了一些数据库与数据库上下文(context)的信息时,须要通过连贯确认将信息传进数据库,再将信息插入数据库。

下面代码没有应用 try...finally 语句来敞开数据库,而是应用的 with() 来敞开数据库连贯,上一期中咱们也是应用的 with() 来敞开 CSV 文件。

尽管 PyMySQL 规模并不大,然而外面有一些十分实用的函数,本篇文章中并没有演示,具体能够参考 Python 的 DBAPI 规范文档。

以上是对于将采集的内容保留到 MySQL 的内容,本实例的所有代码托管于 github。

github: https://github.com/sycct/Scra…

如果有任何问题,欢送在 github issue。

正文完
 0