Tmorrow



python中使用js逆向爬取腾讯漫画_搜索

python中使用js逆向爬取腾讯漫画_动漫_02

python中使用js逆向爬取腾讯漫画_搜索_03


NO.01

篇幅过长还请慢慢观看

>>>>>>>>>>


相比较于之前很长一段时间暗淡无光的国漫,近年来相对比较优秀的作品开始陆续出现,其题材和制作也给市场注入了不少新鲜血液。一提起漫画,想必大家或多或少的想到了几步不错的作品,至于漫画的平台,想必对于二次元爱好者来讲,除了B站,腾讯动漫也是一个不错的去出。目前大陆动漫产业目前在处于上升阶段,腾讯动漫平台给了漫画原作者一个很好的机会,至少我在b站看的几部不错的非3d国产动画都是来自腾讯动漫平台。

腾讯动漫平台应该是朝着日本漫画动画产业发展的,将平台人气高的漫画做成动画,提升漫画作者知名度,鼓励优秀漫画创作。当然目前改编的漫画作品都是偏向于成人向,《尸兄》《中国惊奇先生》《王牌御史》等,不乏稍微卖肉的画面,而且常常出现时事新闻中的梗,这也阻碍了神作的产生,不过在久病不振的喜羊羊等幼稚动画充斥的国产动漫行业已经是一股新风。腾讯应该有做成第一国漫平台的野心。

腾讯动漫网页版,主要涉及到三个方面的内容:动画、漫画、直播三个方面;其中在漫画板块,腾讯利用自己强大的资本,在国内招募了一大批很有潜力的漫画作者并且鼓励国漫的创新。此外还从日本引进了一大批热销的正版漫画;漫画本身是一个重内容得行业,内容丰富起来,用户增长率能够在短时间内达到飞跃,说白了气量非常之快。所以说腾讯也称得上是整个动漫产业链条的巨头。

书归正文,前几天给大家介绍了爬取漫画网站的思路,并通过爬取一个小型的漫画网站作为演示案例,​​~传送链接~​​ ,但是这并未阻止小编想要起飞的步伐,不死心的小编想啃一块硬骨头,也就是爬取腾讯动漫上面的漫画,这就是为什么有了前几段的介绍了。

由于今天涉及的内容颇多,还请各位看官搬上小板凳,带上香瓜子,慢慢细品,话不多说,第一步还是上链接,作为我们的目标网站,首先吧链接贴出来,如下:

https://ac.qq.com/

由于最终的目的是为了爬取漫画的内容页,所以开门见山直接找一个漫画进行页面分析,(该文章主要用于爬取免费的漫画,收费请看代码注释),我们以《尸兄》第一话为例:

https://ac.qq.com/ComicView/index/id/17114/cid/3

按F12进行元素审查,通过往下滑动可以看到图片不断的加载出来,所以内容可能是动态加载出来的。

python中使用js逆向爬取腾讯漫画_搜索_04

页面动态加载内容的话,此时让我们切换到XHR标签,如图:

python中使用js逆向爬取腾讯漫画_搜索_05

通过分析过后,我们发现这两个接口,除了返回的有弹幕外,其他的什么数据都没有返回,所以数据并不是通过此处返回的。

python中使用js逆向爬取腾讯漫画_搜索_06

没办法,看来不是从这请求的数据,接下来让我们找一个图片的名称进行全局搜索,看看哪些文件夹里面包含了这些名称。以便确定数据到底存放在了哪里。

python中使用js逆向爬取腾讯漫画_动漫_07

通过进行的全局搜索,我们发现除了只能搜到这个图片,也没发现其他的文件内有存储该图片的位置,说明这个方法也是行不通的。图片的地址既没有动态加载,也没有在其他的源代码中,那么只能说明地址是被加密放到了某一个地方。除去图片,js,css这些,剩下有可能的就只有主页了,所以直接看主页的源代码,并且主要关注script标签下的内容,整个主页的源代码中,有可能存放加密数据的就只有倒数后面的一个script标签。

python中使用js逆向爬取腾讯漫画_动漫_08

从页面的内容来看,DATA变量很有可能就是加密的数据,此时需要查找如何解密这段数据,继续在全局中搜索DATA,并且区分大小写。

python中使用js逆向爬取腾讯漫画_数据_09

