天凤牌谱采集及分析
采集牌谱 #
采集牌谱流程如下:
下载年度记录 #
前往天凤-记录的过去记录部分, 下载名字形如scraw[year].zip
的文件并解压.
找到凤凰桌的牌谱链接 #
2013年以前的压缩包解压后直接得到一系列scc[yymmdd].html
文件, 2013年及以后的压缩包解压后得到scc[yymmdd].html.gz
文件, 再次解压后得到html
文件. 每个html
文件存储着当天全部的凤凰桌对局信息, 用文本文档打开html
文件, 其中<a href="[url]">
中的[url]
就是牌谱链接, 点击即可选择用天凤网页版或者客户端查看牌谱.
转换牌谱链接得到牌谱文本 #
当然我们需要的不是在线查看牌谱, 我们需要下载牌谱文本. 在线查看牌谱的链接形如tenhou.net/0/?log=[id]
, 牌谱文本的链接形如tenhou.net/0/log/?[id]
. 因此将牌谱链接中的?log=
替换成log/?
, 输入至浏览器即可看到牌谱文本, 复制粘贴即可保存到本地.
自动爬取牌谱文本 #
如果愿意手动重复以上步骤, 那么可以跳过这一部分.
自动化爬取牌谱文本的关键是向牌谱文本链接发送请求, 并保存接收到的回复. 在python
中通常情况下这是由urllib.request.urlopen
函数做到的, 但是天凤似乎对于程序爬取有一定的限制, 直接urlopen
会返回404错误, 所以我们需要加一个请求头伪装成来自浏览器的请求, 同时使用Request
对象和Opener
对象发起请求. KStarXin提供的一个示例如下:
import urllib.request
HEADER = {
'Host': 'e.mjv.jp',
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:65.0) Gecko/20100101 Firefox/65.0',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
'Connection': 'keep-alive'
}
url = 'http://e3.mjv.jp/0/log/?2013010100gm-00e1-0000-e5440138' # for example
req = urllib.request.Request(url=url, headers=HEADER)
opener = urllib.request.build_opener()
response = opener.open(req)
response = gzip.decompress(response.read()).decode('utf-8')
除此之外还需注意牌谱实际不是储存在tenhou.net
域名下的, 而是分别储存在*.mjv.jp
域名下. 经初步整理, 从2013年到2018年的牌谱文件域名有:
e3.mjv.jp
e4.mjv.jp
e5.mjv.jp
k0.mjv.jp
e.mjv.jp
所以对于每一个牌谱id
, 我们分别尝试从各个域名进行下载.
参考资料 #
- 天鳳の牌譜ログ取得方法: https
://mj-data.net/天鳳の牌譜ログ取得方法 (失效链接)
解析牌谱 #
牌谱文件是xml格式的, 可以通过python标准库中xml.etree.ElementTree类来方便地解析xml文件.
接下来我们将介绍xml文件中的各个元素及其属性的含义. 需要注意的是数数一般是从0开始数的(除了立直步骤之外).
mjloggm #
根元素.
ver #
牌谱格式的版本, 2018年1月1日的牌谱版本为2.3. 我们也将只对版本2.3的牌谱进行解析.
- SHUFFLE
- 乱数的信息, 牌山是由这个乱数所决定的. 我们不是很关心这个乱数, 因为其余的牌谱信息足以再现对局.
- seed
- 乱数的种子.
- ref
- 为空.
GO #
牌桌规则和等级等信息.
- type
- 化为十进制表示的8位二进制数.
0x01 如果是PVP对战则为1 0x02 如果没有赤宝牌则为1 0x04 如果无食断则为1 0x08 如果是半庄则为1 0x10 如果是三人麻将则为1 0x20 如果是特上卓或凤凰卓则为1 0x40 如果是速卓则为1 0x80 如果是上级卓则为1 - lobby
- 大会lobby的ID.
UN #
天凤名, 段位, 等级分等的对局者信息.
- n0 ~ n3
- 天凤名. URL格式UTF编码. 对局者的编号是0, 编号按逆时针顺序增加.
- dan
- 段位. 以逗号分隔的四个数. 0: 新人 ~ 20: 天凤.
- rate
- 等级分. 以逗号分隔的四个数, 有两位小数.
- sx
- 性别.
TAIKYOKU #
从这个元素开始都是用来记录实际对局的.
- oya
- 起家. 通常是从0开始.
INIT #
每局的初始信息.
- seed
- 以逗号分隔的六个数, 分别是局顺, 本场, 供托, 开门的两个骰子值(0~5), DORA表示牌的编号.
- ten
- 每局开始时候各玩家的点数除以100. 以逗号分隔的四个数.
- oya
- 庄家的编号.
- hai0 ~ hai3
- 起手牌. 以逗号分隔的13个数. 没有理牌.
(T|U|V|W)(0~135) #
摸牌的信息.
- T ~ W
- 玩家编号. T表示编号为0的玩家摸牌, W表示编号为3的玩家摸牌.
- 0 ~ 135
- 摸进的牌的编号.
(D|E|F|G)(0~135) #
打牌的信息.
- D ~ G
- 玩家编号. D表示编号为0的玩家打牌, G表示编号为3的玩家打牌.
- 0 ~ 135
- 打出的牌的编号.
N #
副露的信息.
- who
- 开副露的玩家的编号.
- 面子的编号.
DORA #
新的DORA牌的信息(在开杠之后).
- hai
- 新的DORA表示牌的编号.
REACH #
立直的信息. 在立直宣言和立直成立时出现. 在立直宣言牌放铳的情况下, 立直成立的REACH元素不出现.
- who
- 立直玩家的编号
- step
- 立直的阶段. 1表示立直宣言, 2表示立直成立.
- ten
- 立直成立后各玩家的持有点数除以100
AGARI #
和牌的信息. 一炮双响的情况下AGARI元素会出现两次.
- ba
- 2个数. 积棒个数和供托的立直棒个数
- hai
- 除了副露以外的手牌(兵牌), 包含和的牌. 用逗号分隔, 牌编号格式.
- m
- 包含暗杠的副露牌, 用逗号分隔, 面子编号格式.
- machi
- 和的牌, 牌编号格式.
- ten
- 3个数. 符, 和的打点(不除以100), 满贯情报(0: 满贯未满, 1: 满贯, 2: 跳满, 3: 倍满, 4: 三倍满, 5: 役满).
- yaku
- 非役满和牌的役的信息. 每个役编号和番数交替以逗号分隔.
- yakuman
- 役满和牌的役的信息. 只有役编号, 用逗号分隔.
- doraHai
- DORA表示牌的牌编号, 用逗号分隔.
- doraHaiUra
- 里DORA表示牌的牌编号, 用逗号分隔.
- who
- 和牌玩家的编号.
- fromWho
- 放铳玩家的编号.
- sc
- 和牌之后点数的移动情况. 8个数. 每个玩家的持有点数/100和收支点数/100交替以逗号分隔.
- owari
- 终局的信息. 8个数, 每个玩家的持有点数/100以及包含uma的point数交替以逗号分隔.
RYUUKYOKU #
流局的信息.
- ba
- 同AGARI元素中的ba.
- sc
- 同AGARI元素中的sc.
- type
- 流局的理由.
值 意思 nm 流局满贯 yao9 九种九牌 kaze4 四风连打 reach4 四家立直 ron3 三家和了 kan4 四杠散了 - owari
- 同AGARI元素中的owari.