前言 昨天导员老师发了个通知,说在疫情期间,清华大学的文泉课堂资源免费开放,那何不用爬虫技术,将所有的的电子书下载下来,于是就有了这篇文章
思路 首先确定一下爬取的思路,浏览了一下发现并没有直接下载的链接,只能免费的阅读里面的书,打开阅读界面,书是以图片的形式呈现的,于是我们的爬虫思路是按顺序爬取所有的图片,最后合成一个pdf文件,这样我们就成功的获取到了这本书的pdf版
过程 我们打开浏览器,随便打开一本书,然后打开调试,发现网页源码并不能直接获得,是通过动态加载的,所以只能考虑图片地址的规律
我们会发现,图片的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如图:
里面的数据:
alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成HS256);typ属性表示这个令牌(token)的类型(type),JWT 令牌统一写为JWT。
中间的dict是Payload,可以理解成我们要给服务器的数据,其中p代表页数,t是时间戳,b是书的id就是bid,k值我看了半天没看懂,后来在网页中我发现了个请求如下图:
刚好和上面的k值里的u,i,t,b,n对上,所以我就肯定没错了,是 这个 了,然后所有的东西确定了之后,我们就开始构建吧,然后在Headers里找到相关信息,并进行获取就行
最下面的是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(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 import osfrom reportlab.lib.units import mm, inchfrom reportlab.lib.pagesizes import A4,A5, landscapefrom reportlab.pdfgen import canvasIMAGEFILES = [] 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都抓到,这次分享就到这儿了