跳转到内容

Forum:中文Minecraft Wiki第一个离线版本的诞生

当前版块: 索引 一般讨论

回复

avatar

如何才能让网络不佳的用户访问MCW?在openZIM的消息传到茶馆之后,我就一直希望能借助这项技术解决这个问题。我在openZIM Wiki上找到了Build your ZIM file这篇教程。

方法有很多,但大部分都不能用。于是我开始了探索……

第一次报错

由于我的电脑上没有Linux系统,只有一个学习用的Linux虚拟机,所以我最初的设想是在我的平板上运行Termux。

许多软件不是停止维护了就是使用了奇奇怪怪的构建工具,最终我选定了MWoffliner这个软件,它目前仍在积极维护。后来我才知道,openZIM官方使用的软件正是MWoffliner。

MWoffliner支持通过npm或Docker安装,但是一来我没有Docker,二来Docker镜像已经被删除了[注 1]。于是我在Termux上安装了Node.js,然后安装了MWoffliner:

pkg install nodejs
mkdir mwoffliner
cd mwoffliner/
npm install mwoffliner

很快便得到了报错:

gyp ERR! UNCAUGHT EXCEPTION
gyp ERR! stack TypeError: Cannot assign to read only property 'cflags' of object '#<Object>'
gyp ERR! stack     at createConfigFile (/data/data/com.termux/files/home/mwoffliner/node_modules/node-gyp/lib/configure.js:118:21)
gyp ERR! stack     at /data/data/com.termux/files/home/mwoffliner/node_modules/node-gyp/lib/configure.js:85:9
gyp ERR! stack     at /data/data/com.termux/files/home/mwoffliner/node_modules/node-gyp/node_modules/mkdirp/index.js:30:20
gyp ERR! stack     at FSReqCallback.oncomplete (node:fs:188:23)
gyp ERR! System Linux 4.19.113-perf-g954cdddf4c24
gyp ERR! command "/data/data/com.termux/files/usr/bin/node" "/data/data/com.termux/files/home/mwoffliner/node_modules/.bin/node-gyp" "rebuild" "-v"
gyp ERR! cwd /data/data/com.termux/files/home/mwoffliner/node_modules/@openzim/libzim
gyp ERR! node -v v20.2.0
gyp ERR! node-gyp -v v6.1.0
gyp ERR! This is a bug in `node-gyp`.
gyp ERR! Try to update node-gyp and file an Issue if it does not help:
gyp ERR!     <https://github.com/nodejs/node-gyp/issues>
npm ERR! code 7
npm ERR! path /data/data/com.termux/files/home/mwoffliner/node_modules/@openzim/libzim
npm ERR! command failed
npm ERR! command sh -c npm run download && node-gyp rebuild -v && npm run bundle

第二次报错

看来平板上是不适合跑了……11月30日(四)的晚上,我启动了电脑上的Debian虚拟机,又开始了尝试:

sudo apt install nodejs npm
mkdir mwoffliner
cd mwoffliner/
npm install mwoffliner

于是又一次得到了报错:

npm ERR! code 1
npm ERR! path /home/co-eda/mwoffliner2/node_modules/sharp
npm ERR! command failed
npm ERR! command sh -c (node install/libvips && node install/dll-copy && prebuild-install) || (node install/can-compile && node-gyp rebuild && node install/dll-copy)
npm ERR! sharp: Please see https://sharp.pixelplumbing.com/install for required dependencies
npm ERR! sharp: Installation error: Expected Node.js version >=14.15.0 but found 12.22.12

第三次报错

报错信息中已经说了:Node的版本太低了。于是我又上网查找如何升版本,很快便找到了官方仓库中的安装说明。只需要敲几条命令,就能导入证书,添加源,并安装最新版了。[注 2]安装好Node 20之后,我们重新运行最后一条命令:

npm install mwoffliner

滚动的进度条变成了六个点[注 3],看起来非常现代。但是报错还是一如既往地来了:

npm ERR! gyp ERR! UNCAUGHT EXCEPTION 
npm ERR! gyp ERR! stack TypeError: Cannot assign to read only property 'cflags' of object '#<Object>'
npm ERR! gyp ERR! stack     at createConfigFile (/home/co-eda/mwoffliner2/node_modules/node-gyp/lib/configure.js:118:21)
npm ERR! gyp ERR! stack     at /home/co-eda/mwoffliner2/node_modules/node-gyp/lib/configure.js:85:9
npm ERR! gyp ERR! stack     at /home/co-eda/mwoffliner2/node_modules/node-gyp/node_modules/mkdirp/index.js:30:20
npm ERR! gyp ERR! stack     at FSReqCallback.oncomplete (node:fs:189:23)
npm ERR! gyp ERR! System Linux 5.10.0-8-amd64
npm ERR! gyp ERR! command "/usr/bin/node" "/home/co-eda/mwoffliner2/node_modules/.bin/node-gyp" "rebuild" "-v"
npm ERR! gyp ERR! cwd /home/co-eda/mwoffliner2/node_modules/@openzim/libzim
npm ERR! gyp ERR! node -v v20.10.0
npm ERR! gyp ERR! node-gyp -v v6.1.0
npm ERR! gyp ERR! This is a bug in `node-gyp`.
npm ERR! gyp ERR! Try to update node-gyp and file an Issue if it does not help:
npm ERR! gyp ERR!     <https://github.com/nodejs/node-gyp/issues>

很好,看来又回到老路上了。

第四次报错

这条评论说降级到Node 18就能解决问题。让我试试。

卸载Node.js,重走第二、三步。只不过这次把set NODE_MAJOR 20(我用的是Fish,不支持NODE_MAJOR=20这种语法)改成set NODE_MAJOR 18

再次安装,再次失败。

npm ERR! gyp ERR! build error 
npm ERR! gyp ERR! stack Error: `make` failed with exit code: 2
npm ERR! gyp ERR! stack     at ChildProcess.onExit (/home/co-eda/mwoffliner2/node_modules/node-gyp/lib/build.js:194:23)
npm ERR! gyp ERR! stack     at ChildProcess.emit (node:events:517:28)
npm ERR! gyp ERR! stack     at ChildProcess._handle.onexit (node:internal/child_process:292:12)
npm ERR! gyp ERR! System Linux 5.10.0-8-amd64
npm ERR! gyp ERR! command "/usr/bin/node" "/home/co-eda/mwoffliner2/node_modules/.bin/node-gyp" "rebuild" "-v"
npm ERR! gyp ERR! cwd /home/co-eda/mwoffliner2/node_modules/@openzim/libzim
npm ERR! gyp ERR! node -v v18.19.0
npm ERR! gyp ERR! node-gyp -v v6.1.0
npm ERR! gyp ERR! not ok

第五次报错

至少报错信息不一样了,这说明我们有进步我这样安慰着自己。

这条评论说安装libvips-dev就能解决问题。让我试试。

sudo apt install libvips-dev

但还是报错了。[注 4]

第六次失败

上述评论的下方又有一条评论,这次是需要安装Yarn并为依赖手动指定版本。

安装成功了!我为mwoffliner编写了脚本,并认真填入了每一项参数。脚本命名为dump.sh,内容如下:

#!/bin/bash
yarn run mwoffliner \
  --mwUrl=https://zh.minecraft.wiki/ \
  --adminEmail=[email protected] \
  --articleList=article_list.txt \
  --mwWikiPath=/w/ \
  --mwApiPath=/api.php \
  --mwRestApiPath=/rest.php/v1 \
  --mwModulePath=/load.php \
  --verbose=info \
  2>&1| tee output.txt

其中的article_list.txt包含我精心挑选的几个页面,包括首页、迁移通知、(当时的)最新版,以及旧站热度排名前三的页面(可在旧站的页面底部看到):

Minecraft Wiki
交易
附魔
村民
Minecraft Wiki:迁移通知
Java版1.20.3-rc1

运行脚本(./dump.sh),开始报错:

[error] [2024-02-12T09:40:27.326Z] Redis Client Error Error: connect ECONNREFUSED 127.0.0.1:6379
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1555:16) {
  errno: -111,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '127.0.0.1',
  port: 6379
}
error Command failed with exit code 3.

第七次失败

这是因为我没有安装Redis!README上已经说了,要自行安装Redis。

