【问题标题】:How to authenticate to Wikimedia Commons Query Service using OAuth in Python?如何在 Python 中使用 OAuth 对 Wikimedia Commons 查询服务进行身份验证?
【发布时间】:2021-03-25 22:40:03
【问题描述】:

我正在尝试使用 Python 以编程方式使用 Wikimedia Commons 查询服务[1],但无法通过 OAuth 1 进行身份验证。

下面是一个独立的 Python 示例,它不能按预期工作。预期的行为是返回结果集,而是返回登录页面的 HTML 响应。您可以使用pip install --user sparqlwrapper oauthlib certifi 获取依赖项。然后应该为脚本提供一个文本文件的路径,该文件包含在申请仅所有者令牌后给出的粘贴输出[2]。例如

Consumer token
    deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
Consumer secret
    deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
Access token
    deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
Access secret
    deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef

[1] https://wcqs-beta.wmflabs.org/ ; https://diff.wikimedia.org/2020/10/29/sparql-in-the-shadow-of-structured-data-on-commons/

[2]https://www.mediawiki.org/wiki/OAuth/Owner-only_consumers

import sys
from SPARQLWrapper import JSON, SPARQLWrapper
import certifi
from SPARQLWrapper import Wrapper
from functools import partial
from oauthlib.oauth1 import Client
 
 
ENDPOINT = "https://wcqs-beta.wmflabs.org/sparql"
QUERY = """
SELECT ?file WHERE {
  ?file wdt:P180 wd:Q42 .
}
"""
 
 
def monkeypatch_sparqlwrapper():
    # Deal with old system certificates
    if not hasattr(Wrapper.urlopener, "monkeypatched"):
        Wrapper.urlopener = partial(Wrapper.urlopener, cafile=certifi.where())
        setattr(Wrapper.urlopener, "monkeypatched", True)
 
 
def oauth_client(auth_file):
    # Read credential from file
    creds = []
    for idx, line in enumerate(auth_file):
        if idx % 2 == 0:
            continue
        creds.append(line.strip())
    return Client(*creds)
 
 
class OAuth1SPARQLWrapper(SPARQLWrapper):
    # OAuth sign SPARQL requests

    def __init__(self, *args, **kwargs):
        self.client = kwargs.pop("client")
        super().__init__(*args, **kwargs)
 
    def _createRequest(self):
        request = super()._createRequest()
        uri = request.get_full_url()
        method = request.get_method()
        body = request.data
        headers = request.headers
        new_uri, new_headers, new_body = self.client.sign(uri, method, body, headers)
        request.full_url = new_uri
        request.headers = new_headers
        request.data = new_body
        print("Sending request")
        print("Url", request.full_url)
        print("Headers", request.headers)
        print("Data", request.data)
        return request
 
 
monkeypatch_sparqlwrapper()
client = oauth_client(open(sys.argv[1]))
sparql = OAuth1SPARQLWrapper(ENDPOINT, client=client)
sparql.setQuery(QUERY)
sparql.setReturnFormat(JSON)
results = sparql.query().convert()
 
print("Results")
print(results)

我也尝试过不使用 SPARQLWrapper,但只使用 requests+requests_ouathlib。但是,我遇到了同样的问题 --- 返回了登录页面的 HTML --- 所以看起来它实际上可能是 Wikimedia Commons 查询服务的问题。

import sys
import requests
from requests_oauthlib import OAuth1


def oauth_client(auth_file):
    creds = []
    for idx, line in enumerate(auth_file):
        if idx % 2 == 0:
            continue
        creds.append(line.strip())
    return OAuth1(*creds)


ENDPOINT = "https://wcqs-beta.wmflabs.org/sparql"
QUERY = """
SELECT ?file WHERE {
  ?file wdt:P180 wd:Q42 .
}
"""


r = requests.get(
    ENDPOINT,
    params={"query": QUERY},
    auth=oauth_client(open(sys.argv[1])),
    headers={"Accept": "application/sparql-results+json"}
)


print(r.text)

【问题讨论】:

标签: python oauth sparql wikidata rdflib


【解决方案1】:

免责声明:我是 WCQS 的作者之一(也是问题中链接的文章的作者,显然有点误导)。

