置顶公告:【置顶】关于临时开启评论区所有功能的公告(2022.10.22) | 【置顶】关于本站Widget恢复使用的公告
  • 你好~!欢迎来到萌娘百科镜像站!如需查看或编辑,请联系本站管理员注册账号。
  • 本镜像站和其他萌娘百科的镜像站无关,请注意分别。

Help:使用Python编辑

贴贴♀百科,万娘皆可贴的百科全书!转载请标注来源页面的网页链接,并声明引自贴贴百科。内容不可商用。
跳到导航 跳到搜索
Commons-emblem-notice.svg
这个页面“Help:使用Python编辑”是萌娘百科的帮助文档
  • 本文用于介绍萌娘百科中一些特定功能的操作方法;
  • 本文仅是一篇论述,不属于方针或指引。如果本指南与相关方针或指引发生冲突或存在不一致的情况,请以方针或指引的条文为准。

这个指南会简单介绍一下如何使用 Python 创建一个维护 wiki 的自动程序/机器人(bot),它可用于在 wiki 上面进行大量编辑或者删除等维护操作。使用它的好处在于,只要您编写过 Python程序,就可以轻松上手。

注意:阅读以下内容,我们假定您拥有 Python 和命令行的基本知识。欲学习 Python,请前往官方文档等其他站点。

配置

安装Python

配置Python

Pywikibot是目前最常用的wiki编辑Python库。除此之外,我们还会用到wikitextparserrequests。只需在命令行[注 1]中输入

python3 -m pip install requests
python3 -m pip install wikitextparser

即可。注意:由于部分操作系统同时包括Python2和Python3,因此推荐在命令中使用python3而不是python。但如果python3命令无法被识别,请使用python命令。

创造初始工程文件夹

官网下载初始文件“core_stable.zip”,将其解压。

解压后会出现一个叫“core_stable”的文件夹,在文件夹中创建一个名为user-config.py的文件[注 2]。文件的内容为:

mylang = 'mgp'
family_files['mgp'] = 'https://zh.moegirl.org.cn/api.php'
family = 'mgp'
# 这里使用您自己的用户名
usernames['mgp']['*'] = '用户名'
password_file = "user-password.py"

然后,创建一个名为user-password.py的文件存储用户名和密码。这里的账号必须是自动确认用户,否则会因为验证码编辑失败。

如果没有创建bot账号(见下文),文件内容为:

# 强烈不推荐这种登录方式:将不加密的密码存储在文件中是很大的安全隐患。
('mgp', 'mgp', '用户名', '密码')

如果已有bot账号,文件内容为:

('mgp', 'mgp', '用户名', BotPassword('bot用户名', 'bot密码'))

因为pywikibot目前的登录逻辑和本站对部分API的限制。请将core_stable/pywikibot/site/_apisite.py文件APISite类的get_token()方法中

        if not types or load_all is not False:
            pdata = self._paraminfo.parameter('query+tokens', 'type')
            assert pdata is not None
            types = pdata['type']

部分代码替换为:

        if not types or load_all is not False:
            if self.logged_in():
                pdata = self._paraminfo.parameter('query+tokens', 'type')
                assert pdata is not None
                types = pdata['type']
            else:
                types = ['login']

接下来,在控制台中使用cd命令进入“core_stable”文件夹,并执行以下命令[注 3]

python3 pwb.py login

登录,此时会出现数行文字,最后一行应为提示登录成功[注 4]

Logged in on mgp:mgp as 用户名.

创建您的 bot 账号(可选)

  1. 一般来说,我们不希望您使用自己的主要账号运行自动程序,这不仅不合理,而且难以维护和监管,因此我们建议您另外注册一个账号作为 bot 账号。
  2. 然后,您还可以为您的 bot 账号申请一个 bot 用户组,明确表示这是一个由他人操作自动程序进行编辑的子账号,并且可以在最近更改中隐藏批量操作以防止扰乱社区巡查工作。您可以在萌娘百科:机器人#申请与授权了解相关信息。
  3. 最后,这一点相当关键:前往您 wiki 的 Special:BotPasswords 页面,为您的 bot 账号创建一个机器人密码,按提示操作,并保存最后得到的密钥,您之后会用到它。

