yuxijian2020 发表于 2021-1-29 09:06:11

女朋友说想看电影,所以我用python写了一个电影爬取工具

搜索是百度搜索,找到 爱奇艺、腾讯、优酷、PPTV、芒果 这些网站的链接
解析主要是通过在线解析网址,然后对解析后的网页分析,获得m3u8视频的url,解密下载
其中如果网页请求失败,我就不断的重新发起请求,所以下载最后阶段会有点慢
主要用的是'https://jx.618g.com/?url=',这个解析地址的电影视频都可以下载
但有时候会跳转到'https://jx.147g.cc/?url=',这个地址有robots协议就只能在线观看

Python版本 -> 3.8.7

废话不多说,直接上代码
import os
import requests
import base64
from lxml import etree
from Crypto.Cipher import AES
import asyncio
import aiohttp
import shutil
import time


class FilmDownloader:
    def __init__(self):
      '''初始化'''
      
      # 爱奇艺、腾讯、优酷、PPTV、芒果

      '''查找电影的url'''
      self.searchUrl = 'https://www.baidu.com/s?wd='
      # 解析url
      self.parseUrl = 'https://jx.618g.com/?url='
      self.parseUrl_147 = 'https://jx.147g.cc/?url='
      # 下载地址头
      self.downloadHead = 'https://video.dious.cc'
      # User-Agent
      self.userAgent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36'
      # 下载临时文件目录
      self.downDir = os.getcwd() + '\\temp\\'
      self.webList = ['爱奇艺', '腾讯视频', '优酷', 'PP视频', '芒果TV']
      # 电影名称
      self.name = ''
      # 在线观看url
      self.onlineUrl = ''
      # 完整的电影播放网址
      self.finalUrl = ''
      # 搜索结果列表
      self.searchResultList = []
      # 电影所有的url的地址
      self.allListUrl = ''
      # 解密视频所需要的秘钥的URL
      self.keyUrl = ''
      # 解密视频的秘钥
      self.key = ''
      # 电影所有url列表
      self.allList = []
      # AES解密 - 初始化加密器
      self.aes = AES.new(b'0000000000000000', AES.MODE_CBC)
      # 临时文件总数
      self.total = 0
      # 已经下载的文件数量
      self.cur = 0

      self.indexUrl = ''

      # 下载目录不存在,则创建目录
      if not os.path.exists(self.downDir) or not os.path.isdir(self.downDir):
            os.mkdir(self.downDir)
      # 清空下载目录
      if len(os.listdir(self.downDir)) != 0:
            shutil.rmtree(self.downDir)
            time.sleep(0.2)
            os.mkdir(self.downDir)

    def SearchFilm(self, name: str) -> bool:
      '''电影搜索'''
      if len(name) == 0:
            print('电影名不能为空...')
            return False

      self.name = name
      head = {
            'Host': 'www.baidu.com',
            'User-Agent': self.userAgent
      }

      print('正在搜索 {} 资源...'.format(name))
      time.sleep(0.2)
      res = requests.get(self.searchUrl + name, headers=head)
      res.encoding = 'utf-8'
      html = etree.HTML(res.text)

      condition = '//a[@target="_blank"][@data-visited="off"][@class="dis-line-block c-gap-right dis-no-line c-blocka"]'
      self.searchResultList = html.xpath(condition)
      # 爱奇艺、腾讯、优酷、PPTV、芒果
      for item in self.searchResultList:
            flag = item.text == '爱奇艺' or item.text == '腾讯视频' or item.text == '优酷'or item.text == 'PP视频' or item.text == '芒果TV'
            if flag:
                self.finalUrl = item.attrib['href']
                break

      if len(self.finalUrl) == 0:
            self.searchResultList = html.xpath('//div//h3//a')
            tempList = []
            isExist = False
            for Item in self.searchResultList:
                child = Item.getchildren()
                for each in child:
                  tempList.append(each.tail)
                  tempList.append(each.text)
                for each in self.webList:
                  if each in str(tempList):
                        isExist = True
                        break
                if isExist:
                  self.finalUrl = Item.attrib['href']
                  break
      
      if len(self.finalUrl) == 0:
            return False

      return True

    def ParseFilmAndGetURL(self) -> bool:
      '''解析视频,并获得下载地址'''
      if len(self.finalUrl) == 0:
            return False

      head = {
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
            'User-Agent': self.userAgent
      }
      time.sleep(0.2)
      res = requests.get(self.finalUrl, headers=head)
      res.encoding = 'utf-8'
      self.finalUrl = self.parseUrl + res.url
      self.onlineUrl = self.finalUrl

      return True

    def getIndexUrl(self) -> bool:
      '''获取视频所有片段的url'''
      if len(self.finalUrl) == 0:
            return False

      head = {
            'User-Agent': self.userAgent
      }

      time.sleep(0.2)
      response = requests.get(self.finalUrl, headers=head)
      response.encoding = 'utf-8'
      html = etree.HTML(response.text)
      nodes = html.xpath('//iframe[@id="player"]')
      if nodes is None or len(nodes) == 0:
            return False

      for item in nodes:
            self.indexUrl = item.attrib['src']
            if self.indexUrl.find('m3u8') != -1:
                index = self.indexUrl.find('url=') + 4
                self.indexUrl = self.indexUrl
            elif self.indexUrl.find('jx.147g.cc') != -1:
                print('由于robots协议,本视频无法下载...')
                return True
            if len(self.indexUrl) != 0:
                # print('url: ' + indexUrl)
                break

      time.sleep(0.2)
      response = requests.get(self.indexUrl, headers=head)
      response.encoding = 'urf-8'
      
      result = response.text.split('\n')
      for line in result:
            if line.find('#') == -1:
                self.allListUrl = self.downloadHead + line
                break

      try:
            response = requests.get(self.allListUrl, headers=head)
      except Exception:
            print('{} 没有下载资源...'.format(self.name))
            return False
      
      response.encoding = 'utf-8'
      tempList = response.text.split('\n')
      n = 0
      for line in tempList:
            if line.find('KEY') != -1 and line.find('URI') != -1:
                self.keyUrl = line
                keyRes = requests.get(self.keyUrl, headers=head)
                keyRes.encoding = 'utf-8'
                self.key = keyRes.text
                self.aes = AES.new(self.key.encode('utf-8'), AES.MODE_CBC)
            elif line.find('http') != -1:
                self.allList.append({
                  'index': n,
                  'url': line
                })
                n += 1
      
      self.total = len(self.allList)
      return True

    async def crawler(self, index, url):
      head = {
            'Connection': 'keep-alive',
            'Host': 'ts1.lslkkyj.com',
            'User-Agent': self.userAgent
      }

      content = b''
      
      try:
            async with aiohttp.ClientSession() as session:
                await asyncio.sleep(5)
                async with session.get(url, headers=head) as response:
                  text = await response.read()
                  await asyncio.sleep(1)
                  
                  content = self.aes.decrypt(text)                                  # 解密

                  filename = self.downDir + '{:0>5d}.mp4'.format(index)
                  file = open(filename, 'wb')
                  file.write(content)
                  file.close()
                  self.cur += 1
                  percent = float(self.cur) / float(self.total) * 100
                  print('\r{} 下载中... {:.2f} %   {:d} / {:d}'.format(self.name, percent, self.cur, self.total), end='')
      except Exception:
            # print('{:d} 下载错误;url:{}'.format(index, url))
            await asyncio.sleep(5)
            await self.crawler(index, url)

    def DownloadFilm(self) -> bool:
      '''协程'''
      if len(self.allList) == 0:
            return False

      print('搜索完成!正在下载...')
      loop = asyncio.get_event_loop()
      tasks = , item['url']) for item in self.allList]
      loop.run_until_complete(asyncio.gather(*tasks))
      loop.close()

      print('\n下载完成,正在合并文件...')
      command = 'copy /b ' + self.downDir + '* ' + os.getcwd() + '\\' + self.name + '.mp4'
      os.system(command)
      shutil.rmtree(self.downDir)
      time.sleep(0.2)
      os.mkdir(self.downDir)
      print('视频下载完成...')

      return True

    def FindFilmAndDownload(self, name: str) -> bool:
      '''查找视频并下载或在线观看'''
      if not self.SearchFilm(name):
            print('没有搜索到 {} 资源...'.format(name))
            return False
      elif not self.ParseFilmAndGetURL():
            print('{} 资源解析失败...'.format(name))
            return False
      elif not self.getIndexUrl():
            print('获取 {} 下载资源失败...'.format(name))
            return False
      chioce = input('是否在线观看?在线观看则不下载视频!y/n\n无法下载的视频不选择在线观看则退出程序...\n')
      if chioce == 'y':
            os.system('start ' + self.onlineUrl)
            return True
      if len(self.allList) == 0:
            return False
      if not self.DownloadFilm():
            print('{} 下载失败...'.format(name))

      