由此可以看到除了自身以外,就剩下一个js文件内有出现过DATA变量,那么进入到这个js文件里面继续搜索DATA,并且同样需要区分大小写。

python中使用js逆向爬取腾讯漫画_动漫_10

搜索后发现“那个村的王富贵”还是蛮多的,浏览一遍发现,除了第一个以外,其他都是取DATA变量的值,说明关键在第一个DATA变量,因为还没有解密又怎么取值呢。直接在第一次出现DATA变量的那一行打上断点,然后刷新。

python中使用js逆向爬取腾讯漫画_搜索_11

如上图,可以看到所有的图片地址都在_v变量的下面,继续在全局中搜索_v变量,也是要区分大小写。

python中使用js逆向爬取腾讯漫画_搜索_12

搜索到13个内容,简单的过了一遍,发现除了取值,函数变量以及一些其他的字符串包含这个,剩下的一个在第一个DATA变量上面的立即执行函数里面。

eval(function(p, a, c, k, e, r) {
e = function(c) {
return (c < a ? "" : e(parseInt(c / a))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36))
}
;
if (!"".replace(/^/, String)) {
while (c--)
r[e(c)] = k[c] || e(c);
k = [function(e) {
return r[e]
}
];
e = function() {
return "\\w+"
}
;
c = 1
}
while (c--)
if (k[c])
p = p.replace(new RegExp("\\b" + e(c) + "\\b","g"), k[c]);
return p
}("p y(){i=\"J+/=\";O.D=p(c){s a=\"\",b,d,h,f,g,e=0;C(c=c.z(/[^A-G-H-9\\+\\/\\=]/g,\"\");e<c.k;)b=i.l(c.m(e++)),d=i.l(c.m(e++)),f=i.l(c.m(e++)),g=i.l(c.m(e++)),b=b<<2|d>>4,d=(d&15)<<4|f>>2,h=(f&3)<<6|g,a+=7.5(b),w!=f&&(a+=7.5(d)),w!=g&&(a+=7.5(h));v a=u(a)};u=p(c){C(s a=\"\",b=0,d=17=8=0;b<c.k;)d=c.o(b),Q>d?(a+=7.5(d),b++):R<d&&S>d?(8=c.o(b+1),a+=7.5((d&F)<<6|8&r),b+=2):(8=c.o(b+1),x=c.o(b+2),a+=7.5((d&15)<<12|(8&r)<<6|x&r),b+=3);v a}}s B=I y(),T=W['K'+'L'].M(''),N=W['n'+'P'+'e'],j,t,q;N=N.U(/\\d+[a-V-Z]+/g);j=N.k;X(j--){t=Y(N[j])&10;q=N[j].z(/\\d+/g,'');T.11(t,q.k)}T=T.13('');14=16.E(B.D(T));", 62, 70, "|||||fromCharCode||String|c2||||||||||_keyStr|len|length|indexOf|charAt||charCodeAt|function|str|63|var|locate|_utf8_decode|return|64|c3|Base|replace|||for|decode|parse|31|Za|z0|new|ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789|DA|TA|split||this|onc|128|191|224||match|zA||while|parseInt||255|splice||join|_v||JSON|c1".split("|"), 0, {}))

接着先将这一段立即执行函数解密一下,使用的是 ~解密网址~ ,可以得到下面的js代码。

#解密网址
#https://wangye.org/tools/scripts/eval/
function Base() {
_keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
this.decode = function(c) {
var a = "",
b, d, h, f, g, e = 0;
for (c = c.replace(/[^A-Za-z0-9\+\/\=]/g, ""); e < c.length;) b = _keyStr.indexOf(c.charAt(e++)),
d = _keyStr.indexOf(c.charAt(e++)),
f = _keyStr.indexOf(c.charAt(e++)),
g = _keyStr.indexOf(c.charAt(e++)),
b = b << 2 | d >> 4,
d = (d & 15) << 4 | f >> 2,
h = (f & 3) << 6 | g,
a += String.fromCharCode(b),
64 != f && (a += String.fromCharCode(d)),
64 != g && (a += String.fromCharCode(h));
return a = _utf8_decode(a)
};
_utf8_decode = function(c) {
for (var a = "",
b = 0,
d = c1 = c2 = 0; b < c.length;) d = c.charCodeAt(b),
128 > d ? (a += String.fromCharCode(d), b++) : 191 < d && 224 > d ? (c2 = c.charCodeAt(b + 1), a += String.fromCharCode((d & 31) << 6 | c2 & 63), b += 2) : (c2 = c.charCodeAt(b + 1), c3 = c.charCodeAt(b + 2), a += String.fromCharCode((d & 15) << 12 | (c2 & 63) << 6 | c3 & 63), b += 3);
return a
}
}
var B = new Base(),
T = W['DA' + 'TA'].split(''),
N = W['n' + 'onc' + 'e'],
len,
locate,
str;
N = N.match(/\d+[a-zA-Z]+/g);
len = N.length;
while (len--) {
locate = parseInt(N[len]) & 255;
str = N[len].replace(/\d+/g, '');
T.splice(locate, str.length)
}
T = T.join('');
_v = JSON.parse(B.decode(T));

