风晓

前言

昨天导员老师发了个通知,说在疫情期间,清华大学的文泉课堂资源免费开放,那何不用爬虫技术,将所有的的电子书下载下来,于是就有了这篇文章

思路

首先确定一下爬取的思路,浏览了一下发现并没有直接下载的链接,只能免费的阅读里面的书,打开阅读界面,书是以图片的形式呈现的,于是我们的爬虫思路是按顺序爬取所有的图片,最后合成一个pdf文件,这样我们就成功的获取到了这本书的pdf版

过程

我们打开浏览器,随便打开一本书,然后打开调试,发现网页源码并不能直接获得,是通过动态加载的,所以只能考虑图片地址的规律

1580799748700

我们会发现,图片的url是页数,后面是一串字符串,这时经百度发现这是jwt

可以理解成是web验证身份的一种方式吧,我也是第一次接触,在网上看了很多相关的文章,在这里推荐几篇

利用JWT生成Token的原理及公钥和私钥加密和解密的原则

JSON Web Token - 在Web应用间安全地传递信息

10分钟了解JSON Web令牌(JWT)

Jwt简介

简单地说JWT 的原理是,服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样。

1
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc

jwt由三部分构成分别如下

1
2
3
Header(头部)
Payload(负载)
Signature(签名)

我百度了一下有个jwt的在线解密的网站,进去后随便复制一个图片的token如图:

里面的数据:

  1. alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。

  2. 中间的dict是Payload,可以理解成我们要给服务器的数据,其中p代表页数,t是时间戳,b是书的id就是bid,k值我看了半天没看懂,后来在网页中我发现了个请求如下图:

    k值

刚好和上面的k值里的u,i,t,b,n对上,所以我就肯定没错了,是 这个  了,然后所有的东西确定了之后,我们就开始构建吧,然后在Headers里找到相关信息,并进行获取就行
  1. 最下面的是Signature 部分是对前两部分的签名,防止数据篡改。

算出签名以后,把 Header、Payload、Signature 三个部分拼成一个字符串,每个部分之间用”点”(.)分隔,就可以返回给用户。

代码如下图示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def gen_jwt_token(bookid, page):
''' get jwt token '''
cur_time = time.time() //时间戳
jwtkey = gen_jwt_key(bookid) //token里的k值
jwttoken = jwt.encode(
{
"p": page,
"t": int(cur_time) * 1000,
"b": str(bookid),
"w": 1000,
# "k": json.dumps(self.jwtkey),
"k": json.dumps(jwtkey),
"iat": int(cur_time),
}, //进行编码
JWT_SECRET,
algorithm='HS256',
).decode('ascii')
return jwttoken

k值获取代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def gen_jwt_key(bookid):
URL ='https://lib-nuanxin.wqxuetang.com'
url = f'{URL}/v1/read/k?bid={bookid}'
try:
resp = requests.get(url,headers = headers)
resp.raise_for_status()
except Exception as exc:
print(exc)

try:
jdata = resp.json()
except Exception as exc:
jdata = {}

res = jdata.get('data')
if res is None:
raise Exception('returned None, something is not right...')

return res

有了这些我们就可以来轻松的获取图片了

我们可以发现图片的url规律是

1
2
URL = 'https://lib-nuanxin.wqxuetang.com'
f'{URL}/page/img/{bookid}/{page}?k={token}'

然后就可以来获取图片了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def fetch_png(bookid, page):
''' download book page img '''
token = gen_jwt_token(bookid, page)
print(token)
url = f'{URL}/page/img/{bookid}/{page}?k={token}'
print(url)
headers = {
'accept': 'image / webp, image / *, * / *;q = 0.8',
'referer': f'{URL}/read/pdf/{bookid}',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36',
}
req = requests.get(url,headers=headers)
res = req.content
if not os.path.exists(str(bookid)):
os.mkdir(str(bookid))
with open(str(bookid)+'/'+str(page)+'.jpg','wb')as f:
f.write(res)

当我们获取到书的所有图片如图:

然后我们再用python将这些图片合成一个完整的pdf

具体代码如下:

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
# -*- coding:utf-8 -*-
#!/usr/bin/env python
# Author:windknew

import os
from reportlab.lib.units import mm, inch
from reportlab.lib.pagesizes import A4,A5, landscape
from reportlab.pdfgen import canvas


# 图片文件名称列表
IMAGEFILES = []

def convertpdf(pdfFile):
'''多个图片合成一个pdf文件'''
(w, h) = landscape(A5) #
cv = canvas.Canvas(pdfFile,pagesize=landscape(A5))
for imagePath in IMAGEFILES:
print(imagePath)
cv.drawImage(imagePath, 0, 0, w, h)
cv.showPage()
cv.save()

def getListImages(dirPath):
'''读取指定文件夹下所有的JPEG图片,存入列表'''
if dirPath is None or len(dirPath) == 0:
raise ValueError('dirPath不能为空,该值为存放图片的具体路径文件夹!')
if os.path.isfile(dirPath):
raise ValueError('dirPath不能为具体文件,该值为存放图片的具体路径文件夹!')
if os.path.isdir(dirPath):
for imageName in os.listdir(dirPath):
if imageName.endswith('.jpg') or imageName.endswith('.jpeg'):
absPath =converPath(dirPath) + imageName
IMAGEFILES.append(absPath)

def converPath(dirPath):
if dirPath is None or len(dirPath) == 0:
raise ValueError('dirPath不能为空!')
if os.path.isfile(dirPath):
raise ValueError('dirPath不能为具体文件,该值为文件夹路径!')
if not str(dirPath).endswith("\\"):
return dirPath + "\\"
return dirPath

if __name__ == '__main__':
getListImages('C:/Users/lyh/Desktop/ershoushu/2175744/')
pdfFile = converPath('C:/Users/lyh/Desktop/ershoushu/2175744/')+'2175744'+ ".pdf"
convertpdf(pdfFile)

结语总结

这样我们就成功的获取到了一本想要的pdf,但是文泉课堂不知什么原因有时候就会502,这样的话我们就需要多爬几次,直到把整本书的img都抓到,这次分享就到这儿了


已经到底了!:

 评论

!--动态线条背景-->