如何获取所有维基百科文章的标题列表

39

我希望获取所有维基百科文章的标题列表。 我知道从Wikimedia-powered Wiki获取内容有两种可能的方法。 一种是API,另一种是数据库转储。

我不想下载维基转储。 首先,它非常庞大,其次,我对查询数据库没有经验。 另一方面,使用API的问题是,我无法找到仅检索文章标题列表的方法,即使需要超过4百万请求,这可能会导致我被阻止进一步请求。

所以我的问题是:

  1. 是否有通过API仅获取维基百科文章标题的方法?
  2. 是否有将多个请求/查询合并为一个的方法?还是我真的必须下载维基百科转储数据?

1
你可以尝试使用API沙盒或实际的查询 - chridam
4个回答

57

所有页面API模块可以让您做到这一点。当设置aplimit=max时,它的限制是500,因此要查询所有450万篇文章,您需要大约9000个请求。

但是,倾倒数据更好,因为有许多不同的倾卸,包括all-titles-in-ns0,正如其名称所示,包含您想要的内容(59 MB的gzip文本)。


1
太棒了,非常感谢!我正好在寻找这样的转储文件,但是一直没有找到。我想再多搜索一下就能找到这个下载链接了 :) 谢谢! - Flavio
3
“in-ns0”和非“in-ns0”的.gz文件有什么区别?它们的大小也不同。 - zwep
3
区别在于,“in-ns0”仅包含有关命名空间0中页面的信息,即文章。 - svick
1
all-titles-in-ns0 包含的标题只是指向真正标题的重定向。 - user3064538
@zwep 命名空间的文档可以在以下链接找到:https://en.wikipedia.org/wiki/Wikipedia:Namespace。命名空间0是“主要文章”(和重定向),其他命名空间包括“讨论:”,“文件:”,“用户:”和“分类:”等“神奇的东西”。 - undefined
显示剩余3条评论

3

根据当前统计数据,文章数量约为580万。 为了获取页面列表,我使用了AllPages API。然而,我得到的页面数量约为1450万,是我预期的约3倍。我限制自己只获取命名空间0的列表。以下是我正在使用的示例代码:

# get the list of all wikipedia pages (articles) -- English
import sys
from simplemediawiki import MediaWiki

listOfPagesFile = open("wikiListOfArticles_nonredirects.txt", "w")


wiki = MediaWiki('https://en.wikipedia.org/w/api.php')

continueParam = ''
requestObj = {}
requestObj['action'] = 'query'
requestObj['list'] = 'allpages'
requestObj['aplimit'] = 'max'
requestObj['apnamespace'] = '0'

pagelist = wiki.call(requestObj)
pagesInQuery = pagelist['query']['allpages']

for eachPage in pagesInQuery:
    pageId = eachPage['pageid']
    title = eachPage['title'].encode('utf-8')
    writestr = str(pageId) + "; " + title + "\n"
    listOfPagesFile.write(writestr)

numQueries = 1

while len(pagelist['query']['allpages']) > 0:

    requestObj['apcontinue'] = pagelist["continue"]["apcontinue"]
    pagelist = wiki.call(requestObj)


    pagesInQuery = pagelist['query']['allpages']

    for eachPage in pagesInQuery:
        pageId = eachPage['pageid']
        title = eachPage['title'].encode('utf-8')
        writestr = str(pageId) + "; " + title + "\n"
        listOfPagesFile.write(writestr)
        # print writestr


    numQueries += 1

    if numQueries % 100 == 0:
        print "Done with queries -- ", numQueries
        print numQueries

listOfPagesFile.close()

查询次数约为28900次,结果为大约1450万个页面名称。 我也尝试了上面答案提到的all-titles链接。在这种情况下,我也得到了大约1450万个页面。 我认为这对实际页面数量的高估是由于重定向造成的,并在请求对象中添加了“nonredirects”选项:
requestObj['apfilterredir'] = 'nonredirects'

做完这些操作后,我只得到了112340页。与5.8M相比太少了。
使用上述代码,我原本期望能获得大约5.8M个页面,但情况似乎并非如此。
是否有其他选项可以尝试获取实际的(约5.8M)页面名称集?

SimpleMediaWiki是Python 3还是Python 2? - Mark Olson
如果您遇到有关打印语句的错误,可以通过直接从GitHub安装而不是PyPI来避免这种情况:pip install pip install git+https://github.com/iliana/python-simplemediawiki.git - Shayan RC

2
enwiki-latest-page.sql中仅下载非重定向标题 文件enwiki-latest-all-titles-in-ns0由svick提到包含了"实际页面"和重定向页面,例如:

存在,并且第一个重定向到第二个。

然而,可以在转储列表中找到的文件enwiki-latest-page.sql似乎包含足够的信息来获取无需重定向的标题。

在Ubuntu 23.04上,我们可以使用类似以下方式将标题提取到titles.txt文件中:

