編集の要約なし
>Fet-Fe 細編集の要約なし |
>Fet-Fe 編集の要約なし |
||
12行目: | 12行目: | ||
連絡は[[利用者・トーク:Fet-Fe|トークページ]]までお願いします | 連絡は[[利用者・トーク:Fet-Fe|トークページ]]までお願いします | ||
== 恒心教徒の皆様へ:当Wikiのソースの魚拓取得をお手伝い下さい == | |||
現在Ost師がいらっしゃらない期間が長く続いており、何かあったのではないかと当職は心配しています | |||
Wikiのダンプを取れるのはOst師だけなので、最悪の場合、Wikiのデータが消失してしまうことも考えられます | |||
そこで、履歴が消えても最新のコンテンツだけは保存できるよう、当職はWikiの各ページの「ソースを編集」で開く編集ページの魚拓を取るスクリプトを書きました | |||
使い方は必要なライブラリをインストールし、走らせるだけです | |||
87行目以降の<code>namespace_list</code>を絞れば、その名前空間のページだけ魚拓をとることができます | |||
多くの教徒が別々の名前空間に対して魚拓をとっていただければ、取得が早く終わります | |||
また1つのIPから多くの魚拓を取得すると制限がかかるかもしれないので、なるべく多様なIPから魚拓をとっていただけると助かります | |||
アクセス先は当wikiとarchive.phだけなので、生IPでも多分大丈夫です | |||
魚拓ページに対して、JavaScriptなら<code>document.querySelector('#wpTextbox1').textContent</code>を実行すればwikiのソースが取れます | |||
皆様ご協力をお願いいたします | |||
<syntaxhighlight lang="python" line> | |||
import subprocess | |||
from urllib.parse import quote, unquote | |||
from bs4 import BeautifulSoup | |||
import re | |||
from time import sleep | |||
from datetime import datetime | |||
from typing import List | |||
ROOT = "https://krsw-wiki.org" | |||
ARCHIVE = "https://archive.ph" | |||
yobi = re.compile(r'\([日月火水木金土]\)') | |||
archive_wip_url_re = re.compile(r'document\.location\.replace\(\"(https:\/\/archive\.ph\/wip\/\S+)\"\)') | |||
# curlコマンドでリクエストを送る | |||
def curl(arg: str) -> str: | |||
sleep(1) # DoS予防 | |||
return subprocess.run('curl -sSL ' + arg, stdout=subprocess.PIPE, shell=True).stdout.decode('utf-8') | |||
# ページのBeautifulSoupオブジェクトを取得 | |||
def fetch_page(url: str) -> BeautifulSoup: | |||
url = url.replace('&', '\&') | |||
response = curl(f"-A 'Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0' {url}") | |||
soup = BeautifulSoup(response, features='lxml') | |||
return soup | |||
# ページが最後に編集された時間を取得 | |||
def last_edit(page_href: str) -> datetime: | |||
info_page_url = f"{ROOT}/index.php?title={page_href[6:]}&action=info" | |||
page_obj = fetch_page(info_page_url) | |||
time_text = page_obj.select_one('#mw-pageinfo-lasttime > td > a').get_text(strip=True) | |||
# 2022年3月3日 (木) 15:40 みたいな形式からdatetimeに | |||
time_text = yobi.sub('', time_text) | |||
timestamp = datetime.strptime(time_text, '%Y年%m月%d日 %H:%M') | |||
return timestamp | |||
# ページのソースが最後に魚拓された時間を取得 | |||
def last_archive(page_href: str) -> datetime: | |||
source_page_url = f"{ROOT}/index.php?title={page_href[6:]}&action=edit" | |||
archive_page_url = f"{ARCHIVE}/{source_page_url}" | |||
page_obj = fetch_page(archive_page_url) | |||
time_text_node = page_obj.select_one('#row0 > .THUMBS-BLOCK > div:last-of-type > a > div') | |||
# 魚拓が無ければ最小のタイムスタンプ | |||
if not time_text_node: | |||
return datetime.min | |||
time_text = time_text_node.get_text(strip=True) | |||
# 16 Jan 2022 00:37 みたいな形式からdatetimeに | |||
timestamp = datetime.strptime(time_text, '%d %b %Y %H:%M') | |||
return timestamp | |||
def fetch_submitid() -> str: | |||
soup = fetch_page(ARCHIVE) | |||
return soup.select_one('#submiturl > input').get('value') | |||
# 1つのページの魚拓をとる | |||
def archive_page(page_href: str): | |||
source_page_url = f"{ROOT}/index.php?title={page_href[6:]}&action=edit" | |||
print(f"{unquote(source_page_url)} の魚拓を取るナリ") | |||
response = curl(f"-X POST -A 'Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0' -H 'Host:archive.ph' {ARCHIVE}/submit/ --data-raw 'anyway=1&submitid={quote(fetch_submitid())}&url={quote(source_page_url)}'") | |||
print(f"{archive_wip_url_re.search(response)[1]} で魚拓を取っているナリ\n") | |||
# ページ一覧の各ページで最新の魚拓をとる | |||
def archive_each_page(page_list_url: str): | |||
page_obj = fetch_page(page_list_url) | |||
page_list = page_obj.select('.mw-allpages-chunk > li > a') | |||
for page_url in page_list: | |||
# 各ページの最終更新日時とそれらのソースの最終魚拓日時を比較 | |||
last_edit_time = last_edit(page_url.get('href')) | |||
last_archive_time = last_archive(page_url.get('href')) | |||
if last_archive_time <= last_edit_time: | |||
# 魚拓が古ければ新しくアーカイブ | |||
archive_page(page_url.get('href')) | |||
# ページ一覧のpagination | |||
pagination_url = page_obj.select_one('.mw-allpages-nav > a:last-of-type') | |||
if pagination_url and '次のページ' in pagination_url.get_text(strip=True): | |||
archive_each_page(f"{ROOT}{pagination_url.get('href')}") | |||
def main(namespace_list: List[int]): | |||
for i in namespace_list: | |||
# 名前空間のページ一覧のURL | |||
page_list_url = f"{ROOT}/wiki/{quote('特別:ページ一覧')}?from=&to=&namespace={i}" | |||
print(f" {unquote(page_list_url)} の名前空間を探索するナリ\n") | |||
archive_each_page(page_list_url) | |||
if __name__ == '__main__': | |||
namespace_list = [ | |||
0, # (標準) | |||
4, # Wiki | |||
3004, # 恒辞苑 | |||
3006, # 恒心文庫 | |||
8, # MediaWiki | |||
10, # テンプレート | |||
14, # カテゴリ | |||
828, # モジュール | |||
3008, # 恒心AA保管庫 | |||
12, # ヘルプ | |||
6, # ファイル | |||
2, # 利用者 | |||
3000, # Forum | |||
3002, # Books | |||
1, # トーク | |||
5, # Wiki・トーク | |||
3005, # 恒辞苑・トーク | |||
3007, # 恒心文庫 | |||
9, # MediaWiki・トーク | |||
11, # テンプレート・トーク | |||
15, # カテゴリ・トーク | |||
829, # モジュール・トーク | |||
3009, # 恒心AA保管庫・トーク | |||
13, # ヘルプ・トーク | |||
7, # ファイル・トーク | |||
3, # 利用者・トーク | |||
3001, # Forum talk | |||
3003, # Books talk | |||
] | |||
main(namespace_list) | |||
</syntaxhighlight> | |||
== Unicodeのノウハウ == | == Unicodeのノウハウ == |