测试小站: 处理网 回收帮 培训网 富贵论坛 老富贵论坛

Python采集X音用户作品+调用Aria2下载+fire生成命令行+Vue界面

  X音采集开源仓库

  gitee/erma0/douyin

  介绍

  Python取数据 + Vue写界面 + Aria2下载

  根据X音各种链接或各种id,通过网页接口采集视频作品,并下载作品到本地。

  支持用户主页链接或sec_uid/话题挑战和音乐原声链接或ID。

  支持下载喜欢列表(需喜欢列表可见)。

  ① 2000多本Python电子书(主流和经典的书籍应该都有了)

  ② Python标准库资料(最全中文版)

  ③ 项目源码(四五十个有趣且经典的练手项目及源码)

  ④ Python基础入门、爬虫、web开发、大数据分析方面的视频(适合小白学习)

  ⑤ Python学习路线图(告别不入流的学习)

  私信小编01即可获取大量Python学习资源使用0x00 安装依赖

  在程序目录打开命令行,输入

  复制代码 隐藏代码

  pip install -r requirements.txt0x01 使用UI界面

  双击打开启动.bat,或者在程序目录打开命令行,输入

  复制代码 隐藏代码

  python ui.py0x02 直接修改douyin.py中相关参数使用

  完全不懂Python的朋友用命令行或操作界面。

  0x03 从命令行使用exec.py直接运行可查看命令列表,或使用-h参数查看帮助 复制代码 隐藏代码

  python exec.py python exec.py -h python exec.py download -h python exec.py download_batch -h使用函数名调用程序 复制代码 隐藏代码

  --type 指定下载类型,默认值:--type=user --limit 指定采集数量,默认值:--limit=0(不限制)例如采集某用户全部作品: 复制代码 隐藏代码

  python exec.py download v.douyin/xxxx/ python exec.py download 用户的secuid例如采集某用户喜欢的前10个作品: 复制代码 隐藏代码

  python exec.py download MS4wLjABAAAAl7TJWjJJrnu11IlllB6Mi5V9VbAsQo1N987guPjctc8 --type=like --limit=10 python exec.py download 用户的secuid例如采集某音乐原声前10个作品: 复制代码 隐藏代码

  python exec.py download v.douyin/xxxx/ --type=music --limit=10 python exec.py download 音乐ID --type=music --limit=10TODO[x] 采集用户作品[x] 调用Aria2下载[x] 话题/原声作品采集[x] 喜欢作品采集[x] 导入文件批量采集[x] 命令行调用[x] 用webview写界面[x] 打包exe 不打包了,直接装个Python环境更简单知识点X音相关网页接口恢复了,一次请求即可取回数据UID几乎没用了,拼不成主页链接了,所有接口都是sec_uidsignature可固定了,不用再扣JS了作品中直接包含无水印视频地址了,不需要移动端UA也可跳转话题/音乐作品数目2021.04.02 喜欢列表也有数据了Aria2相关aria2p库使用体验还不错大部分Aria2下载都是通过rpc接口实现的,这个也一样需要自己下载Aria2c.exe来开启服务,所以要用代码实现自动启动服务若文件已存在则跳过下载的方法:--auto-file-renaming=false 可行,但控制台使用会报错,虽然报错不影响-c 可行,且控制台不报错添加下载任务时通过指定options={'out': filename}指定文件名,即-dAria2会根据指定路径及指定文件名自动创建下载目录Aria2指定路径及文件名中不能传入非法字符串(*|等),所以写了Download.title2path静态方法监听事件要手动停止,不停止会阻塞进程,导致程序无法关闭未发现实时获取任务进度及下载速度的函数,自己写了循环监听回调方法Python相关通过os.popen或subprocess.Popen实现子进程打开程序,无界面,不阻塞继承父类后重写init时,通过super().init()调用父类构造方法继承父类后重写方法时,不能重写私有方法,不能读取私有成员参数指定类型提示挺好用,方便调用参数的函数时自动补全可通过if 'PROGRAMFILES(X86)' in os.environ简单判断系统是否为64位Pylance的自动导入依赖功能很好用,就是感觉时灵时不灵,重新开关后又可以用vscode默认启动路径是当前项目路径,在launch.json中加一句"cwd": "${fileDirname}",即可,不过自动补全pylance就无法识别相对目录了用pipreqs一键生成当前项目依赖:cmd切换到项目路径,pipreqs ./ --encoding=utf-8 --force命令行模块fire相关最简单的方法就是直接一个fire.Fire(),暴露全部函数如果用类或对象暴露,类参数需要单独指定组合命令需要用不同的类,暴露的类中引入需用组合命令的类,但是在这个批量下载的场景下感觉比较繁琐,所以直接加了个参数,分两个函数来调用UI模块pywebview相关可以把一个类的实例暴露给页面js_api,通过pywebview.apic().then(()=> {})调用Python函数也可以把Flask等服务实例暴露给页面js_api(无需url参数),在内部实现index.htmlPython中通过window.evaluate_js('JS代码')调用JS方法在UI中,类的初始化无法传参,所以需要重新定义init在UI中,需要公开的类实例方法不能以下划线_开头创建UI时设置的窗口宽高,好像和网页中大小不一样,值需要比网页中大一些X音采集部分源码

  复制代码 隐藏代码

  # -*- encoding: utf-8 -*-

  '''

  @File : douyin.py

  @Time : 2021年03月12日 18:16:57 星期五

  @Author : erma0

  @Version : 1.0

  @Link : erma0

  @Desc : X音用户作品采集

  '''

  import json

  import os

  import time

  from urllib.parse import parse_qs, urlparse

  import requests

  from download import Download

  class Douyin(object):

  """

  X音用户类

  采集作品列表

  """

  def __init__(self, param: str, limit: int=0):

  """

  初始化用户信息

  参数自动判断:ID/URL

  """

  self.limit=limit

  self.http=requests.Session()

  self.url=''

  self.type='unknow'

  self.download_path='暂未定义目录'

  # ↑ 预定义属性,避免调用时未定义 ↑

  self.param=param.strip()

  self.sign='TG2uvBAbGAHzG19a.rniF0xtrq' # sign可以固定

  self.__get_type() # 判断当前任务类型:链接/ID

  self.aria2=Download() # 初始化Aria2下载服务,先不指定目录了,在设置文件名的时候再加入目录

  self.has_more=True

  self.finish=False

  # 字典格式方便入库用id做key/取值/修改对应数据,但是表格都接收数组

  selfsL=[] #列表格式

  # selfs={} #字典格式

  self.gids={} # gid和作品序号映射

  def __get_type(self):

  """

  判断当前任务类型

  链接/ID

  """

  if '://' in self.param: # 链接

  self.__url2redirect()

  else: # ID

  self.id=self.param

  def __url2redirect(self):

  """

  取302跳转地址

  短连接转长链接

  """

  headers={ # 以前作品需要解析去水印,要用到移动端UA,现在不用了

  'User-Agent':

  'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/89.0.4389.82'

  }

  try:

  r=self.http.head(self.param, headers=headers, allow_redirects=False)

  self.url=r.headers['Location']

  except:

  self.url=self.param

  def __url2id(self):

  try:

  self.id=urlparse(self.url).path.split('/')[3]

  except:

  self.id=''

  def __url2uid(self):

  try:

  query=urlparse(self.url).query

  self.id=parse_qs(query)['sec_uid'][0]

  except:

  self.id=''

  def get_sign(self):

  """

  网页sign算法,现在不需要了,直接固定

  """

  self.sign='TG2uvBAbGAHzG19a.rniF0xtrq'

  return self.sign

  def get_user_info(self):

  """

  取用户信息

  查询结果在 self.user_info

  """

  if self.url:

  self.__url2uid()

  url='iesdouyin/web/api/v2/user/info/?sec_uid=' + self.id

  try:

  res=self.http.get(url).json()

  info=res.get('user_info', dict())

  except:

  info=dict()

  self.user_info=info

  # 下载路径

  username='{}_{}_{}'.format(self.user_info.get('short_id', '0'),

  self.user_info.get('nickname', '无昵称'), self.type)

  self.download_path=Download.title2path(username) # 需提前处理非法字符串

  def get_challenge_info(self):

  """

  取话题挑战信息

  查询结果在 self.challenge_info

  """

  if self.url:

  self.__url2id()

  url='iesdouyin/web/api/v2/challenge/info/?ch_id=' + self.id

  try:

  res=self.http.get(url).json()

  info=res.get('ch_info', dict())

  except:

  info=dict()

  self.challenge_info=info

  # 话题挑战下载路径

  username='{}_{}_{}'.format(self.challenge_info.get('cid', '0'),

  self.challenge_info.get('cha_name', '无标题'), self.type)

  self.download_path=Download.title2path(username) # 需提前处理非法字符串

  def get_music_info(self):

  """

  取音乐原声信息

  查询结果在 self.music_info

  """

  if self.url:

  self.__url2id()

  url='iesdouyin/web/api/v2/music/info/?music_id=' + self.id

  try:

  res=self.http.get(url).json()

  info=res.get('music_info', dict())

  except:

  info=dict()

  self.music_info=info

  # 音乐原声下载路径

  username='{}_{}_{}'.format(self.music_info.get('mid', '0'), self.music_info.get('title', '无标题'),

  self.type)

  self.download_path=Download.title2path(username) # 需提前处理非法字符串

  def crawling_users_post(self):

  """

  采集用户作品

  """

  self.type='post'

  self.__crawling_user()

  def crawling_users_like(self):

  """

  采集用户喜欢

  """

  self.type='like'

  self.__crawling_user()

  def crawling_challenge(self):

  """

  采集话题挑战

  """

  self.type='challenge'

  self.get_challenge_info() # 取当前信息,用做下载目录

  # iesdouyin/web/api/v2/challenge/aweme/?ch_id=1570693184929793&count=9&cursor=9&aid=1128&screen_limit=3&download_click_limit=0&_signature=AXN-GQAAYUTpqVxkCT6GHQFzfg

  url='iesdouyin/web/api/v2/challenge/aweme/'

  cursor='0'

  while self.has_more:

  params={

  "ch_id": self.id,

  "count": "21", # 可调大 初始值:9

  "cursor": cursor,

  "aid": "1128",

  "screen_limit": "3",

  "download_click_limit": "0",

  "_signature": self.sign

  }

  try:

  res=self.http.get(url, params=params).json()

  cursor=res['cursor']

  self.has_more=res['has_more']

  self.__append_videos(res)

  except:

  print('话题挑战采集出错')

  print('话题挑战采集完成')

  def crawling_music(self):

  """

  采集音乐原声

  """

  self.type='music'

  self.get_music_info() # 取当前信息,用做下载目录

  # iesdouyin/web/api/v2/music/list/aweme/?music_id=6928362875564067592&count=9&cursor=18&aid=1128&screen_limit=3&download_click_limit=0&_signature=5ULmIQAAhRYNmMRcpDm2COVC5j

  url='iesdouyin/web/api/v2/music/list/aweme/'

  cursor='0'

  while self.has_more:

  params={

  "music_id": self.id,

  "count": "21", # 可调大 初始值:9

  "cursor": cursor,

  "aid": "1128",

  "screen_limit": "3",

  "download_click_limit": "0",

  "_signature": self.sign

  }

  try:

  res=self.http.get(url, params=params).json()

  cursor=res['cursor']

  self.has_more=res['has_more']

  self.__append_videos(res)

  except:

  print('音乐原声采集出错')

  print('音乐原声采集完成')

  def __crawling_user(self):

  """

  采集用户作品/喜欢

  """

  self.get_user_info() # 取当前用户信息,昵称用做下载目录

  max_cursor=0

  # iesdouyin/web/api/v2/aweme/like/?sec_uid=MS4wLjABAAAAaJO9L9M0scJ_njvXncvoFQj3ilCKW1qQkNGyDc2_5CQ&count=21&max_cursor=0&aid=1128&_signature=2QoRnQAAuXcx0DPg2DVICdkKEY&dytk=

  # iesdouyin/web/api/v2/aweme/post/?sec_uid=MS4wLjABAAAAaJO9L9M0scJ_njvXncvoFQj3ilCKW1qQkNGyDc2_5CQ&count=21&max_cursor=0&aid=1128&_signature=DrXeeAAAbwPmb.wFM3e63w613m&dytk=

  url='iesdouyin/web/api/v2/aweme/{}/'.format(self.type)

  while self.has_more:

  params={

  "sec_uid": self.id,

  "count": "21",

  "max_cursor": max_cursor,

  "aid": "1128",

  "_signature": self.sign,

  "dytk": ""

  }

  try:

  res=self.http.get(url, params=params).json()

  max_cursor=res['max_cursor']

  self.has_more=res['has_more']

  self.__append_videos(res)

  except:

  print('作品采集出错')

  print('作品采集完成')

  def __append_videos(self, res):

  """

  数据入库

  """

  if res.get('aweme_list'):

  for item in res['aweme_list']:

  info=item['statistics']

  info.pop('forward_count')

  info.pop('play_count')

  info['desc']=Download.title2path(item['desc']) # 需提前处理非法字符串

  info['uri']=item['video']['play_addr']['uri']

  info['play_addr']=item['video']['play_addr']['url_list'][0]

  info['dynamic_cover']=item['video']['dynamic_cover']['url_list'][0]

  info['status']=0 # 下载进度状态;等待下载:0,下载中:0.xx;下载完成:1

  # 列表格式

  selfsL.append(info)

  # 字典格式

  # selfs[info['aweme_id']]=info

  # 此处可以直接添加下载任务,不过考虑到下载占用网速,影响采集过程,所以采集完再下载

  if self.limit:

  more=len(selfs) - self.limit

  if more >=0:

  # 如果给出了限制采集数目,超出的删除后直接返回

  self.has_more=False

  # 列表格式

  selfsL=selfsL[:self.limit]

  # 字典格式

  # for i in range(more):

  # selfs.popitem()

  # return

  else: # 还有作品的情况下没返回数据则进入这里

  print('未采集完成,但返回作品列表为空')

  def download_all(self):

  """

  作品抓取完成后,统一添加下载任务

  可选择在外部注册回调函数,监听下载任务状态

  """

  for id, video in enumerate(selfsL):

  # for id, video in selfs.items():

  gid=self.aria2.download(url=video['play_addr'],

  filename='{}/{}_{}.mp4'.format(self.download_path, video['aweme_id'],

  video['desc'])

  # ,options={'gid': id} # 指定gid

  )

  self.gids[gid]=id # 因为传入gid必须16位,所以就不指定gid了,另存一个字典映射

  print('下载任务投递完成')

posted @ 2021-12-11 12:54  linjingyg  阅读(682)  评论(0)    收藏  举报