我用Python 3编写了一个短脚本,该脚本连接到Stack Exchange API,在过去两周中获取了关于Programming Puzzles&Code Golf的所有问题,并确定了每天平均问题数以及每个问题的平均答案数。

每天的问题数量应与51区相同。显然,直接刮取Area 51会容易得多,但是我想自己弄清楚以进行练习。

我不是Python或Web API的专家,所以我希望您能用好代码评论人员可以帮助我改善实践。

import requests, datetime, time

def seconds_since_epoch(dt):
    epoch = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)
    return int((dt - epoch).total_seconds())

today = datetime.datetime.now(datetime.timezone.utc)

params = {
    "site": "codegolf",
    "fromdate": seconds_since_epoch(today - datetime.timedelta(days=14)),
    "todate": seconds_since_epoch(today),
    "pagesize": 100,
    "page": 1
}

base_url = "https://api.stackexchange.com/2.2"

results = []

while True:
    req = requests.get(base_url + "/questions", params=params)
    contents = req.json()
    results.extend(contents["items"])
    if not contents["has_more"]:
        break
    if "backoff" in contents:
        time.sleep(contents["backoff"])
    params["page"] += 1

questions_per_day = len(results) / 14
answers_per_question = sum([q["answer_count"] for q in results]) / len(results)

print("Over the past 2 weeks, PPCG has had...")
print(round(questions_per_day, 1), "questions per day")
print(round(answers_per_question, 1), "answers per question")


我的方法是使用dict构建查询,并使用requests模块向API发出请求。我将页面大小设置为最大,以减少发出的请求数,以免每日配额用尽那么快。

代码托管在GitHub上,如果您想分叉和适应出于自己的目的,假设它不太可怕。

评论

嗨Alex:D为Python新手提供了不错的代码;)

@cat谢谢!我对Python足够熟悉,足以发挥作用,但是我一直没有足够地使用它来擅长于此。 :P

#1 楼

您的seconds_since_epoch函数具有内置的Python等效项datetime.timestamp

如果使用from datetime import datetime, timezone,您的命名空间会更干净。 。

base_url更好地命名为urllib.parse.urljoin

results中,questions多余且效率低。
/>
不是连续3次q4312079而是创建多行格式的字符串并打印一次。

您永远不会创建返回问题的函数,也不会定义主函数。我建议在主函数中打印,该函数调用一个获取并返回问题信息的函数。


这是我的编程方式:

import requests
import time
from datetime import datetime, timezone, timedelta


def get_question_info(site, start, stop):
    API_URL = "https://api.stackexchange.com/2.2/questions"
    req_params = {
        "site": site,
        "fromdate": int(start.timestamp()),
        "todate": int(stop.timestamp()),
        "pagesize": 100,
        "page": 1
    }

    questions = []
    while True:
        req = requests.get(API_URL, params=req_params)
        contents = req.json()
        questions.extend(contents["items"])

        if not contents["has_more"]:
            break
        req_params["page"] += 1

        if "backoff" in contents:
            time.sleep(contents["backoff"])

    return questions


def get_area51_estimate(site):
    now = datetime.now(timezone.utc)
    fortnight_ago = now - timedelta(days=14)
    questions = get_question_info(site, fortnight_ago, now)
    avg_questions = len(questions) / 14
    avg_answers = sum(q["answer_count"] for q in questions) / len(questions)
    return avg_questions, avg_answers


if __name__ == "__main__":
    msg = """Over the past 2 weeks, PPCG has had...
{:.1f} questions per day
{:.1f} answers per question"""
    print(msg.format(*get_area51_estimate("codegolf")))


评论


\ $ \ begingroup \ $
在您的第三点上,您说的是OP当前未使用该urljoin方法,但是它们应该/应该是吧?我认为您在该句子中缺少一些单词。
\ $ \ endgroup \ $
– SirPython
16年2月18日在23:15

\ $ \ begingroup \ $
您是否将API_URL固定在大写形式,因为它是恒定的?
\ $ \ endgroup \ $
– Alex A.
16-2-18在23:51

\ $ \ begingroup \ $
@AlexA。正确。
\ $ \ endgroup \ $
–orlp
16-2-18在23:58

#2 楼

代码样式


import requests, datetime, time



根据PEP8,导入不应排成一行

您的代码不符合不能满足PEP8的“一行上字符太多”规则,但是,应避免在一行上出现这样的内容:


epoch = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc)





base_url

不要将base_url用于添加/questions的其他任何东西,因此只需将其原样添加到base_url中即可。 (然后将其重命名为url

base_url + "/questions"




14是什么?以及为什么要将其除以结果量?

应将幻数移至另一个变量以提高可读性。


len(results) / 14




删除"site": "codegolf"


我在聊天中向您提到了此内容,但是如果您对站点名称进行快速input()的操作,则可以将该脚本扩展到任何API支持的站点。



class结构:

基于以上观点,如果使用class结构,则可以提取大多数将此逻辑分解为不同的部分,并使用生成器来获取下一页,而不是增加页面参数。

评论


\ $ \ begingroup \ $
如果每个导入都应放在单独的行上,那么使用Python语法中的逗号有什么意义? :/
\ $ \ endgroup \ $
– Conor O'Brien
16-2-18在23:42

\ $ \ begingroup \ $
我认为这是PEP8之前的核心语言功能
\ $ \ endgroup \ $
– Quill
16-2-18在23:43

\ $ \ begingroup \ $
当从特定模块导入模块/方法/等时,通常使用逗号。 “不使用逗号”规则仅在仅导入根模块时适用。可以将其概括为“每个模块都有自己的导入行”。
\ $ \ endgroup \ $
–凯文·布朗
16-2-19的2:32

\ $ \ begingroup \ $
@KevinBrown很有意思,谢谢您的解释:-)
\ $ \ endgroup \ $
– Quill
16-2-19的2:33

\ $ \ begingroup \ $
我创建了一些使用类和生成器的东西,这可能很有用-请参见此处。用法非常简单。
\ $ \ endgroup \ $
–马特·迪卡利翁(Matt Deacalion)
16年2月24日在9:23

#3 楼

有点含糊不清

params = {
    ...
}

...

while True:
    req = requests.get(base_url + "/questions", params=params)


为什么不为params变量命名呢?最后一行比您将变量命名为site_info之类的名称更难读,因为不熟悉Python的怪异之处的人可能更容易将命名参数与变量区分开。 (作为经常使用JavaScript的程序员,这句话肯定使我感到困惑。\ _(ツ)_ /¯。)此外,它更具可读性,因为您的脑子不必采取额外的步骤来区分命名变量。参数。

评论


\ $ \ begingroup \ $
@SirPython我不知道,我很喜欢ES6。但是正如Quill所说的,显然……我仍然不了解它。
\ $ \ endgroup \ $
– Conor O'Brien
16-2-18在23:19

\ $ \ begingroup \ $
@CᴏɴᴏʀO'Bʀɪᴇɴ我很抱歉;我将其误认为是另一项功能(您可以在其中指定参数的默认值。
\ $ \ endgroup \ $
– SirPython
16-2-18在23:20

\ $ \ begingroup \ $
@SirPython啊,我知道了。这是有道理的,也是导致我自己对命名参数感到困惑的原因。 ;)
\ $ \ endgroup \ $
– Conor O'Brien
16-2-18在23:21