从维基百科解析出生和死亡日期?

时间:2012-09-03 15:24:54

标签: python mediawiki wikipedia wikipedia-api mediawiki-api

我正在尝试编写一个python程序,可以在维基百科上搜索人的出生和死亡日期。

例如,阿尔伯特爱因斯坦出生于1879年3月14日;死亡:1955年4月18日。

我从Fetch a Wikipedia article with Python

开始
import urllib2
opener = urllib2.build_opener()
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
infile = opener.open('http://en.wikipedia.org/w/api.php?action=query&prop=revisions&rvprop=content&rvsection=0&titles=Albert_Einstein&format=xml')
page2 = infile.read()

这就可以了。 page2是来自Albert Einstein维基百科页面的部分的xml表示。

我看了这个教程,现在我有xml格式的页面...... http://www.travisglines.com/web-coding/python-xml-parser-tutorial,但我不明白如何获取我想要的信息(出生和死亡日期) XML。我觉得我必须亲近,但是,我不知道如何从这里开始。

修改

经过几次回复后,我安装了BeautifulSoup。我现在正处于可以打印的阶段:

import BeautifulSoup as BS
soup = BS.BeautifulSoup(page2)
print soup.getText()
{{Infobox scientist
| name        = Albert Einstein
| image       = Einstein 1921 portrait2.jpg
| caption     = Albert Einstein in 1921
| birth_date  = {{Birth date|df=yes|1879|3|14}}
| birth_place = [[Ulm]], [[Kingdom of Württemberg]], [[German Empire]]
| death_date  = {{Death date and age|df=yes|1955|4|18|1879|3|14}}
| death_place = [[Princeton, New Jersey|Princeton]], New Jersey, United States
| spouse      = [[Mileva Marić]] (1903–1919)<br>{{nowrap|[[Elsa Löwenthal]] (1919–1936)}}
| residence   = Germany, Italy, Switzerland, Austria, Belgium, United Kingdom, United States
| citizenship = {{Plainlist|
* [[Kingdom of Württemberg|Württemberg/Germany]] (1879–1896)
* [[Statelessness|Stateless]] (1896–1901)
* [[Switzerland]] (1901–1955)
* [[Austria–Hungary|Austria]] (1911–1912)
* [[German Empire|Germany]] (1914–1933)
* United States (1940–1955)
}}

所以,更接近,但我仍然不知道如何以这种格式返回death_date。除非我开始使用re解析内容?我可以这样做,但我觉得我会使用错误的工具来完成这项工作。

6 个答案:

答案 0 :(得分:8)

您可以考虑使用BeautifulSouplxml等库来解析响应html / xml。

您可能还想查看Requests,它有更清晰的API来发送请求。


以下是使用RequestsBeautifulSoupre的工作代码,这可能不是最佳解决方案,但它非常灵活,可以针对类似问题进行扩展:

import re
import requests
from bs4 import BeautifulSoup

url = 'http://en.wikipedia.org/w/api.php?action=query&prop=revisions&rvprop=content&rvsection=0&titles=Albert_Einstein&format=xml'

res = requests.get(url)
soup = BeautifulSoup(res.text, "xml")

birth_re = re.search(r'(Birth date(.*?)}})', soup.revisions.getText())
birth_data = birth_re.group(0).split('|')
birth_year = birth_data[2]
birth_month = birth_data[3]
birth_day = birth_data[4]

death_re = re.search(r'(Death date(.*?)}})', soup.revisions.getText())
death_data = death_re.group(0).split('|')
death_year = death_data[2]
death_month = death_data[3]
death_day = death_data[4]

Per @ JBernardo建议使用JSON数据和mwparserfromhell,这个特定用例的更好答案:

import requests
import mwparserfromhell

url = 'http://en.wikipedia.org/w/api.php?action=query&prop=revisions&rvprop=content&rvsection=0&titles=Albert_Einstein&format=json'

res = requests.get(url)
text = res.json["query"]["pages"].values()[0]["revisions"][0]["*"]
wiki = mwparserfromhell.parse(text)

birth_data = wiki.filter_templates(matches="Birth date")[0]
birth_year = birth_data.get(1).value
birth_month = birth_data.get(2).value
birth_day = birth_data.get(3).value

death_data = wiki.filter_templates(matches="Death date")[0]
death_year = death_data.get(1).value
death_month = death_data.get(2).value
death_day = death_data.get(3).value

答案 1 :(得分:5)

首先:维基百科API允许使用 JSON 而不是XML,这将使事情变得更加容易。

第二:根本不需要使用HTML / XML解析器(内容不是HTML,也不是容器)。您需要解析的是JSON的“revisions”标记内的 Wiki 格式。

检查一些Wiki解析器here


这里似乎令人困惑的是,API允许您请求某种格式(XML或JSON),但这只是您要解析的实际格式的某些文本的容器:

这一个:{{Birth date|df=yes|1879|3|14}}

使用上面链接中提供的解析器之一,您将能够做到这一点。

答案 2 :(得分:4)

首先,使用pywikipedia。它允许您通过高级抽象接口查询文章文本,模板参数等。其次,我会使用Persondata模板(看看文章末尾)。此外,从长远来看,您可能会对Wikidata感兴趣,这将花费几个月的时间来介绍,但它会使维基百科文章中的大多数元数据都易于查询。

答案 3 :(得分:1)

现在不推荐使用persondata模板,您应该访问维基数据。见Wikidata:Data access。我之前的(现已弃用)2012年答案如下:

  

您应该做的是解析大多数传记文章中的{{persondata}}模板。有existing tools for easily extracting such data programmatically,您现有的知识和其他有用的答案,我相信您可以做到这一点。

答案 4 :(得分:0)

2019年的一种替代方法是使用Wikidata API,该API除其他外,以结构化格式公开诸如出生日期和死亡日期之类的传记数据,无需任何自定义解析器就可以很容易地使用它。许多Wikipedia文章都依赖Wikidata来获取信息,因此在许多情况下,这与使用Wikipedia数据时是相同的。

例如,查看Wikidata page for Albert Einstein并搜索“出生日期”和“死亡日期”,您会发现它们与Wikipedia中的相同。 Wikidata中的每个实体都有一个“声明”列表,该列表是“属性”和“值”对。要知道爱因斯坦何时出生和去世,我们只需要在语句列表中搜索适当的属性,在本例中为P569P570。要以编程方式执行此操作,最好将实体作为json访问,您可以使用以下网址结构进行操作:

https://www.wikidata.org/wiki/Special:EntityData/Q937.json

作为示例,以下是P569关于爱因斯坦的声明:

        "P569": [
          {
            "mainsnak": {
              "property": "P569",
              "datavalue": {
                "value": {
                  "time": "+1879-03-14T00:00:00Z",
                  "timezone": 0,
                  "before": 0,
                  "after": 0,
                  "precision": 11,
                  "calendarmodel": "http://www.wikidata.org/entity/Q1985727"
                },
                "type": "time"
              },
              "datatype": "time"
            },
            "type": "statement",

您可以在this article中了解有关访问Wikidata的更多信息,更具体地说,可以在Help:Dates中了解日期的结构。

答案 5 :(得分:0)

我遇到了这个问题,并感谢 @Yoshikianswer 中提供的所有有用信息,但需要进行一些综合才能找到可行的解决方案。在这里分享,以防对其他人有用。代码也在 this gist 中,供那些希望分叉/改进它的人使用。

特别是,这里的错误处理方式并不多......

import csv
from datetime import datetime
import json
import requests
from dateutil import parser


def id_for_page(page):
    """Uses the wikipedia api to find the wikidata id for a page"""
    api = "https://en.wikipedia.org/w/api.php"
    query = "?action=query&prop=pageprops&titles=%s&format=json"
    slug = page.split('/')[-1]

    response = json.loads(requests.get(api + query % slug).content)
    # Assume we got 1 page result and it is correct.
    page_info = list(response['query']['pages'].values())[0]
    return  page_info['pageprops']['wikibase_item']


def lifespan_for_id(wikidata_id):
    """Uses the wikidata API to retrieve wikidata for the given id."""
    data_url = "https://www.wikidata.org/wiki/Special:EntityData/%s.json"
    page = json.loads(requests.get(data_url % wikidata_id).content)

    claims = list(page['entities'].values())[0]['claims']
    # P569 (birth) and P570 (death) ... not everyone has died yet.
    return [get_claim_as_time(claims, cid) for cid in ['P569', 'P570']]


def get_claim_as_time(claims, claim_id):
    """Helper function to work with data returned from wikidata api"""
    try:
        claim = claims[claim_id][0]['mainsnak']['datavalue']
        assert claim['type'] == 'time', "Expecting time data type"

        # dateparser chokes on leading '+', thanks wikidata.
        return parser.parse(claim['value']['time'][1:])
    except KeyError as e:
        print(e)
        return None


def main():
    page = 'https://en.wikipedia.org/wiki/Albert_Einstein'

    # 1. use the wikipedia api to find the wikidata id for this page
    wikidata_id = id_for_page(page)

    # 2. use the wikidata id to get the birth and death dates
    span = lifespan_for_id(wikidata_id)

    for label, dt in zip(["birth", "death"], span):
        print(label, " = ", datetime.strftime(dt, "%b %d, %Y"))