编辑示例1: Hello World

在解压的pywikibot文件夹中新建一个名为main.py的文件(与config.py处于同一个文件夹)。文件的内容为:

# 导入pywikibot
import pywikibot as pwb

# 获取名为Help:沙盒的页面
page = pwb.Page(pwb.Site(), "Help:沙盒")

# 给页面已有的内容添加Hello World
page.text += "Hello World"

# 可选:查看更改后的wikitext文本
print(page.text)

# 保存页面,编辑摘要为"测试pywikibot"
page.save(summary="测试pywikibot")

在控制台输入python3 main.py运行机器人,稍等片刻后,输出应为

...(页面已有内容)
Hello World
Page [[Help:沙盒]] saved

前往萌百,会发现这次修改已经在沙盒中生效。

编辑示例2:删除模板参数

有一天,萌百的维护组给全体编辑们布置了一个艰巨的任务:因为{{YoutubeCount}}废弃了fallback参数,所以每一个使用YoutubeCount模板的页面都要被修改。例如

{{YoutubeCount|id=0KK5vQlCVYo|fallback=2968013}}

应改为

{{YoutubeCount|id=0KK5vQlCVYo}}

维护组已经给出了需要修改的条目的列表,但是除了少数想刷编辑的用户外,没有人愿意干这种毫无技术含量的体力活,删除参数的事情因而进展缓慢[注 5]。这时,聪明的你突然想到可以用Python完成批量删除参数,因此写出了以下代码用来测试。

# 导入pywikibot和wikitextparser
import pywikibot as pwb
import wikitextparser as wtp

# 获取一个页面,用页面名代替沙盒
page = pwb.Page(pwb.Site(), "Help:沙盒")
# 使用wikitextparser处理页面
parsed = wtp.parse(page.text)

# 遍历页面中的每一个模板
for t in parsed.templates:
    # 如果模板是YoutubeCount,则删除fallback参数
    if t.name == 'YoutubeCount':
        t.del_arg("fallback")

# 用转换后的wikitext替换已有的wikitext
page.text = str(parsed)

# 保存页面并标记为小编辑(如果没有做出任何更改,会提示保存成功,但是页面不会有任何变化)
page.save(summary="删除fallback参数", minor=True)

接下来,只要读取需要更改的条目列表,然后对每个条目重复以上步骤即可。

除了用于删除参数的del_arg,wikitextparser还提供了用于获取模板参数的get_arg和用于添加、修改参数的set_arg,以及更多功能。详情见API文档和下一个示例。

编辑示例3:再生机器人

假如有这样一个模板[注 6]

{{VOCALOID_Songbox
|乱七八糟的参数
|yt_id = 0KK5vQlCVYo
|其他资料 = 于2014年10月5日投稿至niconico,再生数为{{NiconicoCount|id=sm24626484}}<br>同日投稿至YouTube,再生数为1,993,000+
}}

突然,我们得知{{YoutubeCount}}可以自动获取YouTube视频的播放量,因此原有的"1,993,000+"可以被替换为{{YoutubeCount|id=0KK5vQlCVYo}}。因为需要修改的页面过多,所以需要机器人辅助编辑。

程序的大概思路是:

  1. 获取所有VOCALOID条目。对于每一个条目,做以下的事情:
  2. 获取页面的wikitext代码。
  3. 因为一个条目可能有多个{{VOCALOID_Songbox}},如表里,所以应该选择wikitext中的一个模板进行编辑。
  4. 找到该模板中YouTube视频的id,也就是参数“yt_id”对应的值。
  5. 找到播放量曾经的位置,也就是“其他资料”参数中的“1,993,000+”。
  6. 将“1,993,000+”替换为{{YoutubeCount|id=0KK5vQlCVYo}}
  7. 保存更改。

