研究了一下物書堂的文件格式,提取了完整版NHK 日本語発音アクセント新辞典

我是为了方便给Anki的单词卡加音调标注和语音然后才开始研究这个问题的。本来想直接转换论坛里的mdx文件,结果发现论坛的几个版本提取的内容都不全,只有6万多词条。实际这个词典有75991个词条,然后语音有102821个。

之前有人写过rust的库,但是和我之前写的其他python脚本结合起来用很不方便,所以用chatgpt直接把rust翻译成了python,实际效果还可以,稍微改一下就能用。另外之前这个库也有没处理的情况,我顺便也解决了。

Rust库: GitHub - golddranks/monokakido: A Rust library for parsing and interpreting the Monokakido dictionary format. Full test coverage and efficient implementation with minimal dependencies.

下面讲讲物書堂文件格式:

词典文件在Contents下面的NHK_ACCENT目录。有appendix,audio,contents,headline,key这几个目录。 其中appendix就是普通的html文件,音频保存在audio下,是nrsc格式。词典本体数据是在contents里面,是rsc格式。其他还有标题和索引,标题在headline里面,有长短两种。索引在key目录下,分了三个文件,有普通词,数字和合成词。

NSRC文件

首先有个index.nidx,这里记录每个文件都在什么位置。然后有数字编号的.nrsc存了具体文件内容。知道文件序号和文件内偏移以及大小就可以到这个文件中查找对应的数据了。
下面是nidx的格式,用010 Editor的Binary Template脚本表示,类似C语言:

这里有详细文档: 010 Editor Manual - Introduction to Templates and Scripts

int zero; //总为0
int file_count;

struct {
    short compression;  //0 不压缩 1 zlib压缩
    short fileseq;  //文件序号
    int id_str_offset;  //文件名地址
    int file_offset;  //文件偏移
    int length;  //文件长度
} NrscIdxRecord[file_count] <read=ReadString(id_str_offset)>;

每条记录对应的文件名在索引结束后单独存放,通过偏移来查找。

RSC文件

这些文件是由.idx索引文件,.map映射文件和数字编号的.rsc文件组成
contents.idx,这个文件是索引,从id到map中的条目,因为map也是顺序排列的,实际用处不大。

int count;
struct {
int id;
int map_index;
} idx_record[count];
contents.map,这个文件是记录条目的位置的,每个条目包括所在压缩块的偏移和压缩块内的偏移。

int count;
struct {
int zoffset; //压缩块偏移
int ioffset; //压缩块解压后的文件偏移
} map_record[count];
要查找每个文件,要找到对应压缩块,这个偏移不是文件内偏移,而是全局偏移,所以需要根据这个确定在哪个文件,然后就知道文件内的偏移了。
文件内的偏移是一个压缩块。首先是4字节压缩块长度,然后后面就是压缩数据了。
解压后的数据的偏移位置处也是先是4自己文件长度,然后才是实际数据。

Headline文件

这个多了很多数值为0的数据,实际和nrsc结构差不多,保存了每个条目的page_id和item_id,以及对应标题的文字。文字同样是在最后。

struct {
   int magic;  //总为 2
   int zero1;  //总为 0
   int rec_count;  //记录总数
   int rec_offset;  //开始位置
   int words_offset;  //字符串开始位置
   int rec_bytes;  //记录大小,总为24
   int magic4; //总为 0
   int magic5; //总为 0
} Header;

struct {
    int page_id;
    short item_id;
    short zero1; //总为 0
    int str_offset;
    int zero2; //总为 0
    int zero3; //总为 0
    int zero4; //总为 0
    
} HeadLine[Header.rec_count] <read=ReadWString(str_offset+Header.words_offset)>;

Key索引文件

这个文件非常复杂,首先是文件头,然后是一个偏移数组,指向各单词条目。单词条目包括单词本身和对应的page_id和item_id列表。 page_id对应一页,item_id对应页内的内容。这个列表是变长的,在单词条目后面集中存储。

这个列表后面是索引文件头,可以通过文件头对应的idx_offset访问。 索引文件头里面有4个索引的偏移,注意这个偏移是相对索引文件头的。

在文件中,如果没有索引,对应条目为0,索引本身是一个偏移数组,就是按顺序排列的单词,可以方便二分查找。

这4个索引,决定不同的查询方式,
第一个索引是先按单词长度排序,再按字典顺序排序,这个索引可以查某一长度的单词范围,用处不大。
第二个索引是正常的字典序,可以查询指定内容开头的词。
第三个索引是按后缀排序的,也就是先把单词反转再排序。
第四个索引是按单词中出现的字排序的,也就是拆散每个单词,然后按字排序后重组。这个可以用于查询任意顺序查询出现的字,有一定用处但作用不大。

struct {
    int ver;
    int magic1;
    int words_offset;
    int idx_offset;
    int next_offset;
    int magic5;
    int magic6;
    int magic7;
} Header;

FSeek(Header.words_offset);
int count;
int word_offsets[count];
local int i;
for (i = 0; i < count; i++)
{
    FSeek(word_offsets[i]+Header.words_offset);
    struct {
    int offset;  //索引对应的page和item列表的偏移
    byte zero;  //总为 0
    char text[]; //单词本身
    
    } word_entry <read=(text)>;
}
typedef struct  {
    byte type:4;
    byte item_len:4;
    switch (type)
    {
        case 1:
        byte page_id;
        break;
        case 2:
        byte page_id[2];
        break;
        case 4:
        byte page_id[3];
        break;
    }
    if (item_len == 1)
        byte item_id;
    else if (item_len == 2)
        ushort item_id;

} page_id_t <read=Str("page:%d item:%d",
(type == 1 ? page_id : (type == 2 ? ((uint)page_id[0] << 8) + page_id[1] : 
((uint)page_id[0]<< 16) + ((uint)page_id[1]<< 8)+ page_id[2])),
(item_len != 0 ? item_id : 0))>;
for (i = 0; i < count; i++)
{
    FSeek(word_entry[i].offset + Header.words_offset);
    struct {
    short count;
    local int j;
    for (j = 0;j<count;j++)
    {
        page_id_t page_id;
    }
    
    } pages;
}

