万能的群友经常会发蓝色链接,其中有一些蓝色的蓝奏云,看见那么多学习资料实在是馋得很,必须全部拿下。
话不多说,直接开始。
0.引用包
我这里用到了urllib3,现在的主流还是requests和urllib,各有各的特点,就目前我所需要的工具来说urllib3可以向下兼容,但相对requests来说缺少cookie管理。
import urllib3 # 主角
from lxml import etree # 用于获取xpath
import re # xpath取不到的地方需要正则
import json # 用于将js字典转换为python字典
import os # 用于获得系统路径
1.分析
需要解析的URL如下图:
我们先用GET方法请求一下
# 实例化产生请求对象
http = urllib3.PoolManager(timeout=4.0)
# 构造请求包
url = '这里是需要解析的URL地址'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.3',
'referer': url
}
# get请求指定网址
resp = http.request('GET', url, headers=headers)
# 编码/解码
html = resp.data.decode('utf-8')
获得了该网页的源代码并保存到html中,现在很多网页都是这种动态式的,一堆数据发下来让你的浏览器自行处理,现在我们找到主要目标 下载链接 的构建函数:
对照一下,以上代码的执行效果如下:
现在回到函数,整个函数是通过msg的参数来运行的:
那么msg来自哪里呢,通过(初步)学习JS以及(立志)多年爬虫经验,可以知道,这个ajax方法通过post到filemoreajax.php所获取的包作为msg传递给函数,然后函数将数据整理好呈现出来。知道了这个行为,我们就可以跳过浏览器直接去这个地址拿数据。
2.过程
接下来对POST工作做一下前期铺垫:
首先需要构造data,上面的data里面有三个值(pgs / ibhkhh / ihfwja)为变量,可以从script标签开头找到,fid和uid虽然不是变量,但每个网页肯定会变,也要提取。
其余使用re进行提取,并构造包体,这里的 ‘t’ 和 ‘k’ 的变量名都是6位随机数,因此提取6位数开头的分号中的数据,pgs是页码,我们可以手动改,不用提取。
# 用re提取参数
title = re.findall(r'<title>([\w\W]+?)</title>', html, re.MULTILINE) # 提取文件名,非post参数
# pgs = re.findall(r'pgs =([\d]*);', html, re.MULTILINE)
fid = re.findall(r'\'fid\':([\w]+?),', html, re.MULTILINE)
uid = re.findall(r'\'uid\':\'([\w]+?)\',', html, re.MULTILINE)
params = re.findall(r'var [\w]{6} = \'([\w]+?)\';', html, re.MULTILINE)
# 构造fields
fields = {
'lx': 2,
'fid': int(fid[0]),
'uid': uid[0],
'pg': 1,
'rep': '0',
't': params[0],
'k': params[1],
'up': 1,
}
铺垫完成,开始进行POST,转个码,然后转换为python格式的字典:
# post文件目录接口
indexApi = 'https://www.lanzoui.com/filemoreajax.php'
indexRes = http.request('POST', indexApi, headers=headers, fields=fields)
indexData = indexRes.data.decode('unicode_escape')
# 转换为python字典
index = json.loads(indexData)
整理后的数据存在了index中,‘text’ 里面的 ‘id’ 就是我们需要获取的下载地址:
可以列个表,更加直观:
# 列出文件
print(title[0])
print('链接\t\t文件名\t\t大小\t\t时间')
for i in range(len(index['text'])):
print(index['text'][i]['id'], end='\t\t')
print(index['text'][i]['name_all'], end='\t\t')
print(index['text'][i]['size'], end='\t\t')
print(index['text'][i]['time'])
项目已经有了重大进展,但还没有完成,以最上面的链接为例:
可以发现三个按钮用的是同一个链接XD,这个链接才是下载地址,用xpath取出来GET一下(注意,GET的时候headers要带上accept-language参数,不然不给过,这个就很奇怪了)
downUrl = 'https://www.lanzoui.com/%s' % ibkvJtkvykj
downRes = http.request('GET', downUrl, headers=headers)
# 编码/解码
downData = downRes.data.decode('utf-8')
# 将data数据作为HTML选择器
selector = etree.HTML(downData)
# 下载按钮用的是iframe,所以还要再get一次真正的下载地址
downLink = selector.xpath('/html/body/div[3]/div[2]/div[4]/iframe/@src')[0]
# 请求iframe原链接
url2 = 'https://www.lanzoui.com%s' % downLink
headers2 = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36',
'Accept-Language': 'zh-CN,zh;q=0.9',
'referer': url2
}
res2 = http.request('GET', url2, headers=headers2)
data2 = res2.data.decode('utf-8')
从data2中可以看到,下载连接就在ajaxm.php中,老样子构建并POST
# sign参数会改变,要先得到sign
sign = re.findall(r'\'sign\':([\w]+?),\'ves\'', data2, re.MULTILINE)[0]
ajaxdata = re.findall(r'var ajaxdata = \'(\??[\w]+?)\';', data2, re.MULTILINE)[0]
postsign = re.findall(r'var %s = \'([\w]+?)\';' % sign, data2, re.MULTILINE)[0]
websignkey = re.findall(r'\'websignkey\':\'([\w]+?)\'', data2, re.MULTILINE)[0]
# 构建fields
fields2 = {
'action':'downprocess',
'signs':ajaxdata,
'sign':postsign,
'ves':1,
'websign':'',
'websignkey':websignkey
}
# 这次POST得到真正的下载链接
realUrl = 'https://www.lanzoui.com/ajaxm.php'
realRes = http.request('POST', realUrl, headers=headers2, fields=fields2)
realData = realRes.data.decode('utf-8')
realLink = json.loads(realData)
将dom与url组装起来就是真正的下载地址了
# 组装链接
downloadLink = realLink['dom'] + '/file/' + realLink['url']
# 然后GET
fin = http.request('GET', downloadLink, headers=headers2)
现在得到了针对浏览器的下载链接,如果要进一步偷懒则需要一点加工
这里的data实际上就是二进制流,可以直接储存为文件
# 先获取当前py文件位置
cd = os.getcwd()
# 如果当前位置没有该文件夹则创建一个
if not os.path.exists(title[0]) :
os.mkdir(cd + '\\' + title[0])
# 创建 当前位置=>标题=>文件名 然后将获取的二进制数据写入
with open('%s\\%s\\%s' % (cd, title[0], index['text'][0]['name_all']), "wb") as file:
file.write(fin.data)
print('已下载\t%s' % index['text'][0]['name_all'])
3.整理
接下来进入最后一步,写一个循环进行批量下载。循环就简单了,把单个下载线程括起来变量改掉就完事了。
跑一下
alright,结束战斗
4.完整代码
import urllib3
from lxml import etree
import re
import json
import os
def getIndex(lanzoui):
global title
global index
global headers
url = lanzoui
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.3',
'referer': url
}
resp = http.request('GET', url, headers=headers)
html = resp.data.decode('utf-8')
# pgs = re.findall(r'pgs =([\d]*);', html, re.MULTILINE) 提取出来没有用,我们手动去翻页
title = re.findall(r'<title>([\w\W]+?)</title>', html, re.MULTILINE)
fid = re.findall(r'\'fid\':([\w]+?),', html, re.MULTILINE)
uid = re.findall(r'\'uid\':\'([\w]+?)\',', html, re.MULTILINE)
params = re.findall(r'var [\w]{6} = \'([\w]+?)\';', html, re.MULTILINE)
fields = {
'lx': 2,
'fid': int(fid[0]),
'uid': uid[0],
'pg': 1,
'rep': '0',
't': params[0],
'k': params[1],
'up': 1,
}
indexApi = 'https://www.lanzoui.com/filemoreajax.php'
indexRes = http.request('POST', indexApi, headers=headers, fields=fields)
indexData = indexRes.data.decode('unicode_escape')
index = json.loads(indexData)
print(title[0])
print('链接\t\t文件名\t\t大小\t\t时间')
for i in range(len(index['text'])):
print(index['text'][i]['id'], end='\t\t')
print(index['text'][i]['name_all'], end='\t\t')
print(index['text'][i]['size'], end='\t\t')
print(index['text'][i]['time'])
def downFiles():
for i in range(len(index['text'])):
downUrl = 'https://www.lanzoui.com/%s' % index['text'][i]['id']
downRes = http.request('GET', downUrl, headers=headers)
downData = downRes.data.decode('utf-8')
selector = etree.HTML(downData)
downLink = selector.xpath('/html/body/div[3]/div[2]/div[4]/iframe/@src')[0]
url2 = 'https://www.lanzoui.com%s' % downLink
headers2 = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36',
'Accept-Language': 'zh-CN,zh;q=0.9',
'referer': url2
}
res2 = http.request('GET', url2, headers=headers2)
data2 = res2.data.decode('utf-8')
sign = re.findall(r'\'sign\':([\w]+?),', data2, re.MULTILINE)[0]
ajaxdata = re.findall(r'var ajaxdata = \'(\??[\w]+?)\';', data2, re.MULTILINE)[0]
postsign = re.findall(r'var %s = \'([\w]+?)\';' % sign, data2, re.MULTILINE)[0]
websignkey = re.findall(r'\'websignkey\':\'([\w]+?)\'', data2, re.MULTILINE)[0]
fields2 = {
'action':'downprocess',
'signs':ajaxdata,
'sign':postsign,
'ves':1,
'websign':'',
'websignkey':websignkey
}
realUrl = 'https://www.lanzoui.com/ajaxm.php'
realRes = http.request('POST', realUrl, headers=headers2, fields=fields2)
realData = realRes.data.decode('utf-8')
realLink = json.loads(realData)
downloadLink = realLink['dom'] + '/file/' + realLink['url']
fin = http.request('GET', downloadLink, headers=headers2)
cd = os.getcwd()
if not os.path.exists(title[0]):
os.mkdir(cd + '\\' + title[0])
with open('%s\\%s\\%s' % (cd, title[0], index['text'][i]['name_all']), "wb") as file:
file.write(fin.data)
print('已下载\t%s' % index['text'][i]['name_all'])
print('已完成所有下载')
http = urllib3.PoolManager(timeout=4.0)
if __name__ == '__main__':
getIndex('https://www.lanzoui.com/xxxxxxx') # 这里是网址
downFiles() # 这里是下载函数,可以考虑做一个中断控制,我用不到就不做了
# -*- coding: utf-8 -*-
# @Description : 蓝奏云是个好网站,下载请不要泛滥,良好的互联网环境需要我们共同来维护
# @Date : 2021/09/10
# Copyleft © 2021 Ursa