用新版Bulk Loader往GAE datastore上传XML数据
2010 10 14 04:42 PM 3636次查看
分类:Google App Engine 标签:Google App Engine, Python
简单来说,我们需要配置一个bulkloader.yaml,在这里面定义模型的属性、转换方式等数据,然后用appcfg.py upload_data命令就能上传了。
于是重点就放在bulkloader.yaml上了,它分为2个部分:python_preamble和transformers。
- python_preamble用于设置需要导入的模块(用于转换数据格式),示例如下:
注意这里的import实际上相当于import ... from ...,也就是在后面你想引用google.appengine.ext.bulkload.transform时,直接写transform即可。python_preamble: - import: google.appengine.ext.bulkload.transform - import: google.appengine.ext.db - import: re - import: base64
如果需要用自己的转换模块,就需要在这里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的文章是类似这样的:
因此路径就是:/rss/channel/item<?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>
- style的值可为element_centric或attribute_centric。这个玩意是干啥的呢?熟悉XML的都知道,定义一个node的属性时,既可以写个子node,又可以用“属性名="属性值"”的方式。由于WordPress是采用前者,所以就用element_centric。
- xpath_to_nodes用于定位数据项。
- 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就是属性名,无须解释。
- 如果说property_map还不够用,你还需要一些复杂的格式转换的话,还可以用post_import_function和post_export_function。这2个函数可以用到更多参数,并且可以跳过不需要的实体。由于我用不到,也就不介绍了,需要的自己看文档吧。
- kind用于定义模型名,这个无需解释了。值得一提的是,如果你需要对属性设置indexd、default和choices等参数时,可以import你的model模块,然后在这里写上“model: model.Article”之类的来取代“kind: Article”。
接下来做个简单的例子来演示。
首先得修改一下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)的例子。
向下滚动可载入更多评论,或者点这里禁止自动加载。