这种身份验证方式用于通过 Wikimedia Commons(或任何其他 wikimedia 应用程序)进行身份验证的应用程序,但不适用于 WCQS - 它本身就是通过 Wikimedia Commons 进行身份验证的应用程序。在这种情况下,OAuth 严格用于 Web 应用程序对用户进行身份验证,但目前,您无法使用 OAuth 对机器人和其他应用程序进行身份验证。任何类型的使用都需要用户登录。

这是来自我们当前设置和基础架构的限制,我们计划在投入生产时克服这一限制(服务目前以 beta 状态发布)。不幸的是,我无法告诉您何时会发生这种情况 - 但这对我们很重要。

如果您想在此之前试用您的机器人,您可以随时登录浏览器并在代码中使用令牌,但它必然会过期,并且需要重复该过程。对您的第二个列表进行简单修改即可:

import sys
import requests

ENDPOINT = "https://wcqs-beta.wmflabs.org/sparql"
QUERY = """
SELECT ?file WHERE {
  ?file wdt:P180 wd:Q42 .
}
"""

r = requests.get(
    ENDPOINT,
    params={"query": QUERY},
    headers={"Accept": "application/sparql-results+json", "wcqsSession": "<token retrieved after logging in"}
)


print(r.text)

请注意,直接在 irc (freenode:#wikimedia-discovery) 上询问邮件列表或创建Phabricator 票证是获得 WCQS 帮助的最佳方式。

【讨论】:

    【解决方案2】:

    您为什么不尝试使用requests + OAuth 等“手动”回答 SPARQL 查询,然后,如果可以,您就会知道我们已经与您的应用程序代码中的问题相反,在 SPARQLWrapper 中遇到了错误。

    requests 代码应该类似于以下 + OAuth 内容:

    
    r = requests.get(
        ENDPOINT,
        params={"query": QUERY},
        auth=auth,
        headers={"Accept": "application/sparql-results+json"}
    )
    

    尼克

    【讨论】:

    • 好建议——谢谢。我会尽快尝试并得到结果。
    • 它不起作用,所以我想这一定是维基共享资源查询服务的问题。感谢您帮助我缩小范围!
    【解决方案3】:

    如果您要求进行 MediaWiki OAuth v1 身份验证

    我将此解释为您正在寻找一种仅针对 WikiMedia 站点进行 OAuth 的方法(使用 v1),您的其余代码实际上不是问题的一部分吗? 如果我错了,请纠正我。

    您无需指定您正在开发哪种类型的应用程序,对于使用具有正确后端支持的 Flask 或 Django 的 Web 应用程序,有不同的方法可以使用 OAuth 对 Wikimedia 页面进行身份验证。

    更“通用”的方法是在任何应用程序中使用 mwoauth 库 (python-mwoauth)。 Python 3 和 Python 2 仍然支持它。

    我假设如下:

    • 目标服务器安装了带有 OAuth 扩展的 MediaWiki。
    • 您想与此服务器进行 OAuth 握手以进行身份​​验证。

    使用 Wikipedia.org 作为示例目标平台:

    $ pip install mwoauth

    # Find a suitable place, depending on your app to include the authorization code:
    
    from mwoauth import ConsumerToken, Handshaker
    from six.moves import input # For compatibility between python 2 and 3
    
    # Construct a "consumer" from the key/secret provided by the MediaWiki site
    import config
    consumer_token = ConsumerToken(config.consumer_key, config.consumer_secret)
    
    # Construct handshaker with wiki URI and consumer
    handshaker = Handshaker("https://en.wikipedia.org/w/index.php",
                            consumer_token)
    
    # Step 1: Initialize -- ask MediaWiki for a temporary key/secret for user
    redirect, request_token = handshaker.initiate()
    
    # Step 2: Authorize -- send user to MediaWiki to confirm authorization
    print("Point your browser to: %s" % redirect) #
    response_qs = input("Response query string: ")
    
    # Step 3: Complete -- obtain authorized key/secret for "resource owner"
    access_token = handshaker.complete(request_token, response_qs)
    print(str(access_token))
    
    # Step 4: Identify -- (optional) get identifying information about the user
    identity = handshaker.identify(access_token)
    print("Identified as {username}.".format(**identity))
    
    # Fill in the other stuff :)
    
    

    我可能完全误解了你的问题,如果是这样,请通过我的左耳向我喊。

    GitHub:

    Use the Source, Luke

    这是文档的链接,其中包括一个使用 Flask 的示例: WikiMedia OAuth - Python

    【讨论】:

    • 这与维基共享资源查询服务有关,所以我需要使用 SPARQL 客户端。它类似于 Wikidata SPARQL 端点,但有不同的可用数据选择。请参阅问题中的链接。由于这只是一个供个人使用的脚本,因此我使用的是仅所有者令牌,即不需要用户交互,但令牌只能针对一个帐户运行。因此,不需要涉及 Web 框架。看起来 mwoauth 是围绕 oauthlib 的一个相当薄的包装器。我直接使用 oauthlib,因为我需要将它与 SPARQLWrapper 集成,
    • 好的,我明白了。从来没有使用过,但这是一项有趣的任务,可以获得一些经验。然后我为没有深入研究这个问题而道歉。也许指出这是一个要求,所以你更有可能得到正确的答案。
    • 然后,如果我再次阅读实际的问题标题,那是相当明显的。我的错。
    【解决方案4】:

    我会尝试使用不同的端点运行您的代码。而不是https://wcqs-beta.wmflabs.org/sparql 尝试使用https://query.wikidata.org/sparql。当我使用第一个端点时,我还获得了您获得的登录页面的 HTML 响应,但是,当我使用第二个端点时,我得到了正确的响应:

    from SPARQLWrapper import SPARQLWrapper, JSON
    
    endpoint = "https://query.wikidata.org/sparql"
    sparql = SPARQLWrapper(endpoint)
    
    # Example query to return a list of movies that Christian Bale has acted in:
    query = """
    SELECT ?film ?filmLabel (MAX(?pubDate) as ?latest_pubdate) WHERE {
       ?film wdt:P31 wd:Q11424 .
       ?film wdt:P577 ?pubDate .
       ?film wdt:P161 wd:Q45772 .
      SERVICE wikibase:label {
        bd:serviceParam wikibase:language "en" .
      }
     }
    GROUP BY ?film ?filmLabel
    ORDER BY DESC(?latest_pubdate)
    LIMIT 50
    """
    
    sparql.setQuery(query)
    sparql.setReturnFormat(JSON)
    results = sparql.query().convert()
    
    # Define a quick function to get json into pandas dataframe:
    import pandas as pd
    from pandas import json_normalize
    
    def df_from_res(j):
        df = json_normalize(j['results']['bindings'])[['filmLabel.value','latest_pubdate.value']]
        df['latest_pubdate.value'] = pd.to_datetime(df['latest_pubdate.value']).dt.date
        return df
    
    df_from_res(results).head(5)
    
    
    #   filmLabel.value   latest_pubdate.value
    # 0 Ford v Ferrari    2019-11-15
    # 1 Vice              2019-02-21
    # 2 Hostiles          2018-05-31
    # 3 The Promise       2017-08-17
    # 4 Song to Song      2017-05-25
    

    这个端点也以类似的方式与requests库一起工作:

    import requests
    
    payload = {'query': query, 'format': 'json'}
    
    results = requests.get(endpoint, params=payload).json()
    

    【讨论】:

    • 感谢您的建议,但该端点有不同的可用信息。特别是它没有维基共享资源上的所有结构化数据可用。见:diff.wikimedia.org/2020/10/29/…
    • 啊,是的,我知道你现在要做什么了。我尝试将应用程序连接到我在en.wikipedia.beta.wmflabs.org/wiki 上创建的新帐户以设置所需的凭据,但找不到像以前那样的方法非测试版网站。您是否设法通过该阶段以获得您的凭据? (consumer_key、c​​onsumer_secret、access_token、access_secret)。
    • 好的,我想您已经找到了解决方案。我在错误的 wiki 上获得了一个帐户!我现在会接受您的回答,但请包含有关您需要在哪里注册的信息。 ETA:尚未测试,但急于在到期前给你赏金。 ETA:澄清一下,我试图使用我在非测试版 Wikimedia commons 上创建的身份验证令牌。
    • 不。不工作!虽然我认为肯定会成功。 meta.wikimedia.beta.wmflabs.org/wiki/… 的所有者专用令牌也不适合我。
    • 对不起!我回去尝试了几种不同的方法来以编程方式运行this query...
    猜你喜欢
    • 1970-01-01
    • 2018-02-04
    • 2010-10-21
    • 2021-07-26
    • 1970-01-01
    • 1970-01-01
    • 2015-05-10
    • 2016-10-31
    • 2019-09-03
    相关资源
    最近更新 更多