吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2366|回复: 0
收起左侧

[Python 原创] 爬虫入门:用自己封装模块快速爬取某山音频

[复制链接]
nstar1221 发表于 2020-2-21 13:36
之前写了一个帖子爬音频的,改进了一下,分析方法都一样,只是这次自己封装了一个模块,加入了多线程,用于快速分析爬取。
1分析音频资源
1.1合集首页地址
image.png
第二页
image.png
第三页
image.png
后面以此类推。
1.2每个合集页面可以获得音频的播放网页地址(资源地址),和名称(作为文件名)
image.png
1.3查看播放页面可以获得一个audio的请求
image.png
打开后,可以看到返回的内容
image.png
src就是音频的下载地址了。
1.4总结一下:
合集页面获取所有页面的地址,每个页面获取音频资源的地址(最后一组数字为音频资源的id)。
用音频资源id拼接出请求页面,返回的字符串中提取出资源的下载地址。

2.导入自己写的模块
然后登陆网站,获取合集的网址
image.png
创建一个CrawlBug对象,传入网址,print一下看一下结果
2.1分析一下网页,找到合集名称,页面数,合集内的音频总数
这是合集名称
image.png
合集音频总数
image.png
这是合集页面数
image.png
使用.analysisHtml,传入参数
image.png
得到的返回结果如下
image.png
再用.getInfo提取需要的部分
image.png
image.png
页面数和音频总数的提取方法类似
image.png
最后的结果
image.png
一会音频总数只需要提取[0]即可。

3.继承CrawlBug和重写
3.1创建网页资源类,继承CrawlBug类
image.png
创建一个网页资源类的对象
image.png
传入合集地址,合集页面数
3.2根据前面的分析,需要获取所有合集页面的地址
image.png
在网页资源类中调用comboLinks和getPages方法
重写这两个方法。
image.png
将合集页面链接加入到.linkList中
image.png
使用getHtml获取网页html文本,使用analysisHtml匹配需要的部分(与前面获取合集标题、音频总数等类似)。
使用getInfo获取匹配到的每条音频资源地址和名称,可以使用source = self.getInfo(pattern, audio_list)获取到每个页面的匹配结果,输出source查看。依次解析linkList中的每个页面地址,获得每个页面的资源地址和资源名(作为文件名)
最后调用getSourceList并传入当前页码数
image.png
这样就可以获得sourceList了
image.png
image.png


4.创建本地目录
image.png

5.解析下载地址
由于还没有获得下载地址,需要根据前面的分析,用资源地址解析出下载地址。
重写getDownloadUrl_Thread
image.png
从source_queue中取出元素进行解析,将解析出的下载地址放入download_queue中。

6.下载
设置好线程数
image.png
启动下载
image.png
image.png
image.png
由于使用队列,读取失败的会再次加入队列,运行结束就全部下载完成了。

下面是完整代码
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
image.png
image.png
image.png
image.png
image.png

免费评分

参与人数 2吾爱币 +7 热心值 +2 收起 理由
李玉风我爱你 + 2 + 1 我很赞同!
苏紫方璇 + 5 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-16 21:49

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表