Python CouchDB 无法保存从 feedparser 条目创建的字典?(没有属性 'read')
我有一个脚本,想要读取RSS源里的内容,并把每一条内容以JSON格式存储到CouchDB数据库里。
我代码中有个有趣的部分大概是这样的:
Feed = namedtuple('Feed', ['name', 'url'])
couch = couchdb.Server(COUCH_HOST)
couch.resource.credentials = (COUCH_USER, COUCH_PASS)
db = couch['raw_entries']
for feed in map(Feed._make, csv.reader(open("feeds.csv", "rb"))):
d = feedparser.parse(feed.url)
for item in d.entries:
db.save(item)
当我尝试运行这段代码时,从db.save(item)
那里得到了以下错误:
AttributeError: object has no attribute 'read'
好吧,我于是做了一点调试……
for feed in map(Feed._make, csv.reader(open("feeds.csv", "rb"))):
d = feedparser.parse(feed.url)
for item in d.entries:
print(type(item))
结果显示是<class 'feedparser.FeedParserDict'>
——哦,原来feedparser使用了它自己的字典类型……那如果我试着把它强制转换成dict
呢?
for feed in map(Feed._make, csv.reader(open("feeds.csv", "rb"))):
d = feedparser.parse(feed.url)
for item in d.entries:
db.save(dict(item))
Traceback (most recent call last):
File "./feedchomper.py", line 32, in <module>
db.save(dict(item))
File "/home/dealpref/lib/python2.7/couchdb/client.py", line 407, in save
_, _, data = func(body=doc, **options)
File "/home/dealpref/lib/python2.7/couchdb/http.py", line 399, in post_json
status, headers, data = self.post(*a, **k)
File "/home/dealpref/lib/python2.7/couchdb/http.py", line 381, in post
**params)
File "/home/dealpref/lib/python2.7/couchdb/http.py", line 419, in _request
credentials=self.credentials)
File "/home/dealpref/lib/python2.7/couchdb/http.py", line 239, in request
resp = _try_request_with_retries(iter(self.retry_delays))
File "/home/dealpref/lib/python2.7/couchdb/http.py", line 196, in _try_request_with_retries
return _try_request()
File "/home/dealpref/lib/python2.7/couchdb/http.py", line 222, in _try_request
chunk = body.read(CHUNK_SIZE)
AttributeError: 'dict' object has no attribute 'read'
什、什么?这听起来不对,因为下面的代码运行得很好,而且类型依然是dict
:
some_dict = dict({'foo': 'bar'})
print(type(some_dict))
db.save(some_dict)
我到底漏掉了什么呢?
3 个回答
可能在Python的CouchDB中有个bug。你可以说它对接受的数据要求不够宽松。
基本上,CouchDB存储的是JSON格式的数据。你需要在你的编程语言中处理“JSON”。对于Python来说,这意味着要用dict
对象。
在调用CouchDB之前,最好先把所有的数据类型转换成普通的Python字典,这样可能会得到更好的效果。也许这不是最“正确”的解决方案,但我觉得这是最快的办法。
我的Python有点生疏。dict(foo)
有可能返回一个非字典类型吗?也许FeedParserDict
是dict
的子类,并且在调用dict()
时用元编程返回它自己?你能确认type(dict(item))
一定是普通的Python字典吗?
在JavaScript的世界里,有个常见的技巧是通过序列化器(比如JSON)进行往返处理。像pickle.loads(pickle.dumps(item))
这样的操作。这几乎可以保证你得到了核心数据的一个普通副本。
在邮件列表上回答过这个问题,简单来说,这个问题发生是因为一个feedbparser的条目里有一些数据,无法完整地转换成JSON格式,比如时间的结构体实例。很不幸的是,couchdb-python接下来就假设这些数据是一个文件,这样就掩盖了真正的错误。
我找到了一种方法,就是先把结构转成JSON格式,然后再转回Python的字典,这样我就可以把它传给CouchDB了。CouchDB会再把它转回JSON格式来保存(听起来有点奇怪,也不是最好的方法,但确实能用?)
我需要为dumps写一个自定义的序列化方法,因为time_struct
的repr
不能被eval
执行。
来源:http://diveintopython3.org/serializing.html
代码:
#!/usr/bin/env python2.7
from collections import namedtuple
import csv
import json
import time
import feedparser
import couchdb
def to_json(python_object):
if isinstance(python_object, time.struct_time):
return {'__class__': 'time.asctime',
'__value__': time.asctime(python_object)}
raise TypeError(repr(python_object) + ' is not JSON serializable')
Feed = namedtuple('Feed', ['name', 'url'])
COUCH_HOST = 'http://mycouch.com'
COUCH_USER = 'user'
COUCH_PASS = 'pass'
couch = couchdb.Server(COUCH_HOST)
couch.resource.credentials = (COUCH_USER, COUCH_PASS)
db = couch['raw_entries']
for feed in map(Feed._make, csv.reader(open("feeds.csv", "rb"))):
d = feedparser.parse(feed.url)
for item in d.entries:
j = json.dumps(item, default=to_json)
db.save(json.loads(j))