Python CouchDB 无法保存从 feedparser 条目创建的字典?(没有属性 'read')

3 投票
3 回答
2468 浏览
提问于 2025-04-16 14:50

我有一个脚本,想要读取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 个回答

1

可能在Python的CouchDB中有个bug。你可以说它对接受的数据要求不够宽松。

基本上,CouchDB存储的是JSON格式的数据。你需要在你的编程语言中处理“JSON”。对于Python来说,这意味着要用dict对象。

在调用CouchDB之前,最好先把所有的数据类型转换成普通的Python字典,这样可能会得到更好的效果。也许这不是最“正确”的解决方案,但我觉得这是最快的办法。

我的Python有点生疏。dict(foo)有可能返回一个非字典类型吗?也许FeedParserDictdict的子类,并且在调用dict()时用元编程返回它自己?你能确认type(dict(item))一定是普通的Python字典吗?

在JavaScript的世界里,有个常见的技巧是通过序列化器(比如JSON)进行往返处理。像pickle.loads(pickle.dumps(item))这样的操作。这几乎可以保证你得到了核心数据的一个普通副本。

4

在邮件列表上回答过这个问题,简单来说,这个问题发生是因为一个feedbparser的条目里有一些数据,无法完整地转换成JSON格式,比如时间的结构体实例。很不幸的是,couchdb-python接下来就假设这些数据是一个文件,这样就掩盖了真正的错误。

4

我找到了一种方法,就是先把结构转成JSON格式,然后再转回Python的字典,这样我就可以把它传给CouchDB了。CouchDB会再把它转回JSON格式来保存(听起来有点奇怪,也不是最好的方法,但确实能用?)

我需要为dumps写一个自定义的序列化方法,因为time_structrepr不能被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))

撰写回答