有很多数据库符合Python Database API规范2.0,例如MySQL,PostgreSQL,Orcale,Microsoft SQL Server和SQLite。它们的驱动是很复杂的并且经过了很多测试,如果再为了Twisted重新实现就太浪费了。你可以在Twisted应用中使用这些数据库客户端,例如,Scrapy就使用了twisted.enterprise.adbapi
库。我们会用MySQL来展示一下如何使用,不过相同的使用方法可以应用到其他兼容的数据库上。
先来创建一个数据库:
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的表格,里面有一些准备用来创建pipeline的字段。
接下来会使用MySQL的Python客户端,并要安装一个模块叫做dj-database-url
,它是用来帮助我们解析连接到数据库的URL的,可以使用pip install dj-database-url MySQL-python
来安装。下面是MySQL Pipeline的代码:
import traceback
import dj_database_url
import MySQLdb
from twisted.internet import defer
from twisted.enterprise import adbapi
from scrapy.exceptions import NotConfigured
class MysqlWriter(object):
"""
A spider that writes to MySQL databases
"""
@classmethod
def from_crawler(cls, crawler):
"""Retrieves scrapy crawler and accesses pipeline's settings"""
# Get MySQL URL from settings
mysql_url = crawler.settings.get('MYSQL_PIPELINE_URL', None)
# If doesn't exist, disable the pipeline
if not mysql_url:
raise NotConfigured
# Create the class
return cls(mysql_url)
def __init__(self, mysql_url):
"""Opens a MySQL connection pool"""
# Store the url for future reference
self.mysql_url = mysql_url
# Report connection error only once
self.report_connection_error = True
# Parse MySQL URL and try to initialize a connection
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):
"""Discard the database pool on spider close"""
self.dbpool.close()
@defer.inlineCallbacks
def process_item(self, item, spider):
"""Processes the item. Does insert into MySQL"""
logger = spider.logger
try:
yield self.dbpool.runInteraction(self.do_replace, item)
except MySQLdb.OperationalError:
if self.report_connection_error:
logger.error("Can't connect to MySQL: %s" % self.mysql_url)
self.report_connection_error = False
except:
print traceback.format_exc()
# Return the item for the next stage
defer.returnValue(item)
@staticmethod
def do_replace(tx, item):
"""Does the actual REPLACE INTO"""
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)
@staticmethod
def parse_mysql_url(mysql_url):
"""
Parses mysql url and prepares arguments for
adbapi.ConnectionPool()
"""
params = dj_database_url.parse(mysql_url)
conn_kwargs = {}
conn_kwargs['host'] = params['HOST']
conn_kwargs['user'] = params['USER']
conn_kwargs['passwd'] = params['PASSWORD']
conn_kwargs['db'] = params['NAME']
conn_kwargs['port'] = params['PORT']
# Remove items with empty values
conn_kwargs = dict((k, v) for k, v in conn_kwargs.iteritems() if v)
return conn_kwargs
parse_mysql_url()
函数用来把MYSQL_PIPELINE_URL
设置的值解析成单个的参数并在__init__()
函数中把它传递给adbapi.ConnectPool()
,由这个函数使用adbapi
来对MySQL连接池进行初始化。它的第一个参数是需要加载的模块,在此例中是MySQLdb
。我们为MySQL客户端设置了一些其他的参数来处理Unicode和超时。所有的这些参数都会在adbapi
需要打开新的连接时传递给MySQLdb.connect()
函数。爬虫关闭的时候,调用close()
函数。
process_item()
方法只是对dbpool.runInteraction()
的一个包装。这个方法把回调函数组织成一个队列并在连接池中的某个连接的Transaction
对象可用时调用回调函数。Transaction
对象有一个和DB-API游标类似的API。在这个例子中,回调函数是do_replace()
,定义在后面几行。@staticmethod
意味着这个方法是与类相关的而不是与类的实例相关的,其实可以删除前面的self
参数。如果一个方法中没有使用成员变量,那么最好还是把它设置成静态方法,但是忘记这样做也无所谓。这个方法准备了一个SQL语句,一些参数,并调用了Transaction
的execute()
方法来执行插入操作。方法中的SQL语句使用了REPLACE INTO
而不是INSERT INTO
,以便当一个条目已经存在时来替换它。如果需要SQL语句返回结果,比如使用了SELECT
语句,那就要使用dbpool.runQuery()
。通过设置adbapi.ConnectionPool()
方法的cursorclass
参数还可以改变默认的游标,比如cursorclass=MySQLdb.cursors.DictCursor
,因为它更利于数据的检索。
在ITEM_PIPELINES
中加上:
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)
至于性能和之前的一样。