python爬取基于m3u8协议的ts文件并合并


Posted in Python onApril 26, 2019

前言

简单学习过网络爬虫,只是之前都是照着书上做并发,大概能理解,却还是无法自己用到自己项目中,这里自己研究实现一个网页嗅探HTML5播放控件中基于m3u8协议ts格式视频资源的项目,并未考虑过复杂情况,毕竟只是练练手.

源码

# coding=utf-8
import asyncio
import multiprocessing
import os
import re
import time
from math import floor
from multiprocessing import Manager
import aiohttp
import requests
from lxml import html
import threading
from src.my_lib import retry
from src.my_lib import time_statistics


class M3U8Download:
 _path = "./resource\\" # 本地文件路径
 _url_seed = None # 资源所在链接前缀
 _target_url = {} # 资源任务目标字典
 _mode = ""
 _headers = {"User-agent": "Mozilla/5.0"} # 浏览器代理
 _target_num = 100

 def __init__(self):
 self._ml = Manager().list() # 进程通信列表
 if not os.path.exists(self._path): # 检测本地目录存在否
  os.makedirs(self._path)
 exec_str = r'chcp 65001'
 os.system(exec_str) # 先切换utf-8输出,防止控制台乱码

 def sniffing(self, url):
 self._url = url
 print("开始嗅探...")
 try:
  r = requests.get(self._url) # 访问嗅探网址,获取网页信息
 except:
  print("嗅探失败,网址不正确")
  os.system("pause")
 else:
  tree = html.fromstring(r.content)
  try:
  source_url = tree.xpath('//video//source/@src')[0] # 嗅探资源控制文件链接,这里只针对一个资源控制文件
  # self._url_seed = re.split("/\w+\.m3u8", source_url)[0] # 从资源控制文件链接解析域名
  except:
  print("嗅探失败,未发现资源")
  os.system("pause")
  else:
  self.analysis(source_url)

 def analysis(self, source_url):
 try:
  self._url_seed = re.split("/\w+\.m3u8", source_url)[0] # 从资源控制文件链接解析域名
  with requests.get(source_url) as r: # 访问资源控制文件,获得资源信息
  src = re.split("\n*#.+\n", r.text) # 解析资源信息
  for sub_src in src: # 将资源地址储存到任务字典
   if sub_src:
   self._target_url[sub_src] = self._url_seed + "/" + sub_src
 except Exception as e:
  print("资源无法成功解析", e)
  os.system("pause")
 else:
  self._target_num = len(self._target_url)
  print("sniffing success!!!,found", self._target_num, "url.")
  self._mode = input(
  "1:-> 单进程(Low B)\n2:-> 多进程+多线程(网速开始biubiu飞起!)\n3:-> 多进程+协程(最先进的并发!!!)\n")
  if self._mode == "1":
  for path, url in self._target_url.items():
   self._download(path, url)
  elif self._mode == "2" or self._mode == "3":
  self._multiprocessing()

 def _multiprocessing(self, processing_num=4): # 多进程,多线程
 target_list = {} # 进程任务字典,储存每个进程分配的任务
 pool = multiprocessing.Pool(processes=processing_num) # 开启进程池
 i = 0 # 任务分配标识
 for path, url in self._target_url.items(): # 分配进程任务
  target_list[path] = url
  i += 1
  if i % 10 == 0 or i == len(self._target_url): # 每个进程分配十个任务
  if self._mode == "2":
   pool.apply_async(self._sub_multithreading, kwds=target_list) # 使用多线程驱动方法
  else:
   pool.apply_async(self._sub_coroutine, kwds=target_list) # 使用协程驱动方法
  target_list = {}
 pool.close() # join函数等待所有子进程结束
 pool.join() # 调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool
 while True:
  if self._judge_over():
  self._combine()
  break

 def _sub_multithreading(self, **kwargs):
 for path, url in kwargs.items(): # 根据进程任务开启线程
  t = threading.Thread(target=self._download, args=(path, url,))
  t.start()

 @retry()
 def _download(self, path, url): # 同步下载方法
 with requests.get(url, headers=self._headers) as r:
  if r.status_code == 200:
  with open(self._path + path, "wb")as file:
   file.write(r.content)
  self._ml.append(0) # 每成功一个就往进程通信列表增加一个值
  percent = '%.2f' % (len(self._ml) / self._target_num * 100)
  print(len(self._ml), ": ", path, "->OK", "\tcomplete:", percent, "%") # 显示下载进度
  else:
  print(path, r.status_code, r.reason)

 def _sub_coroutine(self, **kwargs):
 tasks = []
 for path, url in kwargs.items(): # 根据进程任务创建协程任务列表
  tasks.append(asyncio.ensure_future(self._async_download(path, url)))
 loop = asyncio.get_event_loop() # 创建异步事件循环
 loop.run_until_complete(asyncio.wait(tasks)) # 注册任务列表

 async def _async_download(self, path, url): # 异步下载方法
 async with aiohttp.ClientSession() as session:
  async with session.get(url, headers=self._headers) as resp:
  try:
   assert resp.status == 200, "E" # 断言状态码为200,否则抛异常,触发重试装饰器
   with open(self._path + path, "wb")as file:
   file.write(await resp.read())
  except Exception as e:
   print(e)
  else:
   self._ml.append(0) # 每成功一个就往进程通信列表增加一个值
   percent = '%.2f' % (len(self._ml) / self._target_num * 100)
   print(len(self._ml), ": ", path, "->OK", "\tcomplete:", percent, "%") # 显示下载进度

 def _combine(self): # 组合资源方法
 try:
  print("开始组合资源...")
  identification = str(floor(time.time()))
  exec_str = r'copy /b "' + self._path + r'*.ts" "' + self._path + 'video' + identification + '.mp4"'
  os.system(exec_str) # 使用cmd命令将资源整合
  exec_str = r'del "' + self._path + r'*.ts"'
  os.system(exec_str) # 删除原来的文件
 except:
  print("资源组合失败")
 else:
  print("资源组合成功!")

 def _judge_over(self): # 判断是否全部下载完成
 if len(self._ml) == len(self._target_url):
  return True
 return False


