极复杂编码,下载《原神》角色高清图、中日无损配音,爬虫 16 / 120 例

举报
梦想橡皮擦 发表于 2021/12/19 10:02:25 2021/12/19
【摘要】 各位爬虫爱好者,今天咱们要采集的目标站点是 原神官网,核心目标数据为原神角色图,角色大头贴,角色昵称,角色配音文件。 待爬取页面分析本次爬取的页面为:https://ys.mihoyo.com/main/character/mondstadt,其中 mondstadt 可以替换为 liyue,inazuma。目标页面的列表页数据呈现如下图所示,数据量级不是很大。进一步提取目标数据:所有的数据...

各位爬虫爱好者,今天咱们要采集的目标站点是 原神官网,核心目标数据为原神角色图,角色大头贴,角色昵称,角色配音文件。

待爬取页面分析

本次爬取的页面为:https://ys.mihoyo.com/main/character/mondstadt,其中 mondstadt 可以替换为 liyueinazuma

目标页面的列表页数据呈现如下图所示,数据量级不是很大。

在这里插入图片描述

进一步提取目标数据:

  1. 所有的数据都在 li 标签中;

  2. 其中 li 数据分为两部分,大图与音频在一个 li 标签中,大头贴与角色昵称在第二个 li 标签中,具体 DOM 结构如下所示:

  3. 最重要的音频下载地址如下图所示:

    但是在实际抓取的过程中,却发现服务器返回的代码,并非开发者工具加载的样式,代码返回到前端,由前端 JS 进行了二次渲染。

具体你可以通过谷歌浏览器,打开如下链接查阅:

view-source:https://ys.mihoyo.com/main/character/mondstadt?char=0

技术细节强调

本爬虫整体实现难度不大,有一些细节需要额外进行处理。

  1. 数据需要通过正则表达式进行提取,因服务器直接将其返回在页面中;
  2. 爬虫采集过程中需要进行音频下载;
  3. 动态传递参数到 Python 文件中。

其中数据合并使用 zip 函数实现,音频下载处理过程注意请求头与响应体即可,动态参数,采用 python 编译传参方式实现。

目标数据格式规定

在正式编码前,需要对目标数据存储格式进行二次确定,本次抓取输出的目录格式如下:

- download
  - 琴
  	- xxx.mp3
  	- cover1.png
  	- xxxx.mp3
  - 安柏
  	- xxx.mp3
  	- cover1.png
  	- xxxx.mp3

编码时间

通过 requests 模块去获取服务器响应数据之后,发现上述分析还是出了一点点问题,下图是服务器响应的代码段,并没有开发者工具中捕获到的 DOM 结构。

通过 Visual studio Code 进行代码格式化发现,数据是直接渲染到页面上,再通过 JS 进行加载。重要部分数据如下所示:

charList: [
                {
                  title: "琴",
                  icon: "https:\u002F\u002Fuploadstatic.mihoyo.com\u002Fcontentweb\u002F20200306\u002F2020030616591036729.png",
                  cover1:
                    "https:\u002F\u002Fuploadstatic.mihoyo.com\u002Fcontentweb\u002F20200729\u002F2020072917270791000.png",
                  cover2:
                    "https:\u002F\u002Fuploadstatic.mihoyo.com\u002Fcontentweb\u002F20200729\u002F2020072917273354266.png",
                  name: "https:\u002F\u002Fuploadstatic.mihoyo.com\u002Fcontentweb\u002F20190926\u002F2019092620142281505.png",
                  attr: "https:\u002F\u002Fuploadstatic.mihoyo.com\u002Fcontentweb\u002F20190926\u002F2019092620142687125.png",
                  intro:
                    "\u003Cp\u003E身为西风骑士团的代理团长,琴一直忠于职守,为人们带来安宁。虽然并非天赋异禀,但通过刻苦训练,如今的她已然能够独当一面。\u003Cbr \u002F\u003E\n当风魔龙的威胁开始临近,这位可靠的代理团长早已做好了准备,誓要守护蒙德。\u003C\u002Fp\u003E\n",
                  sen: "https:\u002F\u002Fuploadstatic.mihoyo.com\u002Fcontentweb\u002F20190926\u002F2019092620144979413.png",
                  lang: [a, b],
                  cv: [
                    {
                      name: "林簌",
                      audio: [
                        "https:\u002F\u002Fwebstatic.mihayo.com\u002Fupload\u002Fop-public\u002F2019\u002F12\u002F11\u002F209a68a166b14b27e11a8b64c466ea7c_7021182076965695539.mp3",
                        "https:\u002F\u002Fwebstatic.mihayo.com\u002Fupload\u002Fop-public\u002F2019\u002F12\u002F11\u002F806fad7c524efcebd55abc2ce4f8ce6a_5745385847854898057.mp3",
                        "https:\u002F\u002Fwebstatic.mihayo.com\u002Fupload\u002Fop-public\u002F2019\u002F12\u002F11\u002F74c81976dc6f3868ecc264bbd143e571_4077467239236738470.mp3",
                      ],
                    },
                    {
                      name: "斋藤千和",
                      audio: [
                        "https:\u002F\u002Fuploadstatic.mihoyo.com\u002Fcontentweb\u002F20190926\u002F2019092620145220378.mp3",
                        "https:\u002F\u002Fuploadstatic.mihoyo.com\u002Fcontentweb\u002F20190926\u002F2019092620145562610.mp3",
                        "https:\u002F\u002Fuploadstatic.mihoyo.com\u002Fcontentweb\u002F20190926\u002F2019092620145849323.mp3",
                      ],
                    },
                  ],
                },

此时再通过 lxml 进行提取就变得相对困难,但是使用正则表达式进行提取就变得简单很多。

本部分代码可使用 JSON 格式化校验工具,进行排版测试,也便于提取相关字段。下图忽略相关错误。

注意到上述代码中,还存在特殊字符 \u002F\u 的编码字串可以使用如下代码进行编译。

print("https:\\u002F\\u002Fuploadstatic.mihoyo.com\\u002Fcontentweb\\u002F20210508\\u002F2021050818254152089.png".encode('utf-8').decode("unicode-escape"))

本案例比较麻烦的地方就是提取数据,由于整体难度不大,属于编码熟练度练习,故直接放出源码,下述代码在学习时,一定要注意各个编码细节。

# https://ys.mihoyo.com/main/character/mondstadt
import requests
import sys
import random
import re
import sys
import os
def get_headers():
    uas = [
        "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)",
        "Mozilla/5.0 (compatible; Baiduspider-render/2.0; +http://www.baidu.com/search/spider.html)",
        "Baiduspider-image+(+http://www.baidu.com/search/spider.htm)",
        "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 YisouSpider/5.0 Safari/537.36",
        "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
        "Mozilla/5.0 (compatible; Googlebot-Image/1.0; +http://www.google.com/bot.html)",
        "Sogou web spider/4.0(+http://www.sogou.com/docs/help/webmasters.htm#07)",
        "Sogou News Spider/4.0(+http://www.sogou.com/docs/help/webmasters.htm#07)",
        "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0);",
        "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)",
        "Sosospider+(+http://help.soso.com/webspider.htm)",
        "Mozilla/5.0 (compatible; Yahoo! Slurp China; http://misc.yahoo.com.cn/help.html)"
    ]
    ua = random.choice(uas)
    headers = {
        "user-agent": ua,
        "referer": "https://www.baidu.com"
    }
    return headers
# 数据提取函数
def format_text(text):
    # element = etree.HTML(text)
    # print(text)
    # all_li = element.cssselect("li.swiper-slide")
    # print(all_li)
    div_pattern = re.compile('charList:(\[.*?),mod3Index')
    match = div_pattern.search(text)
    div_html = match.group(1)
    title_pattern = re.compile('title:"(.*?)"')
    cover1_pattern = re.compile('cover1:"(.*?)"')
    cover2_pattern = re.compile('cover2:"(.*?)"')
    icon_pattern = re.compile('icon:"(.*?)"')
    # 该正则表达式比较复杂,需要重点理解
    cv_pattern = re.compile(
        'cv:\[\{name:"(?P<cn_name>.*?)"\,audio:\[(?P<cn_audios>.*?)\]\}\,\{name:"(?P<jp_name>.*?)"\,audio:\[(?P<jp_audios>.*?)\]\}\]\}')
    titles = title_pattern.findall(div_html)
    cover1s = cover1_pattern.findall(div_html)
    cover2s = cover2_pattern.findall(div_html)
    icons = icon_pattern.findall(div_html)
    cvs = cv_pattern.findall(div_html)
    print(cvs)
    # print(titles,cover1s,cover2s,icons,cvs)
    for index in range(0, len(titles)):
        my_dict = {
            "title": titles[index],
            "cover1": cover1s[index],
            "cover2": cover2s[index],
            "icon": icons[index],
            "cn_name": cvs[index][0],  # 中文配音名称
            "jp_name": cvs[index][2],  # 日文配音名称
            "cn_audios": cvs[index][1].split(","),
            "jp_audios": cvs[index][3].split(","),
        }
        save(my_dict)
    # print("https:\\u002F\\u002Fuploadstatic.mihoyo.com\\u002Fcontentweb\\u002F20210508\\u002F2021050818254152089.png".encode('utf-8').decode("unicode-escape"))
    # print(len(titles),len(cover1s),len(cover2s))
# 创建文件夹
def save(my_dict):
    is_exists = os.path.exists("./download")

    # 判断结果
    if not is_exists:
        os.mkdir("./download")

    # 提取数据

    title = my_dict["title"]
    cover1 = my_dict["cover1"]
    cover2 = my_dict["cover2"]
    icon = my_dict["icon"]
    cn_name = my_dict["cn_name"]
    jp_name = my_dict["jp_name"]
    cn_audios = my_dict["cn_audios"]
    jp_audios = my_dict["jp_audios"]

    # 创建目录
    os.mkdir(f"./download/{title}")
    # 保存封面图1
    save_img(cover1, title, "cover1")
def save_img(url, title, img_name):
    # 去除 \u 字符
    url = url.encode('utf-8').decode("unicode-escape")
    try:
        res = requests.get(url, headers=get_headers(), timeout=5)
        with open(f'./download/{title}/{img_name}.png', "wb") as f:
            f.write(res.content)
    except Exception as e:
        print(e)
def run(url):
    try:
        res = requests.get(url, headers=get_headers(), timeout=5)
        format_text(res.text)
    except Exception as e:
        print("请求数据发生异常", e)
if __name__ == "__main__":
    argvs = sys.argv
    # 获取传递进来的参数
    category = argvs[1]
    url = "https://ys.mihoyo.com/main/character/{}?char=0".format(category)
    print(url)
    run(url)

执行上述代码,会得到部分高清图片,具体如下,每个文件夹都包含各个高清透明素材图。

再次修改代码,进行大头贴与全身图提取,得到如下数据。

对于 MP3 文件的下载,实现方式基本与图片一致,代码如下:

# 下载音频
def save_audio(title, cn_name, cn_audios):
    try:
        for index in range(0, len(cn_audios)):
            # 去除 \u 字符
            url = cn_audios[index].encode('utf-8').decode("unicode-escape")
            # 去除 url 左右双引号
            url = url.strip('"')

            res = requests.get(url, headers=get_headers(), timeout=5)
            with open(f'./download/{title}/{cn_name}_{index}.mp3', "wb") as f:
                f.write(res.content)
    except Exception as e:
        print(e)

在处理音频地址时,需要注意由于提取的原因,字符串左右多了一个 ",需要手动去除,即上述代码中 url = url.strip('"') 部分内容。

调用 save_audio 代码,实现对中日配音的下载。

运行代码,将 蒙德城璃月港稻妻城 相关数据进行下载,测试过程发现正则表达式匹配存在一些问题,修改如下:

cv:\[\{name:[\"]?(?P<cn_name>.*?)[\"]?\,audio:\[(?P<cn_audios>.*?)\]\}\,\{name:[\"]?(?P<jp_name>.*?)[\"]?\,audio:\[(?P<jp_audios>.*?)\]\}\]\}
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。