主流视频网站弹幕下载

如今主流的视频网站(如bilibili,腾讯,爱奇艺,优酷,芒果TV等)都支持了弹幕,本文介绍了如何下载视频弹幕(.xml)文件并转化为字幕(.ass)文件,支持本地播放。

XML格式弹幕

B站是最早的一批弹幕网站之一,且比较成熟,弹幕可以直接以XML格式下载,非常方便,所以本文下载的弹幕均以B站的XML弹幕格式的简化为标准格式。

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<i>
    <d p="5,1,20,16777215">这是一条弹幕</d>
    ...
</i>

每一条弹幕的属性p的格式为:

  1. 弹幕发送时间,相对于视频开始时间,以秒为单位
  2. 弹幕类型,1-3为滚动弹幕、4为底部、5为顶端、6为逆向、7为精确、8为高级
  3. 字体大小,25为中,18为小,Bilibili只有这2个字号,本地20字号比较合适(电脑分辨率是1920*1080)
  4. 弹幕颜色,RGB颜色转为十进制后的值,16777215为白色
  5. 弹幕发送时间,Unix时间戳格式
  6. 弹幕池,0为普通,1为字幕,2为特殊
  7. 发送人的id
  8. 弹幕id

一般只需要使用前4项即可。

Python中利用request库来爬取网页结果:

1
2
3
4
5
6
7
import urllib.request

def get_response(url):
    req = urllib.request.Request(url)
    req.add_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
    response = urllib.request.urlopen(req).read().decode("utf-8")
    return response

生成XML弹幕文件时需要检查是否有非法XML字符,并可以设置弹幕黑名单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
filename = "XML/" + title + ".xml"
contents = []
with open(filename, "w", encoding='utf-8') as fout:
    fout.write('<?xml version="1.0" encoding="UTF-8"?>\n')
    fout.write('<i>\n')
    illegal = False #标志是否有非法XML字符
    for char in ["<", ">", "&", "\u0000", "\b"]:
        if char in j['content']:
            illegal = True
            break
    if illegal:
        continue
    black_list = [''] #列出弹幕黑名单
    if content not in contents and all(word not in content for word in black_list):
        contents.append(content)
        fout.write('<d p="' + str(timepoint) + ',' + str(ct) +',' + str(size) + ',' + str(color) + '">' + content + '</d>\n')
    fout.write('</i>')

网上很多相关工具(如弹幕ASS转换工具等)可以将XML弹幕文件转换成ASS字幕文件。
基于弹幕ASS转换工具个性化设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 设置项,适合视频2倍速播放
var config = {
  'playResX': 1440,          // 屏幕分辨率宽(像素)
  'playResY': 810,           // 屏幕分辨率高(像素)
  'fontlist': [              // 字形(会自动选择最前面一个可用的)
    '黑体',
    'Microsoft YaHei UI',
    'Microsoft YaHei',
    '文泉驿正黑',
    'STHeitiSC',
  ],
  'font_size': 1.2,          // 字号(比例)
  'r2ltime': 20,             // 右到左弹幕持续时间(秒)
  'fixtime': 5,              // 固定弹幕持续时间(秒)
  'opacity': 0.8,            // 不透明度(比例)
  'space': 0,                // 弹幕间隔的最小水平距离(像素)
  'max_delay': 6,            // 最多允许延迟几秒出现弹幕
  'bottom': 0,               // 底端给字幕保留的空间(像素)
  'use_canvas': true,        // 是否使用canvas计算文本宽度(布尔值,Linux下的火狐默认否,其他默认是,Firefox bug #561361)
  'debug': false,            // 打印调试信息
};

腾讯视频弹幕下载

