之前写了一个帖子爬音频的,改进了一下,分析方法都一样,只是这次自己封装了一个模块,加入了多线程,用于快速分析爬取。
1分析音频资源
1.1合集首页地址
第二页
第三页
后面以此类推。
1.2每个合集页面可以获得音频的播放网页地址(资源地址),和名称(作为文件名)
1.3查看播放页面可以获得一个audio的请求
打开后,可以看到返回的内容
src就是音频的下载地址了。
1.4总结一下:
合集页面获取所有页面的地址,每个页面获取音频资源的地址(最后一组数字为音频资源的id)。
用音频资源id拼接出请求页面,返回的字符串中提取出资源的下载地址。
2.导入自己写的模块
然后登陆网站,获取合集的网址
创建一个CrawlBug对象,传入网址,print一下看一下结果
2.1分析一下网页,找到合集名称,页面数,合集内的音频总数
这是合集名称
合集音频总数
这是合集页面数
使用.analysisHtml,传入参数
得到的返回结果如下
再用.getInfo提取需要的部分
页面数和音频总数的提取方法类似
最后的结果
一会音频总数只需要提取[0]即可。
3.继承CrawlBug和重写
3.1创建网页资源类,继承CrawlBug类
创建一个网页资源类的对象
传入合集地址,合集页面数
3.2根据前面的分析,需要获取所有合集页面的地址
在网页资源类中调用comboLinks和getPages方法
重写这两个方法。
将合集页面链接加入到.linkList中
使用getHtml获取网页html文本,使用analysisHtml匹配需要的部分(与前面获取合集标题、音频总数等类似)。
使用getInfo获取匹配到的每条音频资源地址和名称,可以使用source = self.getInfo(pattern, audio_list)获取到每个页面的匹配结果,输出source查看。依次解析linkList中的每个页面地址,获得每个页面的资源地址和资源名(作为文件名)
最后调用getSourceList并传入当前页码数
这样就可以获得sourceList了
4.创建本地目录
5.解析下载地址
由于还没有获得下载地址,需要根据前面的分析,用资源地址解析出下载地址。
重写getDownloadUrl_Thread
从source_queue中取出元素进行解析,将解析出的下载地址放入download_queue中。
6.下载
设置好线程数
启动下载
由于使用队列,读取失败的会再次加入队列,运行结束就全部下载完成了。
下面是完整代码
import re
from dwspackage.crawlbug import CrawlBug
class WebSource(CrawlBug):
def __init__(self, url, pages):
super().__init__(url)
self.pages = pages
self.comboLinks()
self.getPages()
def comboLinks(self):
"""
根据网页页码规律重写
"""
for i in range(self.pages):
if i == 0:
self.linkList.append(self.url)
continue
tmp = self.url + "p" + str(i + 1) + "/"
self.linkList.append(tmp)
def getPages(self):
"""
获取全部的资源地址并合成资源列表。
:return: [页码.编号,名称,资源地址]
"""
print("开始获取资源列表")
for i in range(self.pages):
self.tmpHtml = self.getHtml(url=self.linkList[i])
audio_list = self.analysisHtml('class', 'text _Vc')
# 获取资源连接和资源名称
pattern = '<a href="(.+?)" title="(.+?)">'
# 得到每个页面的资源连接
self.getInfo(pattern, audio_list)
# 解析并获取下载连接
page = i + 1
self.getSourceList(page)
pass
def getDownloadUrl_Thread(self):
"""
从资源列表中读取资源地址,获取下载地址,并加入到下载地址队列
source_queue:[序号,文件名,资源地址]
download_quequ:[下载地址,文件名]
:return:
"""
while not self.source_queue.empty():
tmp = self.source_queue.get()
# print(tmp) # pass
try:
pattern = r'.+/(\d+)'
tmp_source = 'https://www.ximalaya.com/revision/play/v1/audio?id=' \
+ (re.findall(pattern, tmp[2]))[0] + '&ptype=1'
response = str(self.getHtml(tmp_source))
pattern = '"src":"(.+?)","'
# 返回下载地址
tmp_source = (re.findall(pattern, response))[0]
# print(type(tmp_source)) # pass
tmp_file = tmp[0] + "." + tmp[1] + "." + self.fileType
# print(tmp_file)
self.download_queue.put([tmp_source, tmp_file])
# print([tmp_source, tmp_file])
print("资源解析中")
except Exception as e:
self.source_queue.put(tmp)
print(e)
pass
pass
if __name__ == "__main__":
targetUrl = 'https://www.ximalaya.com/youshengshu/16411402/'
# 创建一个CrawBug对象
myCrawl = CrawlBug(targetUrl)
# print(myCrawl.html)
# 分析网页:获取专辑名
folderTitle = myCrawl.analysisHtml('class', 'title lO_')
# print(folderTitle)
targetPattern = '>(.+?)<'
folderTitle = myCrawl.getInfo(targetPattern, folderTitle)
# print(folderTitle)
# 分析网页:获取专辑页码数
pageNumber = myCrawl.analysisHtml('placeholder', '请输入页码')
targetPattern = r'max="(\d+)"'
pageNumber = myCrawl.getInfo(targetPattern, pageNumber)
# print(pageNumber)
# 分析网页:获取资源总数
itemNumber = myCrawl.analysisHtml('class', '_Qp')
targetPattern = r'专辑里的声音\(<!-- -->(\d+)<!-- -->\)'
itemNumber = myCrawl.getInfo(targetPattern, itemNumber)
# print(itemNumber)
# 创建一个网页对象,继承CrawlBug
# 分析网页:创建网页资源对象,获取当前合集的全部下载连接
xmly = WebSource(targetUrl, int(pageNumber[0]))
# print(xmly.sourceList)
# 当前合集的全部下载连接
if xmly.sourceList:
# 创建目录
xmly.path = 'd:/download/source/'
# 使用合集名作为文件夹名
xmly.folder = str(folderTitle[0])
xmly.fileType = 'm4a'
xmly.mkdir()
# 下载线程数
xmly.download = 4
# 解析线程数
xmly.analysis = 10
xmly.readyDownload()
xmly.startDownload()
print("下载完成")
自写模块
import os
import re
from queue import Queue
from urllib import request
from threading import Thread, Lock
from bs4 import BeautifulSoup
from fake_useragent import UserAgent
class CrawlBug:
"""
接收一个网址,返回网页字符串(utf-8)
获取接收两个个列表:[序号,文件名,资源地址]、[文件夹名称,分页数,资源总数],爬取资源到本地存储
使用方法:
1.创建一个Crawlbug对象,初始化列表中传入爬取合集目录的网页地址
2.可以用成员属性.html看一下内容,分析出爬取的部分
"""
def __init__(self, url):
# 下载线程数
self.download = 4
# 解析线程数
self.analysis = 10
# UserAgent
self.headers = [('User-Agent', UserAgent().random)]
# 爬取网页地址
self.url = url
# 解析文本
self.html = self.getHtml()
# 临时解析文本
self.tmpHtml = self.html
# 爬取指定信息的返回数据
self.info = None
# 爬取合集名作为文件夹名
self.folder = None
# 爬取合集页面数
self.pages = 0
# 爬取合集内容总数
self.items = 0
# 爬取合集页面链接列表,与self.pages匹配,[页面地址]
self.linkList = []
# 爬取合集全部资源地址列表,[序号,文件名,资源地址]
self.sourceList = []
# 本地存储文件夹路径
self.path = None
# 创建文件名,传入参数后与self.path合成创建的文件夹路径
self.fileType = None
# 临时资源地址
self.tmpSource = None
# 临时下载地址
self.tmpUrl = None
# 临时文件名
self.tmpFile = None
# 资源地址队列
self.source_queue = Queue()
# 下载地址队列
self.download_queue = Queue()
# 线程锁
self.lock = Lock()
# 线程进度计数
self.count = 1
def getHtml(self, url=None, headers=None, parser=None):
"""
网页转码
:param url:
:param headers:
:param parser:
:return: 返回一个bs对象
"""
try:
opener = request.build_opener()
if headers is not None:
opener.addheaders = headers
else:
opener.addheaders = self.headers
request.install_opener(opener)
if url is not None:
page = request.urlopen(url).read()
else:
page = request.urlopen(self.url).read()
if parser is not None:
tmp_parser = parser
else:
tmp_parser = 'lxml'
except Exception as e:
print(e)
return None
else:
return BeautifulSoup(page, tmp_parser)
def analysisHtml(self, strAttr, strName):
"""
提取bs对象中的匹配项
:param strAttr:
:param strName:
:return:
"""
return self.tmpHtml.find_all(attrs={strAttr: strName})
pass
def getInfo(self, pattern, soupResult):
"""
使用正则表达式提取目标字符串
:param pattern:
:param soupResult:
:return:
"""
self.info = re.findall(pattern, str(soupResult))
return self.info
pass
def mkdir(self):
"""
创建目录
:return:
"""
self.path = self.path + self.folder + "/"
folder = os.path.exists(self.path)
if not folder:
os.makedirs(self.path)
print("创建文件夹")
else:
print("文件夹已存在")
pass
def runDownload(self):
"""
启动下载
:return:
"""
try:
opener = request.build_opener()
opener.addheaders = self.headers
request.install_opener(opener)
request.urlretrieve(self.tmpUrl, self.tmpFile)
except Exception as e:
print(e)
pass
def readyDownload(self):
"""
启动资源解析,从资源列表self.sourceList中解析出下载地址
:return:
"""
for item in self.sourceList:
self.source_queue.put(item)
for i in range(self.analysis):
t = Thread(target=self.getDownloadUrl_Thread)
t.start()
def doDownload(self):
"""
从下载地址队列中取出下载地址和文件名,并下载
:return:
"""
while not self.download_queue.empty():
tmp = self.download_queue.get()
try:
self.lock.acquire()
self.tmpUrl = tmp[0]
self.tmpFile = self.path + tmp[1]
self.runDownload()
print("下载进度 %d/%d" % (self.count, len(self.sourceList)))
self.count += 1
self.lock.release()
except Exception as e:
self.download_queue.put(tmp)
print(e)
def startDownload(self):
"""
启动下载
:return:
"""
self.count = 1
for i in range(self.download):
t = Thread(target=self.doDownload)
t.start()
pass
def getSourceList(self, page):
"""
获取资源并生成列表。
视具体情况,可能要在子类中重写。
:param page: 当前页码
:return: [序号,文件名,资源地址]
"""
index = 1
for source in self.info:
link = str(source[0])
title = str(source[1])
self.sourceList.append([str(page) + "." + str(index), title, link])
print("获取第%d页,第%d条记录" % (page, index))
index += 1
pass
def comboLinks(self):
"""
在子类中实现
将合集每个页面的连接加入到self.linkList
"""
pass
def getPages(self):
"""
在子类中实现
将合集每个页面的资源连接和资源名称整合,得到一个列表self.sourceList,[序号,文件名,资源地址]
:return: [页码.编号,名称,资源地址]
"""
pass
def getDownloadUrl_Thread(self):
"""
需要在子类中实现
提取加入到queue中的self.sourceList的元素的资源地址,解析出下载地址,并加入到下载地址队列
source_queue:[序号,文件名,资源地址]
download_quequ:[下载地址,文件名]
"""
pass
|