虽然可以通过正则匹配查找模板和参数,但是有更简单的方法:使用wikitextparser。以下是编辑一个条目的代码:

# 导入pywikibot和wikitextparser
import pywikibot as pwb
import wikitextparser as wtp
# 使用正则查找播放量
import re

# 获取一个页面,用页面名代替沙盒
page = pwb.Page(pwb.Site(), "Help:沙盒")
# 使用wikitextparser处理页面
parsed = wtp.parse(page.text)
# 记录页面有无变化
changed = False

# 遍历页面中的每一个模板,并找出所有的VOCALOID_Songbox。
song_boxes = []
for t in parsed.templates:
    # 添加所有名字是VOCALOID_Songbox的模板
    if "VOCALOID_Songbox" == t.name.strip():
        song_boxes.append(t)

# 遍历所有songbox
for t in song_boxes:
    # 获取YouTube视频的id和“其他资料”参数的内容
    # 使用get_arg返回的是一个对象,包含name(参数名)和value(参数值)
    # 不存在的参数则会返回None
    yt_id = t.get_arg("yt_id")
    other_info = t.get_arg("其他资料")
    # 如果两者中的任意一个不存在,则跳过该模板(有可能YouTube无投稿)
    if yt_id is None or other_info is None:
        continue
    # 可选:此时的yt_id.value就是YouTube视频的id
    print(yt_id.value.strip())
    # 使用正则查找播放量,仅为简单例子,实际使用时的正则会更复杂。
    match = re.search("[0-9][0-9,]+[+]", other_info.value)
    # 如果找不到播放量,跳过
    if match is None:
        continue
    # 找出数字播放量开始和结束的位置
    start = match.start()
    end = match.end()
    # 新模板。使用strip去除YouTube视频id中的空格。
    template = "{{YoutubeCount|id=" + yt_id.value.strip() + "}}"
    # 修改“其他信息”的值,将原有内容中的数字部分替换为模板
    other_info.value = other_info.value[:start] + template + other_info.value[end:]
    # 标记页面已修改
    changed = True

# 如果页面被修改过,保存更改,否则告知用户条目没有被修改
if changed:
    # 将修改后的wikitext对象转换为字符串,并用它覆盖页面原有的内容
    page.text = str(parsed)
    # 保存编辑
    # minor: 是否是小编辑
    # botflag: 机器人flag,如果没有机器人或机器用户权限,则没有任何影响。
    # tags: 加入编辑标签,用管道符|分割多个标签。Automation tool指的是“由半自动化脚本或半自动化工具执行的操作”。test指的是测试。
    # watch: 是否添加至监视列表,"nochange"指的是不改变监视列表
    # 可自行在萌百的[https://zh.moegirl.org.cn/api.php api页面]中了解对应的意义
    page.save(summary="测试", minor=True, botflag=True, watch="nochange", tags="Automation tool|test")
else:
    print("没有对页面作出任何修改。")

能够转换一个条目后,接下来只需要获取所有分类为“使用VOCALOID的歌曲”的条目,然后转换每个条目。贴心的官方已经写好了代码,只要会调用即可。

需要注意的是,代码示例中展示了如何避免常见错误,例如参数不存在。但是也跳过了很多可能遇到的坑,比如模板名的对比是会被首字母是否大写影响的,因此很可能出现“lj”不等于“Lj”的状况。

小提示

选用编辑器

如果您还在使用类似记事本的编辑器,很快就会发现诸如缩进、括号配对、拼写错误等问题难以发现。因为记事本的功能过于贫乏,所以建议您选用更强大的工具编写代码:

  1. Sublime Text是一个轻量级的文本编辑器。如果您只是想使用最基本的功能(如文本高亮)或你的电脑配置较差,Sublime Text可以在占用较少资源的前提下满足您的需求。
  2. Visual Studio Code与Sublime Text类似,但是功能更多,且支持大量插件满足您不同的需求,是很受欢迎的选择。
  3. PyCharm分为社区版免费版专业版收费版。收费版可以试用。PyCharm集成了大量功能(如虚拟环境,性能分析工具等),并且可以添加各类插件,因此资源占用较多,也需要一定时间学习如何使用。