FSeek(Header.idx_offset);
struct {
    int count;
    int index_a_offset;
    int index_b_offset;
    int index_c_offset;
    int index_d_offset;
} Index;
int count_a;
int index_a[count_a];
int count_b;
int index_b[count_b];
int count_c;
int index_c[count_c];
int count_d;
int index_d[count_d];

不过提取完以后还需要处理html的数据,还好这比较容易。我目前去除了多余的标签,但大致保留了原始结构,其中母音无声化用()来表示。

这是提取以后的数据,包括结构化的字典数据,标题和索引,其中索引还根据原索引生成了反向索引。

下载:

NHK 日本語発音アクセント新辞典 json.zip (5.9 MB)

音频文件太大了就传到网盘了:

通过网盘分享的文件:NHK 日本語発音アクセント新辞典 audios.zip
链接: https://pan.baidu.com/s/1A5ftISRxyWRD_h2E6SBHIA?pwd=ak6j 提取码: ak6j

11 个赞

能搞清楚这些文件格式,好佩服好羡慕…

我之前下载了从这个帖子里分享的nhk词典,想把原本的数字词头给去掉,只保留日语文字的词头。整理了一点,然后搁置了很久。

刚刚回去翻了一下整理的记录,神奇的是除了优化搜索的词头之外,有正文的词头加起来也是75991个 :joy:
不过搁置了很久,这75991个词头里面是不是都有正文已经完全不记得了(挠头)

语音文件没整理过


这个图片里面的第一列编号我记得好像是mdx解压出来后的就有的数字词头

1 个赞

原来有全的版本啊,我之前都没看到这个帖子,这个数量是对的,原始数据也有一部分是跳转链接。不过看上去是把id也做成查询内容了?确实id查询没有用,我感觉加罗马字可能更方便点。

词条内容总数那个图片是自己做的笔记截图吗? 太好看了!

思源笔记,样式有现成的可以选,也可以自己修改css

1 个赞

最近考古了这本词典, 贴出一些历史版本以作为参考, 希望有帮助:

NHK日本語発音アクセント辞書.mdx - 2013
由 chigre 创建, 虽然是最古老的版本, 但貌似也是目前网盘流通最广的版本

索引: 165646
Creation Date: 2013-6-5
MD5 Hash: fc1f42aef2bf7bbc71682aad470b5b01

参考来源:
[2013.6.10更新下载地址]NHK日语真人语音库 [NHK日本語発音アクセント辞書] - MDict 词库资源区 - MDict Dictionaries - 掌上百科 - PDAWIKI - Powered by Discuz!

下载地址:
Index of /尚未整理/共享2020.5.11/content/0_audio/

NHK日本語発音アクセント辞書.mdx - 2021
由 Kawaei / Gino 创建, 重新生成了优化词头

索引: 278642
Creation Date: 2021-4-1
MD5 Hash: 18268190fd381bf07fc540b0732fa933

参考来源:
[2021.04.01][重新生成词头 实现精确查询]NHK日语真人语音库 - MDict 词库资源区 - MDict Dictionaries - 掌上百科 - PDAWIKI - Powered by Discuz!

下载地址:
https://pan.baidu.com/s/1Yj_MLJcxHf5zW24PfZtJCg 提取码: 5smy

NHK日本語発音アクセント辞書.mdx - 2023
@lsy0xcc 创建, 并开发了 2 个版本的 css 装修文件
@Existentialismus 更新, 修正了一些 mdx 错误

索引: 281729
Creation Date: 2023-11-5 (实际上是 2023-11-7)
MD5 Hash: 8568a06480ec3266b57b7f378f4701e0

参考来源:
NHK日语发音音调词典 一个非常简陋的美化改版

下载地址:
#24 - NHK日本語発音アクセント辞書.mdx (7,0 MB)

NHK 日本語発音アクセント新辞典 - 2025 by @anon14928871

索引: 61246
Creation Date: 2025-5-10
MD5 Hash: 07c38364761f2922f7608aeb4b4df234

参考来源:
物書堂: NHK 日本語発音アクセント新辞典

下载地址:
Google 云端硬盘 - 日本語発音アクセント 新辞典

NHK日本語発音アクセント新辞典 - 2025 by @kiwaa

索引: 420238
Creation Date: 2025-5-29
MD5 Hash: 593f3f37f20d68b6392648db3153312c

参考来源:
日本語辞書3種のMdict版

下载地址:
ProtonDirve - https://drive.proton.me/urls/GH0GV6DMEC#RP55zc2DL8vD

4 个赞

各词头之间的关系,有点懵,不然可以制作一下mdx了

有懂日语的,也可以尝试制作啊 :star_struck:

其实格式比较清楚,本身词头是平假名,有汉字常用写法和不常用写法,分别用两种括号表示,索引是把平假名全转成片假名了。我提取这个数据主要是想给程序用的,不过已经有完整索引和原html的话转成mdx还是很简单的,原版的词典app估计查词的时候也是把用户输入转成片假名,但mdx词典不会自动转,所以不能直接用原来的索引,还要添加平假名部分。

1 个赞

感谢分享,这个版本好全,数词和复合动词都有了~顺便楼主可以分享一下python的脚本嘛,想试试研究一下其他词典

已经传到github了,不过这里面解析词典到json的部分只有这本词典能用。

5 个赞