wget https://dumps.wikimedia.org/enwiki/latest/enwiki-latest-page.sql.gz
gunzip --keep enwiki-latest-page.sql.gz
sed -E '/^  (UNIQUE )?KEY/d;;/^  PRIMARY KEY/{s/,$//}' enwiki-latest-page.sql > enwiki-latest-page-noindex.sql
sudo apt update
sudo apt install mariadb-server
sudo mariadb -e 'create database enwiki'
sudo mariadb enwiki <enwiki-latest-page-noindex.sql
sudo mariadb enwiki -B -N -e 'select page_title from page where page_namespace = 0 and page_is_redirect = 0 order by page_title' > titles.txt
rm enwiki-latest-page.sql.gz enwiki-latest-page.sql enwiki-latest-page-noindex.sql

SQL执行速度非常慢,大约花了17分钟。

截至2023年10月,根据https://en.wikipedia.org/wiki/Wikipedia:Size_of_Wikipedia维基百科有约6.7百万篇文章,而titles.txt文件中也包含了相当数量的行,所以结果看起来是合理的。

我们可以看到enwiki-latest-page.sql创建了这个表:

CREATE TABLE `page` (
  `page_id` int(8) unsigned NOT NULL AUTO_INCREMENT,
  `page_namespace` int(11) NOT NULL DEFAULT 0,
  `page_title` varbinary(255) NOT NULL DEFAULT '',
  `page_is_redirect` tinyint(1) unsigned NOT NULL DEFAULT 0,
  `page_is_new` tinyint(1) unsigned NOT NULL DEFAULT 0,
  `page_random` double unsigned NOT NULL DEFAULT 0,
  `page_touched` binary(14) NOT NULL,
  `page_links_updated` varbinary(14) DEFAULT NULL,
  `page_latest` int(8) unsigned NOT NULL DEFAULT 0,
  `page_len` int(8) unsigned NOT NULL DEFAULT 0,
  `page_content_model` varbinary(32) DEFAULT NULL,
  `page_lang` varbinary(35) DEFAULT NULL,
  PRIMARY KEY (`page_id`),
  UNIQUE KEY `page_name_title` (`page_namespace`,`page_title`),
  KEY `page_random` (`page_random`),
  KEY `page_len` (`page_len`),
  KEY `page_redirect_namespace_len` (`page_is_redirect`,`page_namespace`,`page_len`)
) ENGINE=InnoDB AUTO_INCREMENT=74951970 DEFAULT CHARSET=binary ROW_FORMAT=COMPRESSED;

跟着一堆庞大的INSERT语句。
正如我们所看到的,这个包含了我们想要的titleis_redirect字段。这个表是来自MediaWiki软件的一个实际表,文档可以在这里找到:https://www.mediawiki.org/wiki/Manual:Page_table 另一个令人烦恼的问题是,存在多个索引使得初始导入非常非常慢,所以我们通过使用sed编辑表格来删除除了主键索引之外的所有索引,大大加快了导入速度。在删除这些索引后,导入步骤在我的Lenovo ThinkPad P51上花了17分钟。使用这些语句后,5个小时后还没有完成,我放弃了。以下是相关语句:
/*!40000 ALTER TABLE `page` DISABLE KEYS */;

根据我的系统上执行的情况来看,mysqldump生成的/*!xxxxxx语句*/是什么意思?,并且很可能是由mysqldump添加的,以加快插入速度,因为只在插入后更新索引一次会更快。所以看起来索引生成本身在最后非常慢?

关于没有重定向的标题的具体情况已经在这里提问过了:所有维基百科文章的标题没有重定向

一个相关的维基百科导入问题:将维基百科转储导入到MySql

只是再进行一个时间实验,如果我修改它不包含任何索引,甚至没有主键:

echo page
cat <<'EOF' | time mariadb enwiki
DROP TABLE IF EXISTS `page`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `page` (
  `page_id` int(8) unsigned NOT NULL,
  `page_namespace` int(11) NOT NULL DEFAULT 0,
  `page_title` varbinary(255) NOT NULL DEFAULT '',
  `page_is_redirect` tinyint(1) unsigned NOT NULL DEFAULT 0,
  `page_is_new` tinyint(1) unsigned NOT NULL DEFAULT 0,
  `page_random` double unsigned NOT NULL DEFAULT 0,
  `page_touched` binary(14) NOT NULL,
  `page_links_updated` varbinary(14) DEFAULT NULL,
  `page_latest` int(8) unsigned NOT NULL DEFAULT 0,
  `page_len` int(8) unsigned NOT NULL DEFAULT 0,
  `page_content_model` varbinary(32) DEFAULT NULL,
  `page_lang` varbinary(35) DEFAULT NULL
) ENGINE=InnoDB AUTO_INCREMENT=74951970 DEFAULT CHARSET=binary ROW_FORMAT=COMPRESSED;
/*!40101 SET character_set_client = @saved_cs_client */;
EOF
echo 'insert'
grep -a -e '^INSERT ' enwiki-latest-page.sql | time mariadb enwiki
echo 'index'
time mariadb enwiki -e 'ALTER TABLE `page` ADD PRIMARY KEY `page_pk` (`page_id`)'
time mariadb enwiki -e 'ALTER TABLE `page` ADD UNIQUE KEY `page_name_title` (`page_namespace`,`page_title`)'
#time mariadb enwiki -e 'ALTER TABLE `page` ADD KEY `page_random` (`page_random`)'
#time mariadb enwiki -e 'ALTER TABLE `page` ADD KEY `page_len` (`page_len`)'
#time mariadb enwiki -e 'ALTER TABLE `page` ADD KEY `page_redirect_namespace_len` (`page_is_redirect`,`page_namespace`,`page_len`)'
echo