编辑频率

根据现行方针,使用自动化工具编辑时需要注意编辑频率,避免刷屏最近更改。

示例:使用MediaWiki api获取用户贡献

虽然Pywikibot功能丰富,但有时会遇到以下情景

  1. 需要的功能Pywikibot无法提供
  2. Pywikibot提供的功能过于复杂。例如,它常常会向网站发送请求确认用户有编辑权限,但是对于知道自己已经登录并且有编辑权限的用户,这一步就是多余的。

如何使用MediaWiki api

此时可以使用MediaWiki自带的api操作。帮助页面给出了可用的api列表。虽然直接使用MediaWiki api花费的时间更多且更容易出错,但是用户可以最大程度地控制程序的行为。

以获取用户的所有贡献,并分析不同名字空间的编辑次数为例。

首先前往帮助页面查找需要的功能。用户贡献在query操作的usercontribs列表中。

帮助页面给出的例子是“[1]”,该页面列举了User:Example的最近10条贡献。但是,为了统计用户所有编辑的名字空间,有以下几个问题需要解决。

  1. 用户名不一定是Example。这点很好解决,将ucuser参数的值改为目标用户的用户名即可。
  2. 需要获取一个用户的所有贡献,而不仅仅是最近的10个。解决方法是使用continue参数中的uccontinue,向服务器发送第二个请求,要求服务器从上一次中断的地方继续列举用户的贡献。具体用法见下文的代码。
  3. 虽然有办法获取所有贡献,但是一次只能获取10个效率太低。由帮助页面可知,可以将uclimit参数改为500(非机器用户的上限),一次获取500个贡献。
  4. api返回的信息包含每一次编辑的用户名、用户id、页面id、编辑id、页面名字空间、页面标题、编辑时间和编辑摘要等内容。但是程序只需要页面的名字空间,其它信息都是多余的。因此,可以使用ucprop参数指定返回的信息束。ucprop的参数之一title的描述是“添加页面标题及其名字空间ID”,因此只需要title即可。
  5. api返回的是HTML页面。虽然可读性很高,但很难被程序处理。程序只需要json数据,因此需要加上format=json让api返回纯json数据。

综上所述,请求的链接应为“[2]”。将链接中的Example替换为目标用户名就可以获取用户的贡献。

使用Python获取最近500次贡献

第一次尝试如下:

import requests
import json
import urllib

# 向萌百的服务器发送请求,这里以U:Lihaohong为例。为了保证可读性,代码被分成了好几行。
result = requests.get("https://zh.moegirl.org.cn/api.php?"
                      "action=query&list=usercontribs&"
                      # 注意:在url中输入非拉丁字母和非数字的字符需要用urllib.parse.quote额外处理
                      "ucuser=" + urllib.parse.quote("Lihaohong") + "&"
                      "uclimit=500&"
                      "ucprop=title&"
                      "format=json")
# 将返回的结果转化为json数据
data = json.loads(result.text)
# 输出结果
print(data)

输出的json数据如下

{
  'batchcomplete': '',
  'continue': {
    # 这里的数值可以用来发送下一次请求获取“下一页”贡献。
    'uccontinue': '20220415095523|5841956',
    'continue': '-||'
  },
  'query': {
    'usercontribs': [
      {
        'userid': 741745,
        'user': 'Lihaohong',
        'ns': 12,
        'title': 'Help:沙盒'
      },
      {
        'userid': 741745,
        'user': 'Lihaohong',
        'ns': 0,
        'title': '夜颜的告白'
      },
      {
        'userid': 741745,
        'user': 'Lihaohong',
        'ns': 0,
        'title': '最强Tettoteto计划'
      },
      {
        'userid': 741745,
        'user': 'Lihaohong',
        'ns': 0,
        'title': '追悔'
      }
      # 后面还有很多...但是总共只有500条。
    ]
  }
}

