使用說明:使用Python編輯

萌娘百科,萬物皆可萌的百科全書!轉載請標註來源頁面的網頁連結,並聲明引自萌娘百科。內容不可商用。
貢獻者:
Commons-emblem-notice.svg
這個頁面「Help:使用Python編輯」是萌娘百科的幫助文檔
  • 本文用於介紹萌娘百科中一些特定功能的操作方法;
  • 本文僅是一篇論述,不屬於方針或指引。如果本指南與相關方針或指引發生衝突或存在不一致的情況,請以方針或指引的條文為準。

這個指南會簡單介紹一下如何使用 Python 創建一個維護 wiki 的自動程序/機器人(bot),它可用於在 wiki 上面進行大量編輯或者刪除等維護操作。使用它的好處在於,只要您編寫過 Python程序,就可以輕鬆上手。

注意:閱讀以下內容,我們假定您擁有 Python 和命令行的基本知識。欲學習 Python,請前往官方文檔等其他站點。

配置

安裝Python

配置Python

Pywikibot是目前最常用的wiki編輯Python庫。除此之外,我們還會用到wikitextparserrequests。只需在命令行[注 1]中輸入

  1. python3 -m pip install requests
  2. python3 -m pip install wikitextparser

即可。注意:由於部分作業系統同時包括Python2和Python3,因此推薦在命令中使用python3而不是python。但如果python3命令無法被識別,請使用python命令。

創造初始工程文件夾

官網下載初始文件「core_stable.zip」,將其解壓。

解壓後會出現一個叫「core_stable」的文件夾,在文件夾中創建一個名為user-config.py的文件[注 2]。文件的內容為:

  1. mylang = 'mgp'
  2. family_files['mgp'] = 'http://zh.moegirl.tw/api.php'
  3. family = 'mgp'
  4. # 這裡使用您自己的用戶名
  5. usernames['mgp']['*'] = '用戶名'
  6. password_file = "user-password.py"

然後,創建一個名為user-password.py的文件存儲用戶名和密碼。這裡的帳號必須是自動確認使用者,否則會因為驗證碼編輯失敗。

如果沒有創建bot帳號(見下文),文件內容為:

  1. # 強烈不推薦這種登錄方式:將不加密的密碼存儲在文件中是很大的安全隱患。
  2. ('mgp', 'mgp', '用戶名', '密碼')

如果已有bot帳號,文件內容為:

  1. ('mgp', 'mgp', '用戶名', BotPassword('bot用戶名', 'bot密碼'))

因為pywikibot目前的登錄邏輯和本站對部分API的限制。請將core_stable/pywikibot/site/_apisite.py文件APISite類的get_token()方法中

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

部分代碼替換為:

  1. if not types or load_all is not False:
  2. if self.logged_in():
  3. pdata = self._paraminfo.parameter('query+tokens', 'type')
  4. assert pdata is not None
  5. types = pdata['type']
  6. else:
  7. 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處於同一個文件夾)。文件的內容為:

  1. # 導入pywikibot
  2. import pywikibot as pwb
  3. # 獲取名為Help:沙盒的頁面
  4. page = pwb.Page(pwb.Site(), "Help:沙盒")
  5. # 給頁面已有的內容添加Hello World
  6. page.text += "Hello World"
  7. # 可選:查看更改後的wikitext文本
  8. print(page.text)
  9. # 保存頁面,編輯摘要為"測試pywikibot"
  10. 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完成批量刪除參數,因此寫出了以下代碼用來測試。

  1. # 導入pywikibot和wikitextparser
  2. import pywikibot as pwb
  3. import wikitextparser as wtp
  4. # 獲取一個頁面,用頁面名代替沙盒
  5. page = pwb.Page(pwb.Site(), "Help:沙盒")
  6. # 使用wikitextparser處理頁面
  7. parsed = wtp.parse(page.text)
  8. # 遍歷頁面中的每一個模板
  9. for t in parsed.templates:
  10. # 如果模板是YoutubeCount,則刪除fallback參數
  11. if t.name == 'YoutubeCount':
  12. t.del_arg("fallback")
  13. # 用轉換後的wikitext替換已有的wikitext
  14. page.text = str(parsed)
  15. # 保存頁面並標記為小編輯(如果沒有做出任何更改,會提示保存成功,但是頁面不會有任何變化)
  16. page.save(summary="刪除fallback參數", minor=True)