由此可以看出第40行就是解密后的_v变量,第27行就是我们前面的DATA加密数据,那么可以肯定这段就是关键的解密函数,分析一下这段代码。

_v是调用了B的decode方法,传入的参数是T,然后T又是从前面T参数和N参数计算出来的,那么我们在console界面分别输入W['DA' + 'TA'],W['n' + 'onc' + 'e'],其中W['DA' + 'TA']就是前面的DATA加密数据,W['n' + 'onc' + 'e']可以在主页源代码中找到。

python中使用js逆向爬取腾讯漫画_动漫_13

python中使用js逆向爬取腾讯漫画_搜索_14

先初步尝试获取这两个数据

def getdata():
import requests
import re
url = 'https://ac.qq.com/ComicView/index/id/17114/cid/3'
response = requests.get(url).text
data = re.findall("(?<=var DATA = ').*?(?=')", response)[0]
nonce = re.findall('window\[".+?(?<=;)', response)[0]
nonce = '='.join(nonce.split('=')[1:])[:-1]
print(data)
print(nonce)

但是nonce还是一段js代码,而不是字符串。在这我们使用execjs模块来计算,首先安装模块:

#未安装该模块请先安装
#pip install PyExecJS


def getdata():
import requests
import re
import execjs
url = 'https://ac.qq.com/ComicView/index/id/531490/cid/1'
response = requests.get(url).text
data = re.findall("(?<=var DATA = ').*?(?=')", response)[0]
nonce = re.findall('window\[".+?(?<=;)', response)[0]
nonce = '='.join(nonce.split('=')[1:])[:-1]
nonce = execjs.eval(nonce)
print(data)
print(nonce)

此时成功获取两个参数,然后根据js代码,那么在这里我们只需要把上面的js函数转写为python代码就可以成功的完成取参了,此时解密后的地址都是_v变量里面了。

import requests
import re
import execjs
import json


def getdata():


url = 'https://ac.qq.com/ComicView/index/id/17114/cid/3'
while True:
try:
response = requests.get(url).text
data = re.findall("(?<=var DATA = ').*?(?=')", response)[0]
nonce = re.findall('window\[".+?(?<=;)', response)[0]
nonce = '='.join(nonce.split('=')[1:])[:-1]
nonce = execjs.eval(nonce)
break
except:
pass
T = list(data)
N = re.findall('\d+[a-zA-Z]+', nonce)
jlen = len(N)
while jlen:
jlen -= 1
jlocate = int(re.findall('\d+', N[jlen])[0]) & 255
jstr = re.sub('\d+', '', N[jlen])
del T[jlocate:jlocate + len(jstr)]
T = ''.join(T)
keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
a = []
e = 0
while e < len(T):
b = keyStr.index(T[e])
e += 1
d = keyStr.index(T[e])
e += 1
f = keyStr.index(T[e])
e += 1
g = keyStr.index(T[e])
e += 1
b = b << 2 | d >> 4
d = (d & 15) << 4 | f >> 2
h = (f & 3) << 6 | g
a.append(b)
if 64 != f:
a.append(d)
if 64 != g:
a.append(h)
_v = json.loads(bytes(a))
print(_v)

解密步骤如下:

python中使用js逆向爬取腾讯漫画_数据_15

最后遍历_v变量里面的所有地址下载即可完成。





干货满满,看官请好。  


NO.02

明天还要加油哦

>>>>>>>>>>

在上面我们既然拿到了页面返回的值后,接下来的操作也就简单了。

本次我们是通过漫画的id进行漫画爬取,所以我们不需要再做搜索的那种形式了,若是有兴趣的话可以参照​​~上一篇文章~​​,此处直接让我们进入到漫画的章节页面。地址如下:

https://ac.qq.com/Comic/comicInfo/id/17114

通过审查元素我们可以发现,所有的章节链接都是通过一个ol的标签进行包裹,所以我们只要获取要页面中的一个ol标签下,所有的a链接就可以成功的获取到所有的章节链接了。

python中使用js逆向爬取腾讯漫画_动漫_16

代码如下:

#获取漫画的章节地址
def get_chapter_info(self):
chapter_info = {}
url = 'http://ac.qq.com/Comic/ComicInfo/id/{}'.format(self.comic_id)
html_text = self.get_html(url)
html = self.parser(html_text)
# 找到所有章节列表
ol = html.find('ol')[0]
chapters = ol.find('a')
index = 0
for chapter in chapters:
title = chapter.attrs['title']
link = parse.urljoin(TxComic.COMIC_HOST, chapter.attrs['href'])
key = '第{}章'.format(index)
chapter_info[key] = {'title': title, 'link': link}
index += 1
return chapter_info

获取到漫画的所有章节后,我们可以通过请求每一章节的链接,来获取页面的具体信息,代码如下:

# 请求漫画每一章节的url链接
def get_comic_info(self):
chapters = self.get_chapter_info()
# print(chapters)
for k, v in chapters.items():
url = v['link']
pics = self.get_chapter_pics(url)
self.async_data(pics)


# 分析数据并下载对应章节图片
def async_data(self, res_data):
book_name = res_data['comic']['title']
if not os.path.exists(book_name):
os.mkdir(book_name)
chapter_tname = "第" + str(res_data['chapter']['cid']) + '章__' + res_data['chapter']['cTitle']
chapter_name = eval(repr(chapter_tname).replace('/', '@'))
# print(chapter_name)
path = os.path.join(book_name, chapter_name)
if not os.path.exists(path):
os.mkdir(path)
# print(res_data['picture'])
for index, v in enumerate(res_data['picture']):
name = os.path.join(path, "{}.png".format(index))
self.download_img(name, v['url'])
print(chapter_name + "爬取完毕")

在此处我们可以成功的请求到每一个url链接,接下来我们只需要对返回的页面进行js解密,然后取出_v下面的数据并下载就可以了。代码如下:

# js解码获取章节信息
def get_chapter_pics(slef, url):
while True:
try:
response = requests.get(url).text
# 获取W['DA' + 'TA']
data = re.findall("(?<=var DATA = ').*?(?=')", response)[0]
nonce = re.findall('window\[".+?(?<=;)', response)[0]
nonce = '='.join(nonce.split('=')[1:])[:-1]
# 获取W['n' + 'onc' + 'e']
nonce = execjs.eval(nonce)
break
except:
pass
# 模拟js运行,进行数据解码
T = list(data)
N = re.findall('\d+[a-zA-Z]+', nonce)
jlen = len(N)
while jlen:
jlen -= 1
jlocate = int(re.findall('\d+', N[jlen])[0]) & 255
jstr = re.sub('\d+', '', N[jlen])
del T[jlocate:jlocate + len(jstr)]
T = ''.join(T)
keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
a = []
e = 0
while e < len(T):
b = keyStr.index(T[e])
e += 1
d = keyStr.index(T[e])
e += 1
f = keyStr.index(T[e])
e += 1
g = keyStr.index(T[e])
e += 1
b = b << 2 | d >> 4
d = (d & 15) << 4 | f >> 2
h = (f & 3) << 6 | g
a.append(b)
if 64 != f:
a.append(d)
if 64 != g:
a.append(h)
_v = json.loads(bytes(a))
return _v


代码整合如下:

import re
import json
import os
from urllib import parse
import execjs
import requests
from requests_html import HTML
from requests.adapters import Retry
from requests.adapters import HTTPAdapter


# 该代码适用于免费的腾讯漫画爬取,若是付费文章,
# 请携带session或者cookie进行爬取,在此不做演示
class Spider:


def __init__(self):
self.session = requests.session()
# add requests http adapter with retry, including
http_adapter = HTTPAdapter(max_retries=Retry(
total=3, method_whitelist=frozenset(['GET', 'POST', 'PUT'])))
self.session.mount('http://', http_adapter)
self.session.mount('https://', http_adapter)


def update_session_headers(self, headers):
self.session.headers.update(headers)