给出:

  • 插入需要28分钟
  • 添加主键需要20分钟
  • page_name_title需要7分钟

我对MySQL的速度失去了耐心。

我还尝试手动创建一个SQLite表,并运行未修改的INSERT语句,但它不起作用,因为MySQL使用\' SQL扩展来转义单引号,而不是''。这是标准的...

好的,这里有一个家伙试图用自定义脚本来对抗慢速:使用'mysqldump'以CSV格式导出所有表,位于:https://github.com/jamesmishra/mysqldump-to-csv

sed

如果你真的想要过快节奏的生活,这里有一个尝试:

cat enwiki-latest-page.sql |
  grep -E '^INSERT' |
  sed 's/),(/\n/g' |
  grep -E '^[^,]+,0,' |
  perl -lane "s/^[^']+'//;s/(,[^,]+){8}$//; if (substr(\$_, -1) == '0') { print substr(\$_, 0, -3) }" |
  sed 's/\\\\/\\/g;s/\\'"'"'/'"'"'/g;s/\\"/"/g' \
> titles-sed.txt
sort -o titles-sed.txt titles-sed.txt

这在我的联想ThinkPad P51上大约花了5分钟,生成了完全相同的文本文件,除了处理了极少数包含反斜杠\的标题,例如:https://en.wikipedia.org/wiki/TBWA%5CChiat%5CDay 由于某种原因,MySQL输出包含双反斜杠。不确定它是硬编码在数据库本身中还是IO问题。查看enwiki-latest-all-titles-in-ns0,我发现它包含单个反斜杠的TBWA/Chiat/Day,所以这一定是一个简单的IO问题。
后来我了解到,可以通过awk FPAT来大大改进这个问题,具体方法如下:What's the most robust way to efficiently parse CSV using awk? 这是一个关于sed的上传链接: https://archive.org/details/enwiki-latest-all-titles-in-ns0-no-redirects mysqldump-to-csv https://dev59.com/5Gct5IYBdhLWcg3wgNnR#28168617指向了https://github.com/jamesmishra/mysqldump-to-csv,它可以将转储文件半自动地转换为CSV格式,从而绕过MySQL的速度问题。
git clone https://github.com/jamesmishra/mysqldump-to-csv
cd mysqldump-to-csv
git checkout 24301dfa739c13025844ed3ff5a8abe093ced6cc
patch <<'EOF'
diff --git a/mysqldump_to_csv.py b/mysqldump_to_csv.py
index b49cfe7..8d5bb2a 100644
--- a/mysqldump_to_csv.py
+++ b/mysqldump_to_csv.py
@@ -101,7 +101,8 @@ def main():
     # listed in sys.argv[1:]
     # or stdin if no args given.
     try:
-        for line in fileinput.input():
+        sys.stdin.reconfigure(errors='ignore')
+        for line in fileinput.input(encoding="utf-8", errors="ignore"):
             # Look for an INSERT statement and parse it.
             if is_insert(line):
                 values = get_values(line)
EOF

然后我按照如何提取CSV文件的一列中的方法添加了csvtool,并使用awk来过滤列:
time zcat enwiki-latest-page.sql.gz | python mysqldump-to-csv/mysqldump_to_csv.py | csvtool format '%(2) %(4) %(3)\n' - | awk '$1==0 && $2==0 {print $3}' > titles-csv.txt
sort -o titles-csv.txt titles-csv.txt

这在我的联想ThinkPad P51上花了5.5分钟,结果与sed方法完全相同,这让人放心。所以这个方法虽然快,但更加简洁,而且能够处理其他具有原始换行符的表格。 获取类别层次内的页面 我对这个相关主题进行了一些探索,参考链接:Wikipedia Category Hierarchy from dumps*

-1
这是一个异步程序,它将生成Mediawiki页面标题:
async def wikimedia_titles(http, wiki="https://en.wikipedia.org/"):
    log.debug('Started generating asynchronously wiki titles at {}', wiki)
    # XXX: https://www.mediawiki.org/wiki/API:Allpages#Python
    url = "{}/w/api.php".format(wiki)
    params = {
        "action": "query",
        "format": "json",
        "list": "allpages",
        "apfilterredir": "nonredirects",
        "apfrom": "",
    }

    while True:
        content = await get(http, url, params=params)
        if content is None:
            continue
        content = json.loads(content)

        for page in content["query"]["allpages"]:
            yield page["title"]
        try:
            apcontinue = content['continue']['apcontinue']
        except KeyError:
            return
        else:
            params["apfrom"] = apcontinue

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接