nstar1221 发表于 2020-2-21 13:36

爬虫入门:用自己封装模块快速爬取某山音频

之前写了一个帖子爬音频的,改进了一下,分析方法都一样,只是这次自己封装了一个模块,加入了多线程,用于快速分析爬取。
1分析音频资源
1.1合集首页地址

第二页

第三页

后面以此类推。
1.2每个合集页面可以获得音频的播放网页地址(资源地址),和名称(作为文件名)

1.3查看播放页面可以获得一个audio的请求

打开后,可以看到返回的内容

src就是音频的下载地址了。
1.4总结一下:
合集页面获取所有页面的地址,每个页面获取音频资源的地址(最后一组数字为音频资源的id)。
用音频资源id拼接出请求页面,返回的字符串中提取出资源的下载地址。

2.导入自己写的模块
然后登陆网站,获取合集的网址

创建一个CrawlBug对象,传入网址,print一下看一下结果
2.1分析一下网页,找到合集名称,页面数,合集内的音频总数
这是合集名称

合集音频总数

这是合集页面数

使用.analysisHtml,传入参数

得到的返回结果如下

再用.getInfo提取需要的部分


页面数和音频总数的提取方法类似

最后的结果

一会音频总数只需要提取即可。

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)
            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)) + '&ptype=1'
                response = str(self.getHtml(tmp_source))
                pattern = '"src":"(.+?)","'
                # 返回下载地址
                tmp_source = (re.findall(pattern, response))
                # print(type(tmp_source))# pass
                tmp_file = tmp + "." + tmp + "." + self.fileType
                # print(tmp_file)
                self.download_queue.put()
                # print()
                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))
    # print(xmly.sourceList)
    # 当前合集的全部下载连接
    if xmly.sourceList:
      # 创建目录
      xmly.path = 'd:/download/source/'
      # 使用合集名作为文件夹名
      xmly.folder = str(folderTitle)
      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
                self.tmpFile = self.path + tmp
                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)
            title = str(source)
            self.sourceList.append()
            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
```
页: [1]
查看完整版本: 爬虫入门:用自己封装模块快速爬取某山音频