@time_statistics
def app():
 multiprocessing.freeze_support()
 url = input("输入嗅探网址:\n")
 m3u8 = M3U8Download()
 m3u8.sniffing(url)
 # m3u8.analysis(url)


if __name__ == "__main__":
 app()

这里是两个装饰器的实现:

import time


def time_statistics(fun):
 def function_timer(*args, **kwargs):
 t0 = time.time()
 result = fun(*args, **kwargs)
 t1 = time.time()
 print("Total time running %s: %s seconds" % (fun.__name__, str(t1 - t0)))
 return result

 return function_timer


def retry(retries=3):
 def _retry(fun):
 def wrapper(*args, **kwargs):
  for _ in range(retries):
  try:
   return fun(*args, **kwargs)
  except Exception as e:
   print("@", fun.__name__, "->", e)

 return wrapper

 return _retry

打包成exe文件

使用PyInstaller -F download.py将程序打包成单个可执行文件.
这里需要注意一下,因为程序含有多进程,需要在执行前加一句multiprocessing.freeze_support(),不然程序会反复执行多进程前的功能.

关于协程

协程在Python3.5进化到了async await版本,用 async 标记异步方法,在异步方法里对耗时操作使用await标记.这里使用了一个进程驱动协程的方法,在进程池创建多个协程任务,使用asyncio.get_event_loop()创建协程事件循环,使用run_until_complete()注册协程任务,asyncio.wait()方法接收一个任务列表进行协程注册.

关于装饰器

装饰器源于闭包原理,这里使用了两种装饰器.

  • @time_statistics:统计耗时,装饰器自己无参型
  • @retry():设置重试次数,装饰器自己有参型
  • 按我理解是有参型是将无参型装饰器包含在内部,而调用是加()的,关于():
  • 不带括号时,调用的是这个函数本身
  • 带括号(此时必须传入需要的参数),调用的是函数的return结果

关于CMD控制台