sudo apt install redis-server
[error] [2024-02-12T09:57:06.432Z] 

**********

Unable to find appropriate API end-point to retrieve article HTML

**********


error Command failed with exit code 2.

成功!

最后一次debug显得尤为艰难,因为我必须深挖源代码。

我打开了node_modules/mwoffliner/lib,定位到报错信息产生的位置,并在其周围插入了许多调试语句。经过一些努力,我终于找到了问题所在(Downloader.js):

    async checkCapabilities(testArticleId = 'MediaWiki:Sidebar') {
        // By default check all API's responses and set the capabilities
        // accordingly. We need to set a default page (always there because
        // installed per default) to request the REST API, otherwise it would
        // fail the check.
        logger.log(`checkCapabilities(${testArticleId})...`);
        testArticleId = 'MediaWiki:Sidebar';
        this.mwCapabilities.mobileRestApiAvailable = await this.checkApiAvailabilty(this.mw.getMobileRestApiArticleUrl(testArticleId));
        this.mwCapabilities.desktopRestApiAvailable = await this.checkApiAvailabilty(this.mw.getDesktopRestApiArticleUrl(testArticleId));
        this.mwCapabilities.veApiAvailable = await this.checkApiAvailabilty(this.mw.getVeApiArticleUrl(testArticleId));
        this.mwCapabilities.apiAvailable = await this.checkApiAvailabilty(this.mw.apiUrl.href);
        // Coordinate fetching
        const reqOpts = objToQueryString({
            ...this.getArticleQueryOpts(),
        });
        const resp = await this.getJSON(`${this.mw.apiUrl.href}${reqOpts}`);
        const isCoordinateWarning = resp.warnings && resp.warnings.query && (resp.warnings.query['*'] || '').includes('coordinates');
        if (isCoordinateWarning) {
            logger.info('Coordinates not available on this wiki');
            this.mwCapabilities.coordinatesAvailable = false;
        }
    }

这个函数默认通过MediaWiki:Sidebar来检查各个API是否可用。但是,这个函数在被调用的时候传入的总是首页名称,而获取首页名称的逻辑不适用于中文MCW。解决方法是在函数开始将页面强制更改为MediaWiki:Sidebar(上述代码高亮部分)。

我将生成的ZIM文件和生成用到的其他一些文件打包成mcwzh-testdump-20231201.zip,发到了茶馆群里,希望群友能帮我测试一下。但是没有人理我

成功的捷径

我在官方仓库里提的存档请求时隔一个月都没有任何回复。我在1月5日询问了一下进展,很快就有人创建了配方(recipe)

创建配方的时候,参数写得漏洞百出。我每次帮他们改,都要等一周才能回复,后来索性不回我了(不知道是不是消息太多了没看见)。看到他们在用Docker,我就想着自己搭建一个Docker环境。

Docker

我简单学习了一下Docker官网上的教程,并按照Docker官网上的教程在虚拟机上安装了Docker。之后,我拷贝了配方中使用的参数,进行修改后制成了如下的脚本:

#!/bin/bash

# Usage: sudo ./run.sh

# For docker:
#     Added: --rm
#     Modified: -v
#     Removed: --detach, --cpu-shares, --memory-swappiness, --memory
# For mwoffliner:
#     Modified: --adminEmail, --customZimDescription
#     Removed: --optimisationCacheUrl, --osTmpDir
docker run \
    -v /home/co-eda/mwoffliner-docker/output:/output:rw \
    --name mwoffliner_minecraftwiki_zh_all \
    --rm \
    ghcr.io/openzim/mwoffliner:1.13.0 \
    mwoffliner \
    --adminEmail="[email protected]" \
    --customZimDescription="Docker test" \
    --customZimFavicon="https://zh.minecraft.wiki/images/Wiki2x.png" \
    --customZimLanguage="zho" \
    --customZimTitle="Minecraft Wiki (zh)" \
    --format="novid:maxi" \
    --mwApiPath="/api.php" \
    --mwUrl="https://zh.minecraft.wiki/" \
    --outputDirectory="/output" \
    --publisher="openZIM" \
    --webp

之后就可以不断运行并根据报错调整参数了。

直到那个棘手问题的重现:

Unable to find appropriate API end-point to retrieve article HTML

Git

Docker是封闭环境,这意味着我无法动源代码一分一毫。这样就回到原点了还是得亲自构建。但是与上次不同的是,安装方式并非在空文件夹中执行npm install mwoffliner,而是克隆仓库后直接npm install

npm ERR! code 1
npm ERR! path /home/co-eda/mwoffliner-git/mwoffliner/node_modules/jpegtran-bin
npm ERR! command failed
npm ERR! command sh -c node lib/install.js
npm ERR! ⚠ connect ECONNREFUSED 0.0.0.0:443
npm ERR!   ⚠ jpegtran pre-build test failed
npm ERR!   ℹ compiling from source
npm ERR!   ✖ Error: Command failed: /bin/sh -c ./configure --disable-shared --prefix="/home/co-eda/mwoffliner-git/mwoffliner/node_modules/jpegtran-bin/vendor" --bindir="/home/co-eda/mwoffliner-git/mwoffliner/node_modules/jpegtran-bin/vendor"
npm ERR! configure: error: no nasm (Netwide Assembler) found

根据GitHub issue可知,安装 nasm 即可解决问题。

最初我是在v1.13.0这一分支上进行测试。后来发现源代码问题无法通过调节参数来解决,于是一位叫kelson42的贡献者改用git main运行配方。于是我在本地将分支切换到了main,并重新运行npm install。出现如下报错:

node_modules/@types/imagemin-gifsicle/index.d.ts:7:24 - error TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("imagemin")' call instead.

7 import { Plugin } from 'imagemin';
                         2024年3月21日 (四) 03:33 (UTC)2024年3月21日 (四) 03:33 (UTC)

node_modules/@types/imagemin-webp/index.d.ts:7:24 - error TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("imagemin")' call instead.

7 import { Plugin } from 'imagemin';
                         2024年3月21日 (四) 03:33 (UTC)2024年3月21日 (四) 03:33 (UTC)


Found 2 errors in 2 files.

Errors  Files
     1  node_modules/@types/imagemin-gifsicle/index.d.ts:7
     1  node_modules/@types/imagemin-webp/index.d.ts:7
Build Complete at [2024年 03月 07日 星期四 10:54:42 CST]

解决方法很离谱,就是在那两行代码上方添加忽略报错的命令。

+// @ts-expect-error
 import { Plugin } from 'imagemin';

后记

这篇文章是我从寒假开始写的。也不知道为什么要把这些乱七八糟的debug历程写出来,或许只是为了好玩。于是就这样断断续续写了很久,花了不知道多少时间,到今天终于完稿了。

那么为什么debug这么久呢?一方面当然是因为我是个小白,不知道可以克隆源代码再自己构建,也不会用Docker搭建构建好的镜像文件,于是花了好大的劲去安装官方提供的npm包,浪费了很多时间。另一方面也该怪openZIM这个组织,做出来的程序漏洞百出,安装也容易失败。可是当我看到他们时间精力不足,issue挤压多、处理慢,实在感到无奈。

目前经过我的努力,已经找到了修复配方的方法。接下来就可以开始爬取整个网站了。但是这个过程将花费约5个小时,所以只能在openZIM的机器上跑了。kelson42目前还没有回复我,不知道什么时候才能看到中文MCW的第一个完整离线版本。希望他们不要让我失望。

注释

  1. openZIM Wiki上提供的Docker链接已经失效,正确的链接可在GitHub项目页面找到:ghcr.io/openzim/mwoffliner
  2. 在我写这篇文章的时候,Node.js的安装方式又有变化了:这次只需要输入一条命令就能运行官方脚本完成配置了。不过由于这篇文章是我的安装过程回忆,仍然以当时的经历为准。可参阅旧版安装教程
  3. 参见中文维基百科条目盲文图案
  4. 此处往后再没有报错日志了,因为我无法复现第四次及之后的报错了(第四次报错昨天还可以复现,今天就不行了)。我现在已经成功安装了mwoffliner,即使我并没有安装libvips-dev

TripleCamera留言) 2024年3月21日 (四) 03:33 (UTC)

Cookie帮助我们提供我们的服务。通过使用我们的服务,您同意我们使用cookie。