从 feed 到 DB

这里我们给出一个 scrapy 实现的爬 RSS feed 的例子。RSS 本质是 XML,scrapy 为 XML 提供了特殊的 crawler,我们只需要简单的继承 XMLFeedSpider 即可。我们先看看 allthingsd 这家新闻网站的 RSS feed,我们不难决定以下 artcile 类型的 Item,

from scrapy.item import Item, Field
class article(Item):
    title   = Field ()
    url     = Field ()
    tags    = Field ()
    desc    = Field ()
    content = Field ()
    uuid    = Field ()

简单起见我们就这么简单吧,这样我们的 crawler 很容易实现,不过不知道是不是 0.14 的 bug,注册过的 XML namespace 似乎没有成功,得手工加上去。

from scrapy import log
from scrapy.contrib.spiders import XMLFeedSpider
from reader.items import article

def remove_cdata(s):
    return s[9:-3]

class allthingsd_spider (XMLFeedSpider):
    name = 'allthingsd'
    allowed_domains = ['allthingsd.com']
    start_urls = ['http://allthingsd.com/feed/']
    namespaces = [
        ('content', 'http://purl.org/rss/1.0/modules/content/'),
        ('dc', 'http://purl.org/dc/elements/1.1/')
        ]
    iterators = 'iternodes'
    itertag = 'item'

    def parse_node (self, response, node):
        for prefix, uri in self.namespaces:
            node.register_namespace (prefix, uri)
        art = article()
        art['title']   = node.select('title/text()').extract()[0]
        art['url']     = node.select('link/text()').extract()[0]
        art['tags']    = node.select('category/text()').extract()
        art['tags']    = map (remove_cdata, art['tags'])
        art['uuid']    = node.select('guid/text()').extract()[0]
        art['desc']    = remove_cdata (node.select('description/text()').extract()[0])
        art['content'] = remove_cdata (node.select('content:encoded/text()').extract()[0])
        return art

注意这里的 UUID 其实还不是 UUID,我们将网站提供的 URL 进过 UUID 的运算转换成 UUID,存放在数据库里面,这部分我们使用 pipeline,其一进行 UUID 转换,其二进行数据库查询并在不存在该数据时将数据更新到数据库。另外由于 XML 里面有 CDATA 段,我们简单的去掉了这个东西,理论上 lxml 应该会去掉这个东西吧?简单起见,我们数据库使用了 sqlite。

from scrapy import log
import uuid
import sqlite3

class UUIDPipeline(object):
    def process_item(self, item, spider):
        item ['uuid'] = str(uuid.uuid5 (uuid.NAMESPACE_URL, item['uuid'].encode('utf-8')))
        return item

class SqlitePipeline(object):
    def __init__ (self):
        self.conn = sqlite3.connect ('/tmp/reader.db')
        self.cursor = self.conn.cursor ()
        self.cursor.execute ('CREATE TABLE IF NOT EXISTS articles '
                             '(title VARCHAR, url VARCHAR, desc VARCHAR, '
                             'content VARCHAR, 'uuid VARCHAR) ;')
        self.cursor.execute ('CREATE INDEX IF NOT EXISTS article_uuid ON '
                             'articles (uuid) ;')
        self.cursor.execute ('CREATE TABLE IF NOT EXISTS tags '
                             '(tag VARCHAR, id INTEGER) ;')

    def process_item(self, art, spider):
        self.cursor.execute ('SELECT * FROM articles WHERE uuid=? ;',
                             (art['uuid'], ))
        res = self.cursor.fetchone ()
        if res:
            log.msg ('item exists %s' % art, level=log.DEBUG)
        else:
            self.cursor.execute ('INSERT INTO articles (title, url, '
                                 'desc, content, uuid) '
                                 'values (?, ?, ?, ?, ?)',
                                 (art['title'], art['url'], art['desc'],
                                  art['content'], art['uuid']))
            rowid = self.cursor.lastrowid
            for tag in art['tags']:
                self.cursor.execute ('INSERT INTO tags (tag, id) values '
                                     '(?, ?)', (tag, rowid))
            self.conn.commit ()

我们建立了两张表,一张存放文章相关信息,另外一个存放 tag,当然这里 tag 也应该建立索引,简单起见略过。

这时我们可以通过

scrapy server

启动本地的 server,然后通过 jsonrpc 的方式启动我们的 crawler,

$ curl http://localhost:6800/schedule.json -d project=default -d spider=allthingsd
{"status": "ok", "jobid": "a472f833743211e1bdb4c42c030f708d"}

我们在网页上就能看到进展,并且使用 sqite 可以获得数据库里面的数据了,

$ sqlite /tmp/reader/db
sqlite> SELECT COUNT(*) FROM articles ;
25

这表明我们的程序已经 crawl 到数据并存放至数据库了,最简单的莫过于直接用 watch 加上前面的 curl 搞定定期 crawl。我们也可以用 django 实现一个简单的 client,每隔一段时间 call 一次 scrapy 即可完成数据的 pull。

——————
And he will be a wild man; his hand will be against every man, and every man’s hand against him; and he shall dwell in the presence of all his brothers.

Advertisements
从 feed 到 DB

一个有关“从 feed 到 DB”的想法

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s