if __name__ == "__main__":
   
    print('-' * 50)
    # film = '唐伯虎点秋香'
    film = input('请输入电影名...\n')

    task = FilmDownloader()
    task.FindFilmAndDownload(film)

yuxijian2020 发表于 2021-2-6 21:34:57

思来想去还是把问题集中一下,整理一下问题吧...
目前看来有不少童鞋都出现了问题,我就整理一下
后面的童鞋可以现在这里找找能不能解决,这里没有再开贴问吧

1.这个脚本是通过视频解析网站解析主流视频网站上的视频,然后分析解析之后的视频再进行下载,所以是不需要会员的
但是也正因为是通过解析网站,所以视频画质什么的不能保证(毕竟主流视频网站也没那么容易爬取,我这里都没有用到什么IP代理那种麻烦的东西...)

2.不需要有女朋友也可以用{:10_256:}

3.报错:   ModuleNotFoundError: No module named 'xxxxxx'
             这个错误的原因是没有安装xxxxxx模块
             在控制台利用命令pip install xxxxx来安装模块
             (代码开头部分所有import的模块都要安装!)

4.运行没反应
          确保你的电脑上有Python环境,运行脚本最好是弄个批处理或者在vscode、pycharm上运行(双击.py文件有时候真的不大好使)

5.报错:   requests.exceptions.SSLError
          报错出现上面这句话就说明是SSL认证失败,这时候可以关闭SSL认证试一试
          关闭SSL认证的方法是把所有的requests.get函数调用的地方 把verify参数设置为False
          ressponse = requests.get(url, headers=head, verify=False)

