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爬取斗鱼app中照片方法实例
Dec 03 Python
Python xlwt设置excel单元格字体及格式
Apr 18 Python
pandas多级分组实现排序的方法
Apr 20 Python
Python将一个Excel拆分为多个Excel
Nov 07 Python
浅谈pycharm出现卡顿的解决方法
Dec 03 Python
运用Python的webbrowser实现定时打开特定网页
Feb 21 Python
使用selenium模拟登录解决滑块验证问题的实现
May 10 Python
Django 配置多站点多域名的实现步骤
May 17 Python
Python通过正则库爬取淘宝商品信息代码实例
Mar 02 Python
150行Python代码实现带界面的数独游戏
Apr 04 Python
在Ubuntu 20.04中安装Pycharm 2020.1的图文教程
Apr 30 Python
python实现一次性封装多条sql语句(begin end)
Jun 06 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
php 记录进行累加并显示总时长为秒的结果
2011/11/04 PHP
PHP 使用redis简单示例分享
2015/03/05 PHP
Yii中srbac权限扩展模块工作原理与用法分析
2016/07/14 PHP
微信支付扫码支付php版
2016/07/22 PHP
laravel多条件查询方法(and,or嵌套查询)
2019/10/09 PHP
IE与firefox下Dhtml的一些区别小结
2009/12/02 Javascript
理解Javascript_05_原型继承原理
2010/10/13 Javascript
利用jquery写的左右轮播图特效
2014/02/12 Javascript
Node.js的包详细介绍
2015/01/14 Javascript
Js为表单动态添加节点内容的方法
2015/02/10 Javascript
javascript实现捕捉键盘上按下的键
2015/05/05 Javascript
JavaScript中的原型prototype属性使用详解
2015/06/05 Javascript
javascript数组排序汇总
2015/07/07 Javascript
浅谈jQuery 中的事件冒泡和阻止默认行为
2016/05/28 Javascript
gulp安装以及打包合并的方法教程
2017/11/19 Javascript
微信小程序页面缩放式侧滑效果的实现代码
2018/11/15 Javascript
详解微信小程序实现仿微信聊天界面(各种细节处理)
2019/02/17 Javascript
vue动画效果实现方法示例
2019/03/18 Javascript
在Layui中实现开关按钮的效果实例
2019/09/29 Javascript
JavaScript Canvas编写炫彩的网页时钟
2019/10/16 Javascript
Javascript作用域和作用域链原理解析
2020/03/03 Javascript
vue 出现data-v-xxx的原因及解决
2020/08/04 Javascript
详解如何在vue+element-ui的项目中封装dialog组件
2020/12/11 Vue.js
[11:01]2014DOTA2西雅图邀请赛 冷冷带你探秘威斯汀
2014/07/08 DOTA
[01:13]这,就是刀塔
2014/07/16 DOTA
[01:29:42]Liquid vs VP Supermajor决赛 BO 第一场 6.10
2018/07/05 DOTA
Python素数检测的方法
2015/05/11 Python
python实现数据写入excel表格
2018/03/25 Python
python复合条件下的字典排序
2020/12/18 Python
HTML5移动开发图片压缩上传功能
2016/11/09 HTML / CSS
20世纪40年代连衣裙和复古服装:The Seamstress Of Bloomsbury
2018/07/24 全球购物
法雷奥SQA(electric)面试问题
2016/01/23 面试题
社区植树节活动总结
2015/02/06 职场文书
焦裕禄观后感
2015/06/03 职场文书
用Python进行栅格数据的分区统计和批量提取
2021/05/27 Python
Pandas-DataFrame知识点汇总
2022/03/16 Python