前言
"仙路尽头谁为峰,一见无始道成空。"
2013年初读遮天,十年后遮天的动漫正式上线。依稀记得高中记忆力"三十年河东三十年河西"的萧炎和独断万古的荒天帝。不知道当年经典绝伦的小说,如今改成动漫口碑如何。
于是就打开腾讯视频看看评分,每个视频都要点开才能看到评分和介绍。随即就萌生了用技术整合国漫评分内容的想法。最后历经一周,完成了一个简单的评分展示系统。
静态展示:
动态展示:
一. 国漫数据采集
分析评分数据
首先进入一个动漫的播放页,页面主要有左侧的评分数据,和右侧的简介数据。
评分数据获取
json
POST https://pbaccess.video.qq.com/trpc.message.grade_adapter.GradeService/GetGradeDetail?video_appid=3000010&vplatform=2
{
"cid": "mcv8hkc8zk8lnov"
}
先研究一下评分数据如何获取,在控制台可以找到从后台请求的数据内容。
从请求返回的数据可以看到,可以获取到评分、点评人数、推荐比例等数据。接着对url进行分析,看如何才能获取到这些数据。
可以看到GetGradeDetail 的url,只有一个cid参数。
简介数据获取
json
POST https://pbaccess.video.qq.com/trpc.universal_backend_service.page_server_rpc.PageServer/GetPageData?video_appid=3000010&vplatform=2&vversion_name=8.2.97
{
"req_from": "web",
"cid": "mzc00200s86alsp",
"vid": "a0047tbsjbs",
"lid": "",
"page_type": "detail_operation",
"page_id": "detail_page_introduction"
}
这里需要两个参数,cid和vid,根据我的理解,cid就是cartoon_id,是一部动漫的唯一标识,vid是video_id,是每部动漫每一集的唯一标识。那么就来看cid和vid是如何来获取。
我是通过国漫列表页跳转到播放页的,所以就去列表页看看如何获取cid。
国漫列表
进入腾讯视频的国漫列表,看一下国漫列表。
这种有侧边栏的网站,基本上都是异步请求数据,然后渲染到展示区域。下拉动漫列表:
可以看到动漫区域一直在刷新,这样就肯定了之前的想法。
cid
1. 分析请求
F12进入开发者工具,通过搜索能功能找到对应的url。
这里可以看到每部动漫的cid。然后对playload进行分析,查看请求的参数信息。
参数是一个json串,有很多参数,我们通过查看js源码,来确定这些参数是如何生成的。
2. 分析参数
通过搜索来找到对应的请求部分源码:
可以看到请求参数里面有:x、l、R、n、video_guid几个可变参数。通过分析和debug最后得知,每次请求的变化的只有x,即page_index和page,其他的参数都是固定值。
这里就举例一下:比如channel_id对应的n为什么是100119。
最后一行有个vS()方法,就是调用了上面的请求,i.value对应的就是形参n。i的生成可以在第五行代码中看到,用了一个lambda函数,遍历过滤n.channelListData.channleList。打印此变量:
过滤条件就是是channel_ename == t.channelId,这里的t.channelId通过打印发现是"cartoon",可以看到channel_ename为cartoon对应的channel_id为100119.
那么t是怎么来的呢,t是setup的参数,而setup是用来解构props的,所以t就是props,props在vue中用来接收父组件传值。
所以t就是父组件传给渲染动漫列表的组件一个参数值,其中包含channel_id。接下来的工作就是获取vid。
3. 获取cid
上面已经分析完请求了,接着就是利用python的requests模块,构建请求.通过对返回的json分析,获取目标数据。
从最后一行代码可以看到,数据在CardList[1]中获取,然后层层解构,遍历获取cid。
这样就将第一页前30个国漫的cid获取到了。当我修改变量获取第二页数据,即index = 1的时候,程序开始报下标越界的错误,那么应该是没有获取到数据,我们debug一下。
可以看到第一页数据,是从CardList[1] 获取,第二页数据就变成了CardList[0]。这是因为请求第一页的时候,需要返回筛选条件列表,放在了CardList[0]中。到了第二页就不需要了,所以这里要修改代码做判断。
然后就是对爬取的index做一个限制,目前设置为20次,即爬取20页,爬取每一页sleep(3)。这样就可以获取所有动漫的cid。
vid
我们在国漫列表页点击连接进入播放页的时候,是先进入v.qq.com/x/cover/[ci...
我们先对cid.html页面进行分析。
在html网页中发现了vid的列表信息,对于网页中数据的提取一般使用正则表达式。这样在我们获取了cid之后,就能获取vid。
至此,cid和vid都获取到了。
获取评分和简介
首先构建请求参数,cid和vid设置为空字符。
将cid放到参数中,发起评分grade_url请求。
来获取image_url(封面图片url)、热度、评分、推荐区间比例等数据。接着将cid、vid(从vids列表中任取一个即可)放到动漫简介请求参数中,发起请求。
从返回值可以获取到各种标签数据,对json解析,获取自己需要的数据。
封面图片处理
从获取的image_url中可以下载封面图片,图片存储我准备了三种方案:
- 将image_url直接存入,通过url直接引用
- 将图片下载到本地目录,然后通过命名的方式与动漫信息关联
- 将图片转换成base64存入到MySQL中
方案一可能会在请求的时候出现跨域等问题,而且必须联网,从而请求失败。方案二将图片下载到本地,比较方便。方案三就是会对服务器网络和MySQL的IO造成压力,这里是测试,所以问题不大,这里我选用了方案三。
从image_url中获取图片bytes,然后经过一些工具类转换成base64字符串。
python
urllib.request.urlretrieve(url=image_url, filename='tmp.jpg')
image_source = Image.open('tmp.jpg')
byte_source = BytesIO()
image_source.save(byte_source, format="JPEG")
byte_data = byte_source.getvalue()
base64_str = base64.b64encode(byte_data).decode("ascii")
从image_url请求的是avif的bytes,这里我直接使用urllib将图片下载保存成jpg格式。然后利用Image和BytesIO模块将二进制转换成base64的字符串。
在img标签中,通过src引入base64和引入图片路径是一样的效果。
但是这个方案在最后又被否决了,原因就是:转换成base64之后,MySQL中的varchar和Text都装不下,所以我又选择了方案二,将图片按照cid命名下载到了本地。
数据存储
设计一个存储模块,将上面的评分数据和简介数据存储到MySQL中,这里先根据定义表、数据字段。
建表
sql
create table cartoon(
cid varchar(35) not null primary key,
name varchar(50),
title varchar(100),
score varchar(5),
promoter_score varchar(20),
evaluate_number varchar(30),
type_ varchar(4) comment '类型id',
year varchar(10),
tag_text varchar(10) comment 'VIP' ,
main_genres varchar(20) comment '类型',
hotval varchar(20) comment '热度',
episode_all int(8) comment '全集',
dimension varchar(100) comment '评分比例',
update_notify_desc varchar (100) comment '更新周期',
update_time varchar(20) comment '自定义数据修改日期',
cover_description text comment '描述'
) default charset='utf8';
进入MySQL执行建表语句
开发程序
利用python的pymysql开发数据存储模块,这里一共简单实现了两个功能:
- 根据cid判断数据库中有没有这条数据存在
python
sql = f"select cid from cartoon where cid = '{cid}'"
cursor = conn.cursor()
cursor.execute(sql)
result = cursor.fetchone()
if result:
# 可以更新评分、热度、时间等字段
print(name, '已经存在于数据库中...')
continue
如果存在于数据库的话,可以执行update更新评分、热度等信息,这里先不实现,只是使用continue跳出循环,然后采集下一条数据。在程序的运行过程中,如果出现异常,重新启动程序,这些数据就可以避免再重新获取。
- 数据存储到MySQL
python
sql = f'''insert into cartoon (cid, name, title, score, promoter_score, evaluate_number, type_, year, tag_text, main_genres, hotval, episode_all, dimension, update_notify_desc, update_time, cover_description)
values ('{cid}', '{name}', '{title}', '{score}', '{promoter_score}', '{evaluate_number}', '{type_}', '{year}', '{tag_text}', '{main_genres}', '{hotval}', '{episode_all}', '{dimension}', '{update_notify_desc}', '{update_time}', '{cover_description}')'''
cursor = conn.cursor()
cursor.execute(sql)
conn.commit()
启动程序,开始爬取数据。
最后在数据库中查看爬取的国漫信息。
数据采集优化
上面请求的数据都是json格式,因为不是所有的json返回的都是全字段,很多的json都没有一些字段。所以在爬取过程中,需要根据报错信息一直调整自己的代码。
例如在解析字符串的时候,判断json里是否有这个字段,json中的json是否是NoneType,否则都会报错。下面就是针对于评分数据json的处理:
从图中可以看到,动漫信息可能是从enough 或者lack字段获取,而且还有是NoneType。我对这种情况的处理就是:如果没有评分数据,通过continue跳出这个国漫信息的爬取。
结语
本篇文章详细介绍了,如何使用python爬虫获取腾讯国漫信息,从程序开发思路和程序设计的脚步逐步深入,采集数据放到了MySQL中。下一篇文章会讲讲使用vue如何开发评分系统的前端页面。