接下來,只要讀取需要更改的條目列表,然後對每個條目重複以上步驟即可。

除了用於刪除參數的del_arg,wikitextparser還提供了用於獲取模板參數的get_arg和用於添加、修改參數的set_arg,以及更多功能。詳情見API文檔和下一個示例。

編輯示例3:再生機器人

假如有這樣一個模板[注 6]

  1. {{VOCALOID_Songbox
  2. |亂七八糟的參數
  3. |yt_id = 0KK5vQlCVYo
  4. |其他資料 = 2014105日投稿至niconico,再生數為{{NiconicoCount|id=sm24626484}}<br>同日投稿至YouTube,再生數為1,993,000+
  5. }}

突然,我們得知{{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。以下是編輯一個條目的代碼:

  1. # 導入pywikibot和wikitextparser
  2. import pywikibot as pwb
  3. import wikitextparser as wtp
  4. # 使用正則查找播放量
  5. import re
  6. # 獲取一個頁面,用頁面名代替沙盒
  7. page = pwb.Page(pwb.Site(), "Help:沙盒")
  8. # 使用wikitextparser處理頁面
  9. parsed = wtp.parse(page.text)
  10. # 記錄頁面有無變化
  11. changed = False
  12. # 遍歷頁面中的每一個模板,並找出所有的VOCALOID_Songbox。
  13. song_boxes = []
  14. for t in parsed.templates:
  15. # 添加所有名字是VOCALOID_Songbox的模板
  16. if "VOCALOID_Songbox" == t.name.strip():
  17. song_boxes.append(t)
  18. # 遍歷所有songbox
  19. for t in song_boxes:
  20. # 獲取YouTube視頻的id和“其他資料”參數的內容
  21. # 使用get_arg返回的是一個對象,包含name(參數名)和value(參數值)
  22. # 不存在的參數則會返回None
  23. yt_id = t.get_arg("yt_id")
  24. other_info = t.get_arg("其他資料")
  25. # 如果兩者中的任意一個不存在,則跳過該模板(有可能YouTube無投稿)
  26. if yt_id is None or other_info is None:
  27. continue
  28. # 可選:此時的yt_id.value就是YouTube視頻的id
  29. print(yt_id.value.strip())
  30. # 使用正則查找播放量,僅為簡單例子,實際使用時的正則會更復雜。
  31. match = re.search("[0-9][0-9,]+[+]", other_info.value)
  32. # 如果找不到播放量,跳過
  33. if match is None:
  34. continue
  35. # 找出數字播放量開始和結束的位置
  36. start = match.start()
  37. end = match.end()
  38. # 新模板。使用strip去除YouTube視頻id中的空格。
  39. template = "{{YoutubeCount|id=" + yt_id.value.strip() + "}}"
  40. # 修改“其他信息”的值,將原有內容中的數字部分替換為模板
  41. other_info.value = other_info.value[:start] + template + other_info.value[end:]
  42. # 標記頁面已修改
  43. changed = True
  44. # 如果頁面被修改過,保存更改,否則告知用戶條目沒有被修改
  45. if changed:
  46. # 將修改後的wikitext對象轉換為字符串,並用它覆蓋頁面原有的內容
  47. page.text = str(parsed)
  48. # 保存編輯
  49. # minor: 是否是小編輯
  50. # botflag: 機器人flag,如果沒有機器人或機器用戶權限,則沒有任何影響。
  51. # tags: 加入編輯標籤,用管道符|分割多個標籤。Automation tool指的是“由半自動化腳本或半自動化工具執行的操作”。test指的是測試。
  52. # watch: 是否添加至監視列表,"nochange"指的是不改變監視列表
  53. # 可自行在萌百的[http://zh.moegirl.tw/api.php api頁面]中瞭解對應的意義
  54. page.save(summary="測試", minor=True, botflag=True, watch="nochange", tags="Automation tool|test")
  55. else:
  56. 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次貢獻

第一次嘗試如下:

  1. import requests
  2. import json
  3. import urllib
  4. # 向萌百的服務器發送請求,這裡以U:Lihaohong為例。為了保證可讀性,代碼被分成了好幾行。
  5. result = requests.get("http://zh.moegirl.tw/api.php?"
  6. "action=query&list=usercontribs&"
  7. # 注意:在url中輸入非拉丁字母和非數字的字符需要用urllib.parse.quote額外處理
  8. "ucuser=" + urllib.parse.quote("Lihaohong") + "&"
  9. "uclimit=500&"
  10. "ucprop=title&"
  11. "format=json")
  12. # 將返回的結果轉化為json數據
  13. data = json.loads(result.text)
  14. # 輸出結果
  15. print(data)

輸出的json數據如下

  1. {
  2. 'batchcomplete': '',
  3. 'continue': {
  4. # 這裡的數值可以用來發送下一次請求獲取“下一頁”貢獻。
  5. 'uccontinue': '20220415095523|5841956',
  6. 'continue': '-||'
  7. },
  8. 'query': {
  9. 'usercontribs': [
  10. {
  11. 'userid': 741745,
  12. 'user': 'Lihaohong',
  13. 'ns': 12,
  14. 'title': 'Help:沙盒'
  15. },
  16. {
  17. 'userid': 741745,
  18. 'user': 'Lihaohong',
  19. 'ns': 0,
  20. 'title': '夜顏的告白'
  21. },
  22. {
  23. 'userid': 741745,
  24. 'user': 'Lihaohong',
  25. 'ns': 0,
  26. 'title': '最強Tettoteto計劃'
  27. },
  28. {
  29. 'userid': 741745,
  30. 'user': 'Lihaohong',
  31. 'ns': 0,
  32. 'title': '追悔'
  33. }
  34. # 後面還有很多...但是總共只有500條。
  35. ]
  36. }
  37. }

顯然,query中的usercontribs數組存儲了用戶的編輯,其中的ns包含每次編輯的命名空間信息。

使用Python獲取所有貢獻

在統計命名空間之前,需要先使用uccontinue參數獲取用戶的全部貢獻,而不僅僅是前500個。

  1. import requests
  2. import json
  3. # 用列表(數組)存儲所有貢獻
  4. contributions = []
  5. # 一開始,不需要uccontinue參數
  6. cont = ""
  7. while True:
  8. # 將uccontinue參數加入請求。注意:一開始cont為空字符串。
  9. result = requests.get("http://zh.moegirl.tw/api.php?"
  10. "action=query&list=usercontribs&"
  11. "ucuser=Lihaohong&"
  12. "uclimit=500&"
  13. "ucprop=title&"
  14. "format=json" +
  15. cont)
  16. # 將返回的結果轉化為json數據
  17. data = json.loads(result.text)
  18. # 提取貢獻,並把貢獻存入contributions
  19. contributions.extend(data['query']['usercontribs'])
  20. # 如果api返回的數據中包含continue,表明還有更多貢獻未獲取
  21. if 'continue' in data:
  22. # 將uccontinue參數放入cont變量,用於下一次循環的get請求
  23. cont = "&uccontinue=" + data['continue']['uccontinue']
  24. else: # 如果不包含continue,說明所有貢獻已被讀取完畢,可以退出循環
  25. break
  26. # 輸出所有貢獻
  27. print(contributions)
  28. # 確保貢獻數正確
  29. print(len(contributions))

運行後發現,貢獻數與用戶的真實貢獻數相同[注 7]

使用Python分析數據

接下來,只要統計每個命名空間出現多少次即可。Python的Counter類可以快速統計數組中的每個數字出現了多少次。除此之外,還可以使用字典將數字轉換為文字。

  1. from collections import Counter
  2. namespace_dict = {
  3. 0: "主", 1: "討論",
  4. 2: "用戶", 3: "用戶討論",
  5. 4: "萌娘百科", 5: "萌百討論",
  6. 6: "文件", 7: "文件討論",
  7. 8: "MW", 9: "MW討論",
  8. 10: "模板", 11: "模板討論",
  9. 12: "幫助", 13: "幫助討論",
  10. 14: "分類", 15: "分類討論",
  11. 274: "小部件", 275: "小部件討論",
  12. 828: "模塊", 829: "模塊討論"
  13. }
  14. # 這裡省略了之前獲取contributions列表的代碼
  15. # 獲取每次編輯的名字空間,並將其轉換為文字(如果namespace_dict沒有對應的文字,則保留數字)。最後用Counter統計每個名字空間出現了多少次。
  16. counter = Counter([namespace_dict.get(c['ns'], c['ns'])
  17. for c in contributions])
  18. # 按照編輯次數給所有名字空間排序
  19. t = sorted(list(counter.items()), key=lambda p: p[1], reverse=True)
  20. print(t)

用戶Lihaohong的輸出為

[('主', 1253), ('用戶', 279), ('幫助', 19), ('用戶討論', 16), ('萌娘百科討論', 12), ('討論', 10), ('模板', 9), ('模板討論', 2), ('萌娘百科', 1)]

繪製圖表

最後,熟悉Python繪製圖表工具,例如matplotlib的用戶,可以繪製貢獻的餅狀圖和柱狀圖。

  1. import matplotlib
  2. import matplotlib.pyplot as plt
  3. # 部分操作系統中,matplotlib的默認字體不支持中文,因此需要手動指定字體。
  4. matplotlib.rcParams['font.family'] = "Heiti TC"
  5. matplotlib.rcParams['axes.unicode_minus'] = False
  6. # 把名字空間和編輯次數放入兩個不同的列表。
  7. namespaces, edits = zip(*t)
  8. # 第一張圖:餅狀圖
  9. plt.figure(1, [10, 6])
  10. patches, texts = plt.pie(edits, labels=namespaces)
  11. plt.legend(patches, [f"{p[0]}: {p[1]}" for p in t],
  12. loc='center right', bbox_to_anchor=(0, 0.5), fontsize=15)
  13. plt.savefig("pie.png")
  14. plt.show()
  15. # 第二張圖:柱狀圖
  16. plt.figure(2, [len(namespaces), 6])
  17. bar_plot = plt.bar(namespaces, edits, color=[p.get_facecolor() for p in patches])
  18. plt.bar_label(bar_plot)
  19. plt.savefig("bar.png")
  20. plt.show()

應對WAF

對於大多數用戶,本工具已經夠用了。但是查詢編輯量極大的用戶的所有編輯需要大量(超過100個)請求,極有可能被WAF。解決方法如下:

  1. 使用sleep函數,每發送一個請求就休眠數秒,減少被WAF的可能性。
  2. 使用mzh域名。雖然很少被WAF,但是會被502。
  3. 如果被WAF,保存已有進度。下次啟動程序時從已有進度開始繼續發送請求。

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

  1. # 僅展示如何獲取單頁數據
  2. # 循環,直到成功獲取數據
  3. while True:
  4. try:
  5. result = requests.get("http://mzh.moegirl.tw/api.php?"
  6. "action=query&list=usercontribs&"
  7. "ucuser=" + urllib.parse.quote("Lihaohong") + "&"
  8. "uclimit=500&"
  9. "ucprop=title&"
  10. "format=json" +
  11. cont)
  12. # 將返回的結果轉化為json數據
  13. data = json.loads(result.text)
  14. # 如果成功獲取數據,退出循環
  15. break
  16. except Exception as e: # 如有Exception,多半是502了
  17. # 輸出錯誤信息,並重試
  18. 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獲取至某條目的重新導向頁面列表。

展開代碼(僅作示例,不保證正確性,可以不看
  1. import requests
  2. import json
  3. song_type = "VOCALOID"
  4. def get_pages(offset: int):
  5. # search通過偏移值來獲取更多數據,因此這裡不再使用API返回的continue數據
  6. return json.loads(requests.get("http://zh.moegirl.tw/api.php", params={
  7. "action": "query",
  8. "list": "search",
  9. "srsearch": f"incategory:使用{song_type}的歌曲 incategory:日本音樂作品",
  10. "srlimit": 500,
  11. "sroffset": offset,
  12. "continue": "-||",
  13. "format": "json"}).text)['query']
  14. # titles參數長度限制為50,需切塊處理
  15. def get_redirects(pages: list[str]) -> requests.Response:
  16. return (json.loads(requests.get("http://zh.moegirl.tw/api.php", params={
  17. "action": "query",
  18. "prop": "redirects",
  19. "titles": '|'.join(pages),
  20. "rdlimit": 500,
  21. "format": "json"}).text))['query']
  22. def get_all_redirects(pages: list[str]) -> requests.Response:
  23. result = {'pages': {}}
  24. for i in range(0, len(pages), 50):
  25. result['pages'].update(get_redirects(pages[i:i+50])['pages'])
  26. return result
  27. data = get_pages(0)
  28. total_count = data['searchinfo']['totalhits']
  29. pages = {x['pageid']: x for x in data['search']}
  30. datar = get_all_redirects([x['title'] for x in data['search']])['pages']
  31. for k, v in datar.items():
  32. if 'redirects' in v:
  33. pages[int(k)]['redirects'] = v['redirects']
  34. for i in range(500, total_count, 500):
  35. data = get_pages(i)['search']
  36. pages.update({x['pageid']: x for x in data['search']})
  37. # 同上,不寫了
  38. with open("output.json", 'w', encoding='UTF-8') as f:
  39. 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查詢到的頁面後都會附上指定的屬性信息,從而避免了多次發送查詢請求。

代碼
  1. import requests
  2. import json
  3. song_type = "VOCALOID"
  4. def get_pages_with_redirects(offset: int):
  5. # search通過偏移值來獲取更多數據,因此這裡不再使用API返回的continue數據
  6. # generator的limit值和各個prop的limit值是分別計算的
  7. # 由於查詢中重定向總數可能多於頁面總數,為避免一次請求無法返回所有重定向信息,將srlimit縮減為400
  8. return json.loads(requests.get("http://zh.moegirl.tw/api.php", params={
  9. "action": "query",
  10. "generator": "search",
  11. "gsrsearch": f"incategory:使用{song_type}的歌曲 incategory:日本音樂作品",
  12. "gsrlimit": 400,
  13. "gsroffset": offset,
  14. "continue": "gsroffset||",
  15. "prop": "redirects",
  16. "rdlimit": 500,
  17. "format": "json"}).text)
  18. pages = {}
  19. i = 0
  20. while True:
  21. data = get_pages_with_redirects(i)
  22. pages.update(data['query']['pages'])
  23. i += 400
  24. if not 'continue' in data: break
  25. with open("output.json", 'w', encoding='UTF-8') as f:
  26. json.dump(pages, f, indent=4, ensure_ascii=False)
程序輸出
  1. {
  2. "103030": {
  3. "pageid": 103030,
  4. "ns": 0,
  5. "title": "1 2 fan club",
  6. "index": 215,
  7. "redirects": [
  8. {
  9. "pageid": 119944,
  10. "ns": 0,
  11. "title": "12 Fan Club"
  12. },
  13. {
  14. "pageid": 262220,
  15. "ns": 0,
  16. "title": "いーあるふぁんくらぶ"
  17. },
  18. {
  19. "pageid": 262222,
  20. "ns": 0,
  21. "title": "一二粉絲俱樂部"
  22. }
  23. ]
  24. },
  25. "468148": {
  26. "pageid": 468148,
  27. "ns": 0,
  28. "title": "3分鐘女孩",
  29. "index": 234,
  30. "redirects": [
  31. {
  32. "pageid": 468195,
  33. "ns": 0,
  34. "title": "3分ガール"
  35. },
  36. {
  37. "pageid": 468196,
  38. "ns": 0,
  39. "title": "三分鐘女孩"
  40. }
  41. ]
  42. },
  43. "303078": {
  44. "pageid": 303078,
  45. "ns": 0,
  46. "title": "8.32",
  47. "index": 102
  48. },
  49. ...
  50. }

注釋

  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}}相同,但是與顯示貢獻數的小工具不同

參見

此頁面最後編輯於 2024年11月22日 (週五) 00:41。
搜尋萌娘百科 (按"/"快速搜尋)
有新的未讀公告

阅读更多:Help:使用Python編輯(http://zh.moegirl.tw/Help%3A%E4%BD%BF%E7%94%A8Python%E7%B7%A8%E8%BC%AF )
本文引自萌娘百科(http://zh.moegirl.tw ),文字内容默认使用《知识共享 署名-非商业性使用-相同方式共享 3.0 中国大陆》协议。