6.已经安装了crypto模块却依然报错:    ModuleNotFoundError: No module named 'Crypto'
          crypto模块本身有些小问题
          找到你crypto模块的路径( {你的Python目录}/Lib/site-packages/下crypto文件夹 )
          把这个文件夹第一个字母改成大写

7.报错:   ConnectionResetError: 远程主机强迫关闭了一个现有的连接。
          可能是因为爬虫速度过快所以被网站暂时禁止连接了
          可以把代码里requests.get前面加上延时(time.sleep({这里参数单位为 秒 }))
          时间设置长一点....0.0

8.代码我自己在Python3.8.7和Python3.9.1上都测试通过的{:10_256:}

祝大家使用愉快咯

nahongyan1997 发表于 2021-1-29 19:06:48

有这个是不是就不要会员了

yuxijian2020 发表于 2021-1-29 19:14:48

nahongyan1997 发表于 2021-1-29 19:06
有这个是不是就不要会员了

是的{:9_235:}

yuxijian2020 发表于 2021-1-29 19:21:09

早上9点发的帖,晚上下班回家审核通过,大家支持一下吧{:9_234:}

Project1 发表于 2021-1-30 10:22:51

测试,成功{:10_257:}虽然每个都有会员,但是运行成功的感觉真好{:10_264:}

basketmn 发表于 2021-1-30 10:30:34

膜拜大佬!!!

ngchiuyuen 发表于 2021-1-30 21:19:47

这个清晰度如何

26913 发表于 2021-1-30 23:00:49

我运行了没反应是怎么回事?

Daniel_Zhang 发表于 2021-1-30 23:18:26

然后女朋友不会用,转身包了一个会员{:10_257:}

yuxijian2020 发表于 2021-1-30 23:53:09

Daniel_Zhang 发表于 2021-1-30 23:18
然后女朋友不会用,转身包了一个会员

其实是嫌弃没有弹幕,转身开了个会员{:9_220:}

Daniel_Zhang 发表于 2021-1-30 23:58:29

yuxijian2020 发表于 2021-1-30 23:53
其实是嫌弃没有弹幕,转身开了个会员

有被笑到{:10_250:}

Mike_python小 发表于 2021-1-31 09:57:41

划重点了啊   这些代码其实都不重要   重要的是你得有个女朋友{:10_266:}{:10_266:}{:10_266:}

zhiweng07 发表于 2021-1-31 11:31:32

解析网站能生存多长时间不可预测

yuxijian2020 发表于 2021-1-31 12:50:15

zhiweng07 发表于 2021-1-31 11:31
解析网站能生存多长时间不可预测

其实我最开始是准备从我看了很多年的一个dao版视频网站上爬的,之前一个版本是在那个网站上搜索视频然后再解密下载。分析了半天结果那个网站是用的视频解析,然后我就直接百度搜,顺便测试了下,这个解析网站能支持解析不少主流视频网址....   而且本来爬虫生存时间就比较短,且行且珍惜吧{:10_282:}

yuxijian2020 发表于 2021-1-31 12:56:14

26913 发表于 2021-1-30 23:00
我运行了没反应是怎么回事?

首先,我的Python版本是3.8.7,我家里电脑Python版本3.9.1 都测试通过
然后你要看看我上面import的模块,你是不是全都pip下来了
最后有时候双击.py文件可能没法运行,你可以弄到你的vscode或者pycharm里面运行
或者你可以写一个批处理
比如你把上面的代码保存为 -> FileDownloader.py
然后新建批处理文件(电影搜索下载.bat)
批处理内容 -> start FileDownloader.py
然后运行批处理文件

fatalman 发表于 2021-1-31 14:27:32

点个赞

study_z 发表于 2021-1-31 14:30:03

免会员的吗{:5_109:}

yuxijian2020 发表于 2021-1-31 18:30:33

study_z 发表于 2021-1-31 14:30
免会员的吗

是的{:10_256:}

我是小白啊 发表于 2021-2-1 11:59:27

大神,膜拜

hsq1134388059 发表于 2021-2-1 12:45:28

准备学习Python 像卤煮学习
页: [1] 2 3 4
查看完整版本: 女朋友说想看电影,所以我用python写了一个电影爬取工具