打开一个腾讯视频PC网页端,其源码中的VIDEO_INFO字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
var VIDEO_INFO = {
    "publish_date": "",
    "leading_actor_id": [""],
    "duration": ,
    "guests": ,
    "race_teams_id": ,
    "type_name": ,
    "tag": [

    ],
    "singer_id": ,
    "episode": ,
    "race_stars_id": ,
    "srcsite_name": ,
    "type": ,
    "title": ,
    "leading_actor": [""],
    "show_type": ,
    "singer_name": ,
    "danmu_status": ,
    "second_title": ,
    "positive_trailer": ,
    "athlete": ,
    "mv_stars": ,
    "trytime_second": ,
    "c_full": ,
    "update_flag": ,
    "first_recommand": ,
    "desc": ,
    "pioneer_tag": ,
    "begin_time": ,
    "upload_qq": ,
    "category_map": [, ""],
    "is_trailer": ,
    "stars_name": ,
    "pic_640_360": ,
    "c_title_segment": ,
    "guests_id": ,
    "presenter_id": ,
    "upload_src": ,
    "athlete_id": ,
    "sec_recommand": ,
    "costar_id": ,
    "relative_stars_id": ,
    "relative_stars": ,
    "drm": ,
    "modify_time": ,
    "tail_time": ,
    "valid_tag_id": ,
    "vid": ,
    "pic_url": ,
    "costar": ,
    "race_teams_name": ,
    "c_title_output": ,
    "director_id": [""],
    "title_en": ,
    "stars": ,
    "danmu": ,
    "mv_stars_id": ,
    "playright": [""],
    "presenter": ,
    "race_stars": ,
    "view_all_count": ,
    "c_tags_flag": ,
    "c_has_adv_danmu": ,
    "head_time": ,
    "state": ,
    "copyright_id": ,
    "pic160x90": ,
    "director": [""],
    "famous_id": ,
    "pioneer_tag_ids": ,
    "trytime": ,
    "famous_actor": ,
    "video_checkup_time": ,
    "": ,
    "isFull": 
};

其中所需的字段是duration、title、vid。
接下来通过vid找到targetid:http://bullet.video.qq.com/fcgi-bin/target/regist?otype=json&vid=(%vid%),打开此链接得到:

1
2
3
4
5
6
7
8
9
10
11
QZOutputJson = {
    "danmukey":"bubble_flag=&targetid=&vid=&type=",
    "display":,
    "is_has_adv":,
    "is_has_bubble":,
    "open":,
    "returncode":,
    "returnmsg":,
    "targetid":,
    "userstatus":
}

然后就可以通过targetid得到弹幕:http://mfm.video.qq.com/danmu?timestamp=(%timestamp%)&target_id=(%targetid%),其中timestamp从0开始并且以30为增量,打开此链接得到(只截取了第一条弹幕):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
    "err_code":,
    "err_msg":,
    "peroid":,
    "target_id":,
    "count":,
    "tol_up":,
    "single_max_count":,
    "session_key":,
    "comments":[
        {
            "commentid":,
            "content":,
            "upcount":,
            "isfriend":,
            "isop":,
            "isself":,
            "timepoint":,
            "headurl":,
            "opername":,
            "bb_bcolor":,
            "bb_head":,
            "bb_level":,
            "bb_id":,
            "rich_type":,
            "uservip_degree":,
            "content_style": "{\"color\":\"\",\"position\":}"
        }
    ]
}

其中timepoint、content_style中的color、content字段可以组成xml弹幕格式。
全部python代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import urllib.request
import re
import json
import os.path
import random

def get_response(url):
    req = urllib.request.Request(url)
    req.add_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
    response = urllib.request.urlopen(req).read().decode("utf-8")
    return response

