极复杂编码,下载《原神》角色高清图、中日无损配音,爬虫 16 / 120 例
【摘要】 各位爬虫爱好者,今天咱们要采集的目标站点是 原神官网,核心目标数据为原神角色图,角色大头贴,角色昵称,角色配音文件。 待爬取页面分析本次爬取的页面为:https://ys.mihoyo.com/main/character/mondstadt,其中 mondstadt 可以替换为 liyue,inazuma。目标页面的列表页数据呈现如下图所示,数据量级不是很大。进一步提取目标数据:所有的数据...
各位爬虫爱好者,今天咱们要采集的目标站点是
原神官网
,核心目标数据为原神角色图,角色大头贴,角色昵称,角色配音文件。
待爬取页面分析
本次爬取的页面为:https://ys.mihoyo.com/main/character/mondstadt
,其中 mondstadt
可以替换为 liyue
,inazuma
。
目标页面的列表页数据呈现如下图所示,数据量级不是很大。
进一步提取目标数据:
-
所有的数据都在
li
标签中; -
其中
li
数据分为两部分,大图与音频在一个li
标签中,大头贴与角色昵称在第二个li
标签中,具体 DOM 结构如下所示:
-
最重要的音频下载地址如下图所示:
但是在实际抓取的过程中,却发现服务器返回的代码,并非开发者工具加载的样式,代码返回到前端,由前端 JS 进行了二次渲染。
具体你可以通过谷歌浏览器,打开如下链接查阅:
view-source:https://ys.mihoyo.com/main/character/mondstadt?char=0
技术细节强调
本爬虫整体实现难度不大,有一些细节需要额外进行处理。
- 数据需要通过正则表达式进行提取,因服务器直接将其返回在页面中;
- 爬虫采集过程中需要进行音频下载;
- 动态传递参数到 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)