【问题标题】:Python generator for paged API resource用于分页 API 资源的 Python 生成器
【发布时间】:2013-07-17 14:38:48
【问题描述】:

所以我有这个带有集合people 的 RESTful API,可以这样调用:

http://example.com/people?lastname=smith

返回如下 JSON 响应:

    {
      "page": 0,
      "next": 1,
      "total": 5000000,
      "people": [
        { 
          "firstname": "John",
          "lastname": "Smith",
          "age": 32
        },
        { 
          "firstname": "Adam",
          "lastname": "Smith",
          "age": 84
        },
        ...
    }

我想编写一个 Python 生成器,它将从响应中产生每个人,当它到达最后一个人时,如果有下一页,它将使用 http://example.com/people?lastname=smith&page=1 请求下一页并继续迭代无缝地覆盖结果。生成的类调用很简单:

    client = PeopleClient("http://example.com/people")
    smiths = client.get_people_by_last_name("smith")

然后我就可以遍历smiths 中的每个“Smith”;如有必要,通过全部 500 万。

关于如何实现或是否有可能实现这一点的任何想法?

更新

使用来自@ali-afshar 的答案作为指导,这个实现应该适用于假设的 REST API:

    import requests

    class PeopleClient:
        def __init__(self, url):
            self._url = url

        def _get_people(self, **kwargs):
            return requests.get(self._url, params=kwargs)

        def get_people_by_last_name(self, lastname):
            current_page = 0
            while current_page >= 0:
                result = self._get_people(lastname=lastname, page=current_page)
                for person in result.get("people", []):
                    yield person

                current_page = result.get("next", -1)

【问题讨论】:

  • 您可以使用 requests 包和内置的 json 包来解析 JSON。然后遍历所有名称。完成后,请求下一页。如果返回 404 则停止,否则继续上述操作。挖掘完所有内容后,只需遍历此列表即可。
  • @Vineet 认为使用生成器的重点不是在迭代之前处理整个数据集。我在示例中使用了 500 万这个数字来准确说明为什么您不会预先提出所有这些请求。
  • 为什么没有人看到我已经知道 Python 生成器,并且我零打算首先将所有结果放入内存?以为我在我的问题中使用了“我想写一个 Python 生成器”和“yield”这样的短语来表达得很清楚。
  • @jeremyswitzer @ali-afshar 如果我们 yield result.get("people", []) 多个人直接将有任何性能改进,但我同意我们需要更改使用生成器的 api 以及接受多人

标签: python generator


【解决方案1】:

除了为您编写代码之外,您还想利用 Python 的生成器,而不是将整个集合实现为一个列表。这样您就可以立即开始使用结果,并且仅在到达页面末尾时才执行分页请求。

for person in PeopleClient("http://ex..").get_people_by_last_name("smith"):
    # Do something with the person

其次,您对实际请求的实现应该采用一个可以递增的页面参数,并且可以由包装器生成器调用。

def get_people_page(name, page):
    # Perform the HTTP request, using page=page

生成器本身类似于:

def get_all_people(name):
    page = 0
    has_more = 1
    while has_more:
        for person in get_people_page(name, page):
            yield person
        page += 1
        has_more = # calculate has more by whether you have a next link
                   # or whether the results set is empty
                   # or whether you get an error

【讨论】:

  • 谢谢。尽管我必须弄清楚响应中是否有下一个链接。 get_people_page 函数只返回集合。
  • 我用其他方法更新了答案,看看你是否还有更多。
【解决方案2】:

这是我的生成器解决方案,我认为它是一种触摸清洁器,当使用指定的 per_page 时,它可以为您节省一个额外的不必要的请求。

def get_all(per_page=100):
    page = 0
    while True:
        items = self.api.get(per_page=per_page, page=page)

        for item in items:
            yield item

        if len(items) < per_page:
            break

        page += 1

all_items = list(get_all())

self.api.get() 必须接受 pageper_page 参数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多