显然,query中的usercontribs数组存储了用户的编辑,其中的ns包含每次编辑的名字空间信息。

使用Python获取所有贡献

在统计名字空间之前,需要先使用uccontinue参数获取用户的全部贡献,而不仅仅是前500个。

import requests
import json

# 用列表(数组)存储所有贡献
contributions = []
# 一开始,不需要uccontinue参数
cont = ""
while True:
    # 将uccontinue参数加入请求。注意:一开始cont为空字符串。
    result = requests.get("https://zh.moegirl.org.cn/api.php?"
                          "action=query&list=usercontribs&"
                          "ucuser=Lihaohong&"
                          "uclimit=500&"
                          "ucprop=title&"
                          "format=json" + 
                          cont)
    # 将返回的结果转化为json数据
    data = json.loads(result.text)
    # 提取贡献,并把贡献存入contributions
    contributions.extend(data['query']['usercontribs'])
    
    # 如果api返回的数据中包含continue,表明还有更多贡献未获取
    if 'continue' in data: 
        # 将uccontinue参数放入cont变量,用于下一次循环的get请求
        cont = "&uccontinue=" + data['continue']['uccontinue']
    else: # 如果不包含continue,说明所有贡献已被读取完毕,可以退出循环
        break

# 输出所有贡献
print(contributions)
# 确保贡献数正确
print(len(contributions))

运行后发现,贡献数与用户的真实贡献数相同[注 7]

使用Python分析数据

接下来,只要统计每个名字空间出现多少次即可。Python的Counter类可以快速统计数组中的每个数字出现了多少次。除此之外,还可以使用字典将数字转换为文字。

from collections import Counter
namespace_dict = {
    0: "主", 1: "讨论",
    2: "用户", 3: "用户讨论",
    4: "萌娘百科", 5: "萌百讨论",
    6: "文件", 7: "文件讨论",
    8: "MW", 9: "MW讨论",
    10: "模板", 11: "模板讨论",
    12: "帮助", 13: "帮助讨论",
    14: "分类", 15: "分类讨论",
    274: "小部件", 275: "小部件讨论",
    828: "模块", 829: "模块讨论"
}
# 这里省略了之前获取contributions列表的代码
# 获取每次编辑的名字空间,并将其转换为文字(如果namespace_dict没有对应的文字,则保留数字)。最后用Counter统计每个名字空间出现了多少次。
counter = Counter([namespace_dict.get(c['ns'], c['ns'])
                  for c in contributions])
# 按照编辑次数给所有名字空间排序
t = sorted(list(counter.items()), key=lambda p: p[1], reverse=True)
print(t)

用户Lihaohong的输出为

[('主', 1253), ('用户', 279), ('帮助', 19), ('用户讨论', 16), ('萌娘百科讨论', 12), ('讨论', 10), ('模板', 9), ('模板讨论', 2), ('萌娘百科', 1)]

绘制图表

最后,熟悉Python绘制图表工具,例如matplotlib的用户,可以绘制贡献的饼状图和柱状图。

import matplotlib
import matplotlib.pyplot as plt

# 部分操作系统中,matplotlib的默认字体不支持中文,因此需要手动指定字体。
matplotlib.rcParams['font.family'] = "Heiti TC"
matplotlib.rcParams['axes.unicode_minus'] = False

# 把名字空间和编辑次数放入两个不同的列表。
namespaces, edits = zip(*t)
# 第一张图:饼状图
plt.figure(1, [10, 6])
patches, texts = plt.pie(edits, labels=namespaces)
plt.legend(patches, [f"{p[0]}: {p[1]}" for p in t], 
           loc='center right', bbox_to_anchor=(0, 0.5), fontsize=15)
