Scrapy Pipeline之与数据库交互

来源:互联网 发布:java 编码格式 编辑:程序博客网 时间:2024/05/27 20:19

有很多数据库符合Python Database API规范2.0,例如MySQL,PostgreSQL,Orcale,Microsoft SQL Server和SQLite。它们的驱动是很复杂的并且经过了很多测试,如果再为了Twisted重新实现就太浪费了。你可以在Twisted应用中使用这些数据库客户端,例如,Scrapy就使用了twisted.enterprise.adbapi库。我们会用MySQL来展示一下如何使用,不过相同的使用方法可以应用到其他兼容的数据库上。

写入MySQL的Pipeline

先来创建一个数据库:

mysql> create database properties;mysql> use propertiesmysql> 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 tracebackimport dj_database_urlimport MySQLdbfrom twisted.internet import deferfrom twisted.enterprise import adbapifrom scrapy.exceptions import NotConfiguredclass 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语句,一些参数,并调用了Transactionexecute()方法来执行插入操作。方法中的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)

至于性能和之前的一样。

0 0
原创粉丝点击