url = 'https://v.qq.com/x/cover/(%id%).html' # 视频的url
video_info = json.loads(str([s for s in get_response(url).split('\n') if 'VIDEO_INFO' in str(s)]).strip('[\'var VIDEO_INFO = ').strip('\']'))
duration = video_info['duration']
title = video_info['title']
vid = video_info['vid']
targetid = json.loads(get_response('http://bullet.video.qq.com/fcgi-bin/target/regist?otype=json&vid=' + vid).strip('QZOutputJson=').strip(';'))['targetid']
filename = "XML/" + title + ".xml"
contents = []
with open(filename, "w", encoding='utf-8') as fout:
    fout.write('<?xml version="1.0" encoding="UTF-8"?>\n')
    fout.write('<i>\n')
    for i in range(int(duration)//30+1):
        timestamp = i*30
        danmu = json.loads(get_response('http://mfm.video.qq.com/danmu?timestamp=' + str(timestamp) + '&target_id=' + targetid), strict=False)
        for j in danmu['comments']:
            illegal = False #标志是否有非法XML字符
            for char in ["<", ">", "&", "\u0000", "\b"]:
                if char in j['content']:
                    illegal = True
                    break
            if illegal:
                continue
            timepoint = j['timepoint']  #弹幕发送时间
            ct = 1              #弹幕样式
            size = 20           #字体大小
            # 获取颜色
            if "color" in j["content_style"]:
                content_style = json.loads(j["content_style"])
                color = int(content_style["color"], 16)
            else:
                color = 16777215
            content = j['content'] #弹幕内容
            black_list = [''] #列出弹幕黑名单
            if content not in contents and all(word not in content for word in black_list):
                contents.append(content)
                fout.write('<d p="' + str(timepoint) + ',' + str(ct) +',' + str(size) + ',' + str(color) + '">' + content + '</d>\n')
    fout.write('</i>')

爱奇艺视频弹幕下载

打开一个爱奇艺视频PC网页端,其源码中的page-info字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
{
    "albumId":,
    "albumName":,
    "imageUrl":,
    "tvId":,
    "vid":,
    "cid":,
    "isSource":,
    "contentType":,
    "vType":,
    "pType":,
    "pageNo":,
    "pageType":,
    "userId":,
    "pageUrl":,
    "tvName":,
    "isfeizhengpian":,
    "categoryName":,
    "categories":,
    "downloadAllowed":,
    "publicLevel":,
    "payMark":,
    "payMarkUrl":,
    "vipType":[
        
    ],
    "qiyiProduced":,
    "exclusive":,
    "tvYear":,
    "duration":"::",
    "wallId":,
    "rewardAllowed":,
    "commentAllowed":,
    "heatShowTypes":,
    "videoTemplate":,
    "issueTime":
}

其中所需的字段是duration、tvName、albumId、tvId、cid。
duration由‘时:分:秒’格式转为秒:

1
2
3
4
5
duration_str = page_info['duration'].split(':')
duration = 0
for i in range(len(duration_str)-1):
    duration = (duration + int(duration_str[i])) * 60
duration = duration + int(duration_str[-1])

然后就可以通过albumId、tvId、cid得到弹幕:http://cmts.iqiyi.com/bullet/(%tvId[-4:-2]%)/(%tvId[-2:]%)/(%tvId%)_300_(%page%).z?rn=0.(%16位随机数%)&business=danmu&is_iqiyi=true&is_video_page=true&tvid=(%tvid%)&albumid=(%albumid%)&categoryid=(%cid%)&qypid=01010021010000000000,其中tvId需要分割出倒数4-3位和倒数2-1位,page从1开始并且以1为增量,打开此链接得到(%tvId%)_300_(%page%).z的文件,这个文件是压缩的字节流需要解压。
Python中利用zlib库,dec = zlib.decompressobj(32 + zlib.MAX_WBITS)b = dec.decompress('z文件').decode("utf-8")得到XML格式的弹幕(只截取了第一条弹幕):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0" encoding="utf-8"?>

<danmu> 
  <code></code>  
  <data> 
    <entry> 
      <int></int>  
      <list> 
        <bulletInfo> 
          <contentId></contentId>  
          <content></content>  
          <showTime>1</showTime>  
          <font></font>  
          <color></color>  
          <opacity></opacity>  
          <position></position>  
          <background></background>  
          <contentType></contentType>  
          <isReply></isReply>  
          <likeCount></likeCount>  
          <plusCount></plusCount>  
          <dissCount></dissCount>  
          <userInfo> 
            <senderAvatar></senderAvatar>  
            <uid></uid>  
            <udid></udid>  
            <name></name> 
          </userInfo> 
        </bulletInfo> 
      </list> 
    </entry> 
  </data>  
  <sum></sum>  
  <validSum></validSum>  
  <duration></duration>  
  <ts></ts> 
</danmu>

其中showTime、color、content字段可以组成xml弹幕格式(color需要从16进制转换成10进制)。
全部python代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import urllib.request
import re
import json
import os.path
from random import randint
import zlib
import xml.etree.ElementTree as ET

def get_response(url):
    req = urllib.request.Request(url)
    req.add_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
    response = urllib.request.urlopen(req).read()
    return response

url = 'https://www.iqiyi.com/(%id%).html' # 视频的url'
page_info = json.loads(re.search(r'page-info=\'(.*)\' :video-info', get_response(url).decode("utf-8")).group(1))
duration_str = page_info['duration'].split(':')
duration = 0
for i in range(len(duration_str)-1):
    duration = (duration + int(duration_str[i])) * 60
duration = duration + int(duration_str[-1])
title = page_info['tvName']
albumid = page_info['albumId']
tvid = page_info['tvId']
categoryid = page_info['cid']
page = duration // (60 * 5) + 1
filename = "XML/" + title + "1.xml"
contents = []
with open(filename, "w", encoding='utf-8') as fout:
    fout.write('<?xml version="1.0" encoding="UTF-8"?>\n')
    fout.write('<i>\n')
    for i in range(duration // (60 * 5) + 1):
        dec = zlib.decompressobj(32 + zlib.MAX_WBITS)
        b = dec.decompress(get_response('http://cmts.iqiyi.com/bullet/' + str(tvid)[-4:-2] + '/' + str(tvid)[-2:] + '/' + str(tvid) + '_300_' + str(i+1) + '.z?rn=0.' + ''.join(["%s" % randint(0, 9) for num in range(0, 16)]) + '&business=danmu&is_iqiyi=true&is_video_page=true&tvid=' + str(tvid) + '&albumid=' + str(albumid) + '&categoryid=' + str(categoryid) + '&qypid=01010021010000000000'))
        root = ET.fromstring(b.decode("utf-8"))
        for bulletInfo in root.iter('bulletInfo'):
            timepoint = bulletInfo[2].text      #弹幕发送时间
            ct = 1                              #弹幕样式
            size = 20                           #字体大小
            color = int(bulletInfo[4].text, 16) #颜色
            content = bulletInfo[1].text        #弹幕内容
            black_list = [''] #列出弹幕黑名单
            if content not in contents and all(word not in content for word in black_list):
                contents.append(content)
                fout.write('<d p="' + str(timepoint) + ',' + str(ct) +',' + str(size) + ',' + str(color) + '">' + content + '</d>\n')
    fout.write('</i>')

优酷视频弹幕下载

打开一个优酷视频PC网页端,其源码中的window.PageConfig字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
window.PageConfig = {
    transfer_mode: ,
    isDRM: ,
    videoCategoryId: ,
    isSimple: ,
    videoId: ,
    newVersion: ,
    isDebug: ,
    pid: ,
    homeHost: ,
    youku_homeurl: ,
    catId: ,
    playmode: ,
    videoOwner: ,
    videoOwner_en: ,
    videoId2: ,
    currentEncodeVid: ,
    catName: ,
    seconds: ,
    bullet: ,
    transfer: ,
    panorama: ,
    folderId: ,
    fpos: ,
    forder: ,
    ftotalpos: ,
    showid_en: ,
    showid: ,
    cp: ,
    paid: ,
    showtype: ,
    tabs: ,
    singerId: ,
    loadinglogo: ,
    lottery_open_sidetool: ,
    lottery_id_sidetool: ,
    lottery_sidetool: ,
    page: {
        type: ,
        isdatetype: ,
        year: ,
        firstMon: ,
        lastMon: ,
        currMon: ,
        episodeLast: ,
        parentvideoid: ,
        compeleted:
    },
    copytoclip: ,
    playerUrl: 
};
var str = "&ct=c&cs=&td=&s=&v=&u=&paid=&tt=";

其中所需的字段是seconds、tt、videoId。
然后就可以通过videoId得到弹幕:https://service.danmu.youku.com/list?mat=(%mat%)&ct=1001&iid=(%videoId%),其中mat从0开始并且以1为增量,打开此链接得到(只截取了第一条弹幕):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
    "count": ,
    "filtered": ,
    "result": [{
        "aid": ,
        "content": "",
        "createtime": ,
        "ct": ,
        "extFields": {
            "voteUp": 
        },
        "id": ,
        "iid": ,
        "ipaddr": ,
        "level": ,
        "lid": ,
        "mat": ,
        "ouid": ,
        "playat": ,
        "propertis": "{\"pos\":,\"size\":,\"effect\":,\"color\":,\"dmfid\":}",
        "status": ,
        "type": ,
        "uid": ,
        "ver": 
    }],
    "scm": "0"
}

其中playat、propertis中的color、content字段可以组成xml弹幕格式。
全部python代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import urllib.request
import re
import json
import os.path
import random

def get_response(url):
    req = urllib.request.Request(url)
    req.add_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
    response = urllib.request.urlopen(req).read().decode("utf-8")
    return response

url = 'https://v.youku.com/v_show/(%id%).html' # 视频的url'
res = get_response(url)
title = re.search(r'<title>(.*)</title>', res).group(1).split('—')[0]
iid = re.search(r'videoId: \'(\d*)\'', res).group(1)
duration = float(re.search(r'seconds: \'(.*)\',', res).group(1))
filename = "XML/" + title + ".xml"
contents = []
with open(filename, "w", encoding='utf-8') as fout:
    fout.write('<?xml version="1.0" encoding="UTF-8"?>\n')
    fout.write('<i>\n')
    for mat in range(int(duration) // 60 + 1):
        req = urllib.request.Request('https://service.danmu.youku.com/list?mat=' + str(mat) + '&ct=1001&iid=' + iid)
        req.add_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
        response = urllib.request.urlopen(req)
        danmu = json.loads(response.read().decode("utf-8"))
        for i in range(len(danmu["result"])):
            illegal = False #标志是否有非法XML字符
            for char in ["<", ">", "&", "\u0000", "\b"]:
                if char in danmu["result"][i]["content"]:
                    illegal = True
                    break
            if illegal:
                continue
            playat = danmu["result"][i]["playat"]/1000  #弹幕发送时间
            ct = 1      #弹幕样式
            size = 20   #字体大小
            # 获取颜色
            if "color" in danmu["result"][i]["propertis"]:
                propertis = json.loads(danmu["result"][i]["propertis"])
                color = propertis["color"]
            else:
                color = 16777215
            content = danmu["result"][i]["content"] #弹幕内容
            black_list = [''] #列出弹幕黑名单
            if content not in contents and all(word not in content for word in black_list):
                contents.append(content)
                fout.write('<d p="' + str(playat) + ',' + str(ct) +',' + str(size) + ',' + str(color) + '">' + content + '</d>\n')
    fout.write('</i>')

芒果视频弹幕下载

打开一个芒果视频PC网页端,其网址(以https://www.mgtv.com/b/9015/4828668.html为例)中以/分割,倒数第二位是cid,倒数第一位是vid。
从源码中<title>霸王别姬 - 视频在线观看 - 霸王别姬 - 芒果TV</title>可获得title。
然后就可以通过cid和vid得到弹幕:https://galaxy.bz.mgtv.com/rdbarrage?vid=(%vid%)&cid=(%cid%)&time=(%time%),其中time从0开始并且下一个time的值可从弹幕中得到,打开此链接得到(只截取了第一条弹幕):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
    "status":,
    "msg":"操作成功",
    "seq":"",
    "data":{
        "next":,
        "interval":,
        "items":[
            {
                "id":,
                "type":,
                "uid":,
                "content":,
                "time":
            }
        ]
    }
}

其中time、content字段可以组成xml弹幕格式。
全部python代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import urllib.request
import re
import json
import os.path
import random
import sys
import io

sys.stdout = io.TextIOWrapper(sys.stdout.buffer,encoding='utf8')
def get_response(url):
    req = urllib.request.Request(url)
    req.add_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
    response = urllib.request.urlopen(req).read().decode("utf-8")
    return response

url = 'https://www.mgtv.com/b/(%cid%)/(%vid%).html' # 视频的url''
cid = url.split('/')[4]
vid = url.split('/')[5].strip('.html')
title = re.search(r'title: \"(.*)\",', get_response(url)).group(1)
filename = "XML/" + title + ".xml"
contents = []
with open(filename, "w", encoding='utf-8') as fout:
    fout.write('<?xml version="1.0" encoding="UTF-8"?>\n')
    fout.write('<i>\n')
    time = 0
    while True:
        danmu = json.loads(get_response('https://galaxy.bz.mgtv.com/rdbarrage?vid=' + vid + '&cid=' + cid + '&time=' + str(time)))
        break
        if danmu['data']['items'] == None:
            break
        for j in danmu['data']['items']:
            illegal = False #标志是否有非法XML字符
            for char in ["<", ">", "&", "\u0000", "\b"]:
                if char in j['content']:
                    illegal = True
                    break
            if illegal:
                continue
            timepoint = j['time']/1000  #弹幕发送时间
            ct = 1                      #弹幕样式
            size = 20                   #字体大小
            color = 16777215            #弹幕颜色
            content = j['content']      #弹幕内容
            black_list = [''] #列出弹幕黑名单
            if content not in contents and all(word not in content for word in black_list):
                contents.append(content)
                fout.write('<d p="' + str(timepoint) + ',' + str(ct) +',' + str(size) + ',' + str(color) + '">' + content + '</d>\n')
        time = danmu['data']['next']
    fout.write('</i>')
点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注

12 + 9 =