与标准Python客户端建立数据库接口
有很多重要的数据库遵从 Python 数据库 API 规范 2.0 版本,包括 MySQL、PostgreSQL、Oracle、Microsoft SQL Server 和 SQLite。它们的驱动一般都比较复杂且久经考验,如果为 Twisted 重新实现的话则是巨大的浪费。人们可以在 Twisted 应用中使用这些数据库客户端,比如在 Scrapy 使用 twisted.enterprise.adbapi 库。我们将使用 MySQL 作为示例演示其使用,不过对于任何其他兼容的数据库来说,也可以应用相同的原则。
用于写入MySQL的管道
MySQL 是一个非常强大且流行的数据库。我们将编写一个管道,将 item 写入到其中。我们已经在虚拟环境中运行了一个 MySQL 实例。现在只需使用 MySQL 命令行工具执行一些基本管理即可,同样该工具也已经在开发机中预安装好了,下面执行如下操作打开 MySQL 控制台。
$ mysql -h mysql -uroot -ppass
这将会得到 MySQL 的提示符,即 mysq>,现在可以创建一个简单的数据库表,其中包含一些字段,如下所示。
mysql> create database properties;
mysql> use properties
mysql> CREATE TABLE properties (
url varchar(100) NOT NULL,
title varchar(30),
price DOUBLE,
description varchar(30),
PRIMARY KEY (url)
);
mysql> SELECT * FROM properties LIMIT 10;
Empty set (0.00 sec)
非常好,现在拥有了一个 MySQL 数据库,以及一张名为 properties 的表,其中包含了一些字段,此时可以准备创建管道了。请保持 MySQL 的控制台为开启状态,因为之后还会回来检查是否正确插入了值。如果想退出控制台,只需要输入 exit 即可。
在本节,我们将会向 MySQL 数据库中插入房产信息。如果你想擦除它们,可以使用如下命令:
|
我们将使用 Python 的 MySQL 客户端。我们还将安装一个名为 djdatabase-url 的小工具模块,帮助我们解析连接的 URL(仅用于为我们在IP、端口、密码等不同设置中切换节省时间)。可以使用 pip install dj-database-url MySQL-python 安装这两个库,不过我们已经在开发环境中安装好它们了。我们的 MySQL 管道非常简单,如下所示。
from twisted.enterprise import adbapi
...
class MysqlWriter(object):
...
def __init__(self, mysql_url):
conn_kwargs = MysqlWriter.parse_mysql_url(mysql_url)
self.dbpool = adbapi.ConnectionPool('MySQLdb',
charset='utf8',use_unicode=True,
connect_timeout=5,
**conn_kwargs)
def close_spider(self, spider):
self.dbpool.close()
@defer.inlineCallbacks
def process_item(self, item, spider):
try:
yield self.dbpool.runInteraction(self.do_replace, item)
except:
print traceback.format_exc()
defer.returnValue(item)
@staticmethod
def do_replace(tx, item):
sql = """REPLACE INTO properties (url, title, price,description) VALUES (%s,%s,%s,%s)"""
args = (
item["url"][0][:100],
item["title"][0][:30],
item["price"][0],
item["description"][0].replace("\r\n", " ")[:30]
)
tx.execute(sql, args)
本示例的完整代码地址为 ch09/properties/properties/pipeline/mysql.py。 |
本质上,大部分代码仍然是模板化的爬虫代码。我们省略的代码用于将 MYSQL_PIPELINE_URL 设置中包含的 mysql://user:pass@ip/database 格式的 URL 解析为独立参数。在爬虫的 __init__()
中,我们将这些参数传给 adbapi.ConnectionPool(),使用 adbapi 的基础功能初始化 MySQL 连接池。第一个参数是想要导入的模块名称。在该 MySQL 示例中,为 MySQLdb。我们还为 MySQL 客户端设置了一些额外的参数,用于处理 Unicode 和超时。所有这些参数会在每次 adbapi 需要打开新连接时,前往底层的 MySQLdb.connect() 函数。当爬虫关闭时,我们为该连接池调用 close() 方法。
我们的 process_item() 方法实际上包装了 dbpool.runInteraction()。该方法将稍后调用的回调方法放入队列,当来自连接池的某个连接的 Transaction 对象变为可用时,调用该回调方法。Transaction 对象的 API 与 DB-API 游标相似。在本例中,回调方法为 do_replace(),该方法在后面几行进行了定义。@staticmethod 意味着该方法指向的是类,而不是具体的类实例,因此,可以省略平时使用的 self 参数。当不使用任何成员时,将方法静态化是个好习惯,不过即使忘记这么做,也没有问题。该方法准备了一个 SQL 字符串和几个参数,调用 Transaction 的 execute() 方法执行插入。我们的 SQL 语句使用了 REPLACE INTO 来替换已经存在的条目,而 不是更常见的 INSERT INTO, 原因是如果条目已经存在,可以使用相同的主键。在本例中这种方式非常便捷。如果想使用 SQL 返回数据,如 SELECT 语句,可以使用 dbpool.runQuery()。如果想要修改默认游标,可以通过设置 adbapi.ConnectionPool() 的 cursorclass 参数来实现,比如设置 cursorclass=MySQLdb.cursors.DictCursor,可以让数据获取更加便捷。
要想使用该管道,需要在 settings.py 文件的 ITEM_PIPELINES 字典中添加它,另外还需要设置 MYSQL_PIPELINE_URL 属性。
ITEM_PIPELINES = { ...
'properties.pipelines.mysql.MysqlWriter': 700,
...
MYSQL_PIPELINE_URL = 'mysql://root:pass@mysql/properties'
执行如下命令。
scrapy crawl easy -s CLOSESPIDER_ITEMCOUNT=1000
该命令运行后,可以回到 MySQL 提示符下,按如下方式查看数据库中的记录。
mysql> SELECT COUNT(*) FROM properties;
+----------+
| 1006 |
+----------+
mysql> SELECT * FROM properties LIMIT 4;
+------------------+--------------------------+--------+-----------+
| url | title | price | description
+------------------+--------------------------+--------+-----------+
| http://...0.html | Set Unique Family Well | 334.39 | website c
| http://...1.html | Belsize Marylebone Shopp | 388.03 | features
| http://...2.html | Bathroom Fully Jubilee S | 365.85 | vibrant own
| http://...3.html | Residential Brentford Ot | 238.71 | go court
+------------------+--------------------------+--------+-----------+
4 rows in set (0.00 sec)
延时和吞吐量等性能和之前保持相同,相当不错。