def get_html(self, url):
try:
r = self.session.get(url)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
print('get html failed')
return None


def parser(self, text):
'''文本转换为reques_html的对象,方便我们做各种解析'''
return HTML(html=text)


def download_img(self, name, url):
with open(name, 'wb') as f:
f.write(requests.get(url).content)


class TxComic(Spider):


COMIC_HOST = 'http://ac.qq.com'
HEADERS = {
'User-Agent': ('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/65.0.3325.146 Safari/537.36')
}


def __init__(self, comic_id):
'''
init腾讯漫画的爬虫
comic_id 是漫画的id 比如海贼王的是`505430`
'''
super(TxComic, self).__init__()
self.comic_id = comic_id
self.update_session_headers(TxComic.HEADERS)


#获取漫画的章节地址
def get_chapter_info(self):
chapter_info = {}
url = 'http://ac.qq.com/Comic/ComicInfo/id/{}'.format(self.comic_id)
html_text = self.get_html(url)
html = self.parser(html_text)
# 找到所有章节列表
ol = html.find('ol')[0]
chapters = ol.find('a')
index = 0
for chapter in chapters:
title = chapter.attrs['title']
link = parse.urljoin(TxComic.COMIC_HOST, chapter.attrs['href'])
key = '第{}章'.format(index)
chapter_info[key] = {'title': title, 'link': link}
index += 1
return chapter_info


# 请求漫画每一章节的url链接
def get_comic_info(self):
chapters = self.get_chapter_info()
# print(chapters)
for k, v in chapters.items():
url = v['link']
pics = self.get_chapter_pics(url)
self.async_data(pics)


# 分析数据并下载对应章节图片
def async_data(self, res_data):
book_name = res_data['comic']['title']
if not os.path.exists(book_name):
os.mkdir(book_name)
chapter_tname = "第" + str(res_data['chapter']['cid']) + '章__' + res_data['chapter']['cTitle']
chapter_name = eval(repr(chapter_tname).replace('/', '@'))
# print(chapter_name)
path = os.path.join(book_name, chapter_name)
if not os.path.exists(path):
os.mkdir(path)
# print(res_data['picture'])
for index, v in enumerate(res_data['picture']):
name = os.path.join(path, "{}.png".format(index))
self.download_img(name, v['url'])
print(chapter_name + "爬取完毕")


# js解码获取章节信息
def get_chapter_pics(slef, url):
while True:
try:
response = requests.get(url).text
# 获取W['DA' + 'TA']
data = re.findall("(?<=var DATA = ').*?(?=')", response)[0]
nonce = re.findall('window\[".+?(?<=;)', response)[0]
nonce = '='.join(nonce.split('=')[1:])[:-1]
# 获取W['n' + 'onc' + 'e']
nonce = execjs.eval(nonce)
break
except:
pass
# 模拟js运行,进行数据解码
T = list(data)
N = re.findall('\d+[a-zA-Z]+', nonce)
jlen = len(N)
while jlen:
jlen -= 1
jlocate = int(re.findall('\d+', N[jlen])[0]) & 255
jstr = re.sub('\d+', '', N[jlen])
del T[jlocate:jlocate + len(jstr)]
T = ''.join(T)
keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
a = []
e = 0
while e < len(T):
b = keyStr.index(T[e])
e += 1
d = keyStr.index(T[e])
e += 1
f = keyStr.index(T[e])
e += 1
g = keyStr.index(T[e])
e += 1
b = b << 2 | d >> 4
d = (d & 15) << 4 | f >> 2
h = (f & 3) << 6 | g
a.append(b)
if 64 != f:
a.append(d)
if 64 != g:
a.append(h)
_v = json.loads(bytes(a))
return _v


if __name__ == '__main__':


print('example:漫画链接为:https://ac.qq.com/Comic/comicInfo/id/511915,则id编号为:511915')
comic_id = input("请输入漫画id编号:")
if comic_id.isdigit():
tool = TxComic(comic_id)
tool.get_comic_info()
else:
print("输入id编号需为纯数字,请重新输入")
exit()


至此代码结束,右击运行代码,根据你的需求下载你想要下载的漫画,以上就是今天给大家分享的内容,源代码获取请回复“腾讯漫话”。

更多精品教程请关注公众号SpiderBy,回复“Python教程”,即可获取*智基础+就业班课程。