程序会使用CMD命令来将下载的ts文件合并.
因为CMD默认使用GB2312编码,调用os.system()需要先切换成通用的UTF-8输出,否则系统信息会乱码.
而且使用cmd命令时参数最好加双引号,以避免特殊符号报错.

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python操作mongodb根据_id查询数据的实现方法
May 20 Python
Python读取一个目录下所有目录和文件的方法
Jul 15 Python
python文件操作之批量修改文件后缀名的方法
Aug 10 Python
Python日期时间Time模块实例详解
Apr 15 Python
Python实现打砖块小游戏代码实例
May 18 Python
简单了解Python生成器是什么
Jul 02 Python
Python操作SQLite数据库过程解析
Sep 02 Python
用OpenCV将视频分解成单帧图片,图片合成视频示例
Dec 10 Python
详解python tkinter模块安装过程
Jan 06 Python
matplotlib之pyplot模块坐标轴标签设置使用(xlabel()、ylabel())
Feb 22 Python
python实现双向链表原理
May 25 Python
Python使用pandas导入csv文件内容的示例代码
Dec 24 Python
python游戏开发之视频转彩色字符动画
Apr 26 #Python
python接口自动化测试之接口数据依赖的实现方法
Apr 26 #Python
python使用参数对嵌套字典进行取值的方法
Apr 26 #Python
python将视频转换为全字符视频
Apr 26 #Python
使用Python创建简单的HTTP服务器的方法步骤
Apr 26 #Python
Python3.5内置模块之random模块用法实例分析
Apr 26 #Python
python3.5安装python3-tk详解
Apr 26 #Python
You might like
php5.3 goto函数介绍和示例
2014/03/21 PHP
PHP实现搜索相似图片
2015/09/22 PHP
thinkPHP微信分享接口JSSDK用法实例
2017/07/07 PHP
PHP应用跨时区功能的实现方法
2019/03/21 PHP
javascript下有关dom以及xml节点访问兼容问题
2007/11/26 Javascript
Javascript的一种模块模式
2008/03/22 Javascript
基于jquery实现一张图片点击鼠标放大再点缩小
2013/09/29 Javascript
jquery实现滑动图片自己测试的例子
2013/11/05 Javascript
JS解析XML实例分析
2015/01/30 Javascript
jQuery实现鼠标滑过链接控制图片的滑动展开与隐藏效果
2015/10/28 Javascript
简介BootStrap model弹出框的使用
2016/04/27 Javascript
jquery自定义插件——window的实现【示例代码】
2016/05/06 Javascript
JavaScript对Json的增删改属性详解
2016/06/02 Javascript
纯JS焦点图特效实例(可一个页面多用)
2016/12/07 Javascript
jQuery中Datatables增加跳转到指定页功能
2017/02/08 Javascript
基于JavaScript实现百度搜索框效果
2020/06/28 Javascript
基于jQuery实现的单行公告活动轮播效果
2017/08/23 jQuery
jQuery 1.9版本以上的浏览器判断方法代码分享
2017/08/28 jQuery
AngularJS实现的获取焦点及失去焦点时的表单验证功能示例
2017/10/25 Javascript
关于jQuery里prev()的简单操作代码
2017/10/27 jQuery
详解react-refetch的使用小例子
2019/02/15 Javascript
使用Three.js实现太阳系八大行星的自转公转示例代码
2019/04/09 Javascript
JS自定义右键菜单实现代码解析
2020/07/16 Javascript
Python过滤函数filter()使用自定义函数过滤序列实例
2014/08/26 Python
Python遍历目录并批量更换文件名和目录名的方法
2016/09/19 Python
python实现多线程抓取知乎用户
2016/12/12 Python
Python特殊属性property原理及使用方法解析
2020/10/09 Python
boostrap modal 闪现问题的解决方法
2020/09/01 HTML / CSS
FragranceNet中文网:北美健康美容线上零售商
2020/08/26 全球购物
运动会入场解说词300字
2014/01/25 职场文书
学校消防演习方案
2014/02/19 职场文书
感恩父母的演讲稿
2014/05/06 职场文书
学生旷课检讨书500字
2014/10/28 职场文书
工会积极分子个人总结
2015/03/03 职场文书
浅谈如何写好演讲稿?
2019/06/12 职场文书
励志语录:你若不勇敢,谁替你坚强
2019/11/08 职场文书