使用 JSON 行抓取大量数据时保持正确的 JSON 结构

Keeping proper JSON structure when using JSONlines to scrape large amounts of data

最近我不得不抓取大量数据并从使用提要格式 'json' 更改为 'jsonlines' 以避免所有数据被打乱和重复。问题是现在 none 我的程序将导出的文件识别为 JSON,因为它删除了开始和结束方括号以及每个项目后面的逗号。第一个例子展示了数据的样子,第二个例子展示了我想要实现的目标。

    {"name": "Color TV", "price": "1200"}
    {"name": "DVD player", "price": "200"}

    ---------------------------------------

    {"data" : [
    {"name": "Color TV", "price": "1200"},
    {"name": "DVD player", "price": "200"},
    {"name": "Color TV", "price": "1200"}
    ]}

有没有办法在仍然使用 JsonLinesItemExporter 的同时手动添加逗号并使其成为数组?

我认为我的爬虫中唯一相关的代码是我的 yield 关键字,但我很乐意展示完整的代码。我没有使用 PHP 或 MySQL。

非常感谢您。

    yield {
            "name": name,
            "old_price": old_price,
            "discount_price": discount_price
        }

首先是逗号。

最好的解决方案是将 JsonLinesItemExporter 换行,以便在每个项目的末尾添加一个逗号。

如果适当的方法没有以您可以覆盖它的方式公开,super 它,并添加逗号,您可能必须在子 class 中重新实现该方法,甚至 monkeypatch 出口商 class。不太好看。

或者,您可以将传递给导出器的文件挂钩,使写入执行 replace('\n', ',\n')。这是 hacky,所以如果你可以挂钩导出器,我不会这样做,但它确实具有简单的优点。


现在,文件开头和结尾的括号。在不知道您正在使用的库或您使用它的方式的情况下,这将是非常模糊的。

如果您对每个文件使用单个 "session" 导出器——也就是说,您在启动时打开它,向其中写入一堆项目,然后关闭它,永远不会 re-open 它并附加到它,这很容易。假设您通过 subclassing 导出器 class 挂钩其写入来解决第一个问题,如下所示:

class JsonArrayExporter(JsonLinesItemExporter):
    def _write_bytes(self, encoded_bytes):
        encoded_bytes = _encoded_bytes.replace(b'\n', b',\n')
        returns super()._write_bytes(encoded_bytes)

我在猜测实现的样子,但您已经发现了正确的做法,因此您应该能够将我的猜测转化为现实。现在,您需要像这样添加两个方法:

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._writebytes(b'[\n')

    def close(self):
        if not self.closed():
            self._writebytes(b']\n')
        super().close()

如果导出器 class 内部有自己的缓冲区,您可能需要在 _writebytes 之前的某处添加 flush,但这是我希望看到的唯一额外的复杂性。

如果您在每个会话中重新打开文件并附加到它们,这显然是行不通的。您 可以 __init__:

中执行类似于此伪代码的操作
if file is empty:
    write('[\n')
else:
    seek to end of file
    if last two bytes are ']\n':
        seek back 2 bytes

这具有对您的客户端代码透明的优点,但它有点 hacky。如果你的客户端代码知道它什么时候打开一个新文件而不是附加到一个旧文件,并且知道它什么时候结束一个文件,那么添加 addStartMarkeraddEndMarker 方法并调用它们可能更干净,或者让客户端在 initializing/after 关闭导出器之前手动将括号写入文件。