plt.savefig("pie.png")
plt.show()
# 第二张图:柱状图
plt.figure(2, [len(namespaces), 6])
bar_plot = plt.bar(namespaces, edits, color=[p.get_facecolor() for p in patches])
plt.bar_label(bar_plot)
plt.savefig("bar.png")
plt.show()

应对WAF

对于大多数用户,本工具已经够用了。但是查询编辑量极大的用户的所有编辑需要大量(超过100个)请求,极有可能被WAF。解决方法如下:

  1. 使用sleep函数,每发送一个请求就休眠数秒,减少被WAF的可能性。
  2. 使用mzh域名。虽然很少被WAF,但是会被502。
  3. 如果被WAF,保存已有进度。下次启动程序时从已有进度开始继续发送请求。

以下是使用mzh域名的例子:

# 仅展示如何获取单页数据
# 循环,直到成功获取数据
while True:
    try:
        result = requests.get("https://mzh.moegirl.org.cn/api.php?"
                              "action=query&list=usercontribs&"
                              "ucuser=" + urllib.parse.quote("Lihaohong") + "&"
                              "uclimit=500&"
                              "ucprop=title&"
                              "format=json" + 
                              cont)
        # 将返回的结果转化为json数据
        data = json.loads(result.text)
        # 如果成功获取数据,退出循环
        break
    except Exception as e: # 如有Exception,多半是502了
        # 输出错误信息,并重试
        print(str(e), "retrying...")

最终效果

查询萌百某用户的编辑记录。在发送上百个请求后,得到以下结果。

[('主', 33674), ('萌百讨论', 5931), ('模板', 5783), ('分类', 4007), ('用户', 3877), ('MW', 2887), ('用户讨论', 2365), ('萌娘百科', 862), ('讨论', 794), ('帮助', 450), ('模板讨论', 404), ('模块', 119), ('MW讨论', 102), ('小部件', 72), ('分类讨论', 17), ('帮助讨论', 9), ('小部件讨论', 7), ('模块讨论', 5)]

示例:使用API获取VOCALOID歌曲条目及其重定向信息

当你爆肝殿堂曲列表时,常常需要在萌百站内搜索殿堂列表中的曲目日文标题,以确保列表中的内链能正确地指向歌曲条目。

这时,你发现有亿些歌曲在站内有条目,却没有建立原名重定向,以致血压升高。于是你打算整理出站内所有日文VOCALOID曲目的重定向信息,以备筛查。

查询query操作文档后,发现可以通过list=search搜索代码获取位于Cat:使用VOCALOID的歌曲Cat:日本音乐作品分类中的条目、通过prop=redirects获取至某条目的重定向页面列表。

代码(仅作示例,不保证正确性,可以不看
import requests
import json

song_type = "VOCALOID"

def get_pages(offset: int):
    # search通过偏移值来获取更多数据,因此这里不再使用API返回的continue数据
    return json.loads(requests.get("https://zh.moegirl.org.cn/api.php", params={
        "action": "query",
        "list": "search",
        "srsearch": f"incategory:使用{song_type}的歌曲 incategory:日本音乐作品",
        "srlimit": 500,
        "sroffset": offset,
        "continue": "-||",
        "format": "json"}).text)['query']

# titles参数长度限制为50,需切块处理
def get_redirects(pages: list[str]) -> requests.Response:
    return (json.loads(requests.get("https://zh.moegirl.org.cn/api.php", params={
        "action": "query",
        "prop": "redirects",
        "titles": '|'.join(pages),
        "rdlimit": 500,
        "format": "json"}).text))['query']

def get_all_redirects(pages: list[str]) -> requests.Response:
    result = {'pages': {}}
    for i in range(0, len(pages), 50):
        result['pages'].update(get_redirects(pages[i:i+50])['pages'])
    return result

data = get_pages(0)
total_count = data['searchinfo']['totalhits']
pages = {x['pageid']: x for x in data['search']}
datar = get_all_redirects([x['title'] for x in data['search']])['pages']
for k, v in datar.items():
    if 'redirects' in v:
        pages[int(k)]['redirects'] = v['redirects']

for i in range(500, total_count, 500):
    data = get_pages(i)['search']
    pages.update({x['pageid']: x for x in data['search']})
    # 同上,不写了

with open("output.json", 'w', encoding='UTF-8') as f:
    json.dump(pages, f, indent=4, ensure_ascii=False)

然而,如果直接这样操作,不仅代码繁琐,还意味着每次查询都需要向服务器多发送10次prop=redirects请求,大大增加了耗费时间、服务器负担和WAF风险。

于是,我们可以使用生成器,在一次查询中同时获取页面列表和重定向信息。

在查询中使用生成器(Generator)

除有特殊声明外,query操作的绝大多数子模块都能被用作生成器。因此,绝大多数情况下,只需将list/prop=...参数改为generator=...,其他相关参数前加上g,就能将一个普通查询操作转换成一个generator查询操作。

对于一个generator查询操作,可附加一个参数prop=A|B|C|...。这时,服务器返回的数据中,每个通过generator查询到的页面后都会附上指定的属性信息,从而避免了多次发送查询请求。

代码
import requests
import json

song_type = "VOCALOID"

def get_pages_with_redirects(offset: int):
    # search通过偏移值来获取更多数据,因此这里不再使用API返回的continue数据
    # generator的limit值和各个prop的limit值是分别计算的
    # 由于查询中重定向总数可能多于页面总数,为避免一次请求无法返回所有重定向信息,将srlimit缩减为400
    return json.loads(requests.get("https://zh.moegirl.org.cn/api.php", params={
        "action": "query",
        "generator": "search",
        "gsrsearch": f"incategory:使用{song_type}的歌曲 incategory:日本音乐作品",
        "gsrlimit": 400,
        "gsroffset": offset,
        "continue": "gsroffset||",
        "prop": "redirects",
        "rdlimit": 500,
        "format": "json"}).text)

pages = {}
i = 0
while True:
    data = get_pages_with_redirects(i)
    pages.update(data['query']['pages'])
    i += 400
    if not 'continue' in data: break

with open("output.json", 'w', encoding='UTF-8') as f:
    json.dump(pages, f, indent=4, ensure_ascii=False)
程序输出
{
    "103030": {
        "pageid": 103030,
        "ns": 0,
        "title": "1 2 fan club",
        "index": 215,
        "redirects": [
            {
                "pageid": 119944,
                "ns": 0,
                "title": "12 Fan Club"
            },
            {
                "pageid": 262220,
                "ns": 0,
                "title": "いーあるふぁんくらぶ"
            },
            {
                "pageid": 262222,
                "ns": 0,
                "title": "一二粉丝俱乐部"
            }
        ]
    },
    "468148": {
        "pageid": 468148,
        "ns": 0,
        "title": "3分钟女孩",
        "index": 234,
        "redirects": [
            {
                "pageid": 468195,
                "ns": 0,
                "title": "3分ガール"
            },
            {
                "pageid": 468196,
                "ns": 0,
                "title": "三分钟女孩"
            }
        ]
    },
    "303078": {
        "pageid": 303078,
        "ns": 0,
        "title": "8.32",
        "index": 102
    },
    ...
}

注释

  1. Windows系统下可以使用cmd或PowerShell;macOS系统下使用终端(即Terminal)即可
  2. 创建任何文件时请确保文件使用utf-8编码。例如,Windows下的记事本会在保存时允许用户选择文件编码。如果默认的编码是ANSI,请把它改成utf-8。
  3. 以下默认使用python3。如果设备中python3命令不存在,请使用python命令。
  4. 如果显示SiteDefinitionError,有可能是WAF导致的,可以在config.py的family_files处,把https://zh.moegirl...改为https://mzh.moegirl...
  5. 本故事纯属虚构因为删除参数这种事情可以直接用正则完成,根本没有必要手动做
  6. 来自条目雨声残响的历史版本
  7. 这里的统计方式和{{User Infobox}}相同,但是与显示贡献数的小工具不同

参见