用新版Bulk Loader往GAE datastore上传XML数据

标签:Google App Engine, Python

最近在做个blog,需要把以前的数据导入到GAE的datastore里去。看了下《Uploading and Downloading Data》这篇文档后,发现Bulk Loader已经升级了,中文文档里介绍的方法已经过时了,于是只好重新学习了。

简单来说,我们需要配置一个bulkloader.yaml,在这里面定义模型的属性、转换方式等数据,然后用appcfg.py upload_data命令就能上传了。

于是重点就放在bulkloader.yaml上了,它分为2个部分:python_preamble和transformers。
  • python_preamble用于设置需要导入的模块(用于转换数据格式),示例如下:
    python_preamble:
    - import: google.appengine.ext.bulkload.transform
    - import: google.appengine.ext.db
    - import: re
    - import: base64
    注意这里的import实际上相当于import ... from ...,也就是在后面你想引用google.appengine.ext.bulkload.transform时,直接写transform即可。
    如果需要用自己的转换模块,就需要在这里import。
  • transformers则是定义转换方式了,它又分成了几个部分:kind、connector、connector_options和property_map。
    • kind用于定义模型名,这个无需解释了。值得一提的是,如果你需要对属性设置indexd、default和choices等参数时,可以import你的model模块,然后在这里写上“model: model.Article”之类的来取代“kind: Article”。
    • connector用于定义数据格式,有csv、simplexml和simpletext这3种,其中simpletext只能用于下载数据。由于我要导入WordPress的数据,所以选择simplexml。
    • connector_options用于定义数据格式的详细设定。3种格式所用的参数都不一样,这里只介绍simplexml的,其他的可以自己去读读文档。
      • xpath_to_nodes用于定位数据项。
        由于WordPress的文章是类似这样的:
        <?xml version="1.0" encoding="UTF-8"?>
        <rss version="2.0"
          xmlns:excerpt="http://wordpress.org/export/1.0/excerpt/"
          xmlns:content="http://purl.org/rss/1.0/modules/content/"
          xmlns:wfw="http://wellformedweb.org/CommentAPI/"
          xmlns:dc="http://purl.org/dc/elements/1.1/"
          xmlns:wp="http://wordpress.org/export/1.0/"
        >
        
        <channel>
        ...
          <item>
            <title>...</title>
            <pubDate>...</pubDate>
            <content:encoded><![CDATA[...]]></content:encoded>
          </item>
        </channel>
        </rss>
        因此路径就是:/rss/channel/item
      • style的值可为element_centric或attribute_centric。这个玩意是干啥的呢?熟悉XML的都知道,定义一个node的属性时,既可以写个子node,又可以用“属性名="属性值"”的方式。由于WordPress是采用前者,所以就用element_centric。
    • property_map就是最重要的属性定义部分了,它又分为property、external_name、import_template、import_transform、export_transform和export这几项:
      • property就是属性名,无须解释。
      • external_name是该属性对应到文件里的名字。
      • import_template是一个比较高级的东东,当你的1个属性需要从文件里的多个属性来转换时,就需要用到它了。解析器会将所有字段作为一个字典参数,传给它来进行string interpolation操作(也就是%操作),再将结果作为import_transform。例如你需要date和time这2个字段,那就可以这样:"%(date)s %(time)s"。一般来说,设置了这个字段,就不需要external_name了。
      • import_transform就是导入的转换函数了(可以是之前import的函数,也可以是lambda函数),不写的话就当成string来处理。它接受external_name或import_template作为参数,并返回一个正确类型的对象。可以参考google.appengine.ext.bulkload.transform里的函数来写。
      • export_transform就是导出的转换函数了,和import_transform正好相反。
      • export和import_template相反,你上传的时候将多个字段合并成一个字段,在这里你就可以将一个字段分离成多个字段。它下面还可以有external_name和export_transform参数,参照前文就知道啥意思了。
    • 如果说property_map还不够用,你还需要一些复杂的格式转换的话,还可以用post_import_function和post_export_function。这2个函数可以用到更多参数,并且可以跳过不需要的实体。由于我用不到,也就不介绍了,需要的自己看文档吧。

接下来做个简单的例子来演示。

首先得修改一下WordPress的导出数据,因为Bulk Loader不支持<wp:post_date>这种属性名。因此可以做个全文搜索替换,把“wp:”等替换成空即可。

接着定义bulkloader.yaml:
python_preamble:
- import: google.appengine.ext.bulkload.transform
- import: google.appengine.ext.db
- import: my_transform

transformers:

- kind: Article
  connector: simplexml
  connector_options:
    xpath_to_nodes: /rss/channel/item
    style: element_centric
  property_map:

    - property: __key__
      external_name: post_id
      import_transform: my_transform.import_key_from_id('Article')

    - property: date
      external_name: post_date_gmt
      import_transform: transform.import_date_time('%Y-%m-%d %H:%M:%S')
      export_transform: transform.export_date_time('%Y-%m-%d %H:%M:%S')

    - property: title
      external_name: title

    - property: content
      external_name: content
      import_transform: db.Text
这个例子里我只需要用到4个字段,其他的字段会被忽略。
注意content的长度可能很长,所以要用db.Text来转换。这里也不需要我们来处理<![CDATA[...]]>,它会被自动解析成CDATA所包含的数据。
另外还会注意到,我处理key的时候使用了自定义的函数(放在my_transform.py里),用于将数字id转成db.Key对象,代码如下:
def import_key_from_id(kind):
	def make_key(id):
		return db.Key.from_path(kind, int(id))
	return make_key
如果不写import_transform的话,会被当成key name处理;如果不写“__key__”属性的话,会自动生成一个不重复的key。

最后运行一下上传代码即可:
appcfg.py upload_data --config_file=bulkloader.yaml --filename=wordpress.xxx.xml --kind=Article --url=http://你的app.appspot.com/remote_api地址
注意修改filename和url。

顺便再说下怎么导入Discuz!论坛的数据。最方便的就是在phpMyAdmin里将cdb_posts导出为XML格式,然后运行这个脚本:
import re

r1 = re.compile('<column name="subject">(.*?)</column>')
r2 = re.compile('<column name="message">(.*?)</column>', re.S)
r3 = re.compile('<column name="(.+?)">(.*?)</column>')

s = open('cdb_posts.xml').read()
s = r1.sub(r'<subject><![CDATA[\1]]></subject>', s)
s = r2.sub(r'<message><![CDATA[\1]]></message>', s)
s = r3.sub(r'<\1>\2</\1>', s) # 如果不在乎文件大小的话,直接用s = r3.sub(r'<\1><![CDATA[\2]]></\1>', s)就能省去前2次替换了

open('cdb_posts2.xml', 'w').write(s)
然后拿cdb_posts2.xml去操作就行了。其他表、视图或查询结果也可以导出,再做类似处理。

最后给个Bulkloader demo网站。这个网站做了很多个例子可以参考,包括自定义connector(使用JSON)的例子。

1条评论 你不来一发么↓ 顺序排列 倒序排列

    向下滚动可载入更多评论,或者点这里禁止自动加载

    想说点什么呢?