提示:
1.本代码实例仅供学习使用,请遵守你所在地的法律法规。
2.IDE:PyCharm 2021.1.1 python:3.9
3.selenium,browsermobproxy库的安装方法请自行百度
4.感谢 极客挖掘机(https://www.geekdigging.com/) 分享的python学习内容
import os import re import time import random import requests from pyquery import PyQuery from urllib.parse import urlencode from tqdm import tqdm # 下载进度条 from browsermobproxy import Server # 代理,用于抓取 har 中的视频列表请求参数 from selenium import webdriver # 调用浏览器,用于访问 uper 页面以产生访问视频列表 api 地址的行为 from selenium.webdriver.chrome.options import Options # 中文、英文标点符号两者之间互换 def exchange_char(char_in, char_type='cn'): char_cn = ['、', '、', ':', '*', '?', '“', '”', '《', '》', '|', '‘', '’'] # 中文标点符号 char_en = ['/', '\', ':', ' ', '?', '"', '"', '<', '>', ' ', "'", "'"] # 要互换的英文标点符号,与上面的中文列表对应 char_out = str(char_in) for i in range(0, len(char_en)): if char_type == 'cn': # 英文2成中文 char_out = char_out.replace(char_en[i], char_cn[i]) else: # 中文2成英文 char_out = char_out.replace(char_cn[i], char_en[i]) return char_out # 新建文件夹 def create_dir(path): if os.path.exists(path) is False: os.makedirs(path) # 切换到路径 os.chdir(path) # 视频下载 def down_video(vedio_url, file_path): file_name = os.path.basename(file_path) try: ''' tqdm 参数: 使用手动设置更新 total 设置总大小 initial 当前操作文件的大小 desc 进度条前的描述 ncols 设置进度条显示长度 nit_scale 如果设置,迭代的次数会自动按照十、百、千来添加前缀,默认为false ''' # 下载视频 rsp = requests.get(vedio_url, stream=True, headers=headers) length = float(rsp.headers['content-length']) with open(file_path, 'wb') as fb: global down_count down_count += 1 msg = '~~~下载中' + str(down_count) + ':' + file_name pbar = tqdm(initial=os.path.getsize(file_path), desc=msg, total=length, unit_scale=True, ncols=150, mininterval=0.5) for chuck in rsp.iter_content(chunk_size=512): if chuck: fb.write(chuck) pbar.update(512) # 手动进度条 pbar.close() except requests.exceptions.ConnectionError: print(' !!!连接错误:' + file_name + ' ' + vedio_url) except requests.exceptions.Timeout: print(' !!!连接超时:' + file_name + ' ' + vedio_url) # 下载单个分享的视频 def down_one(share_url): # 真实url url_real = requests.head(share_url).headers['Location'] # 302 后的url # 视频id item_ids = re.compile(r'video/(d+)').findall(url_real)[0] # 获取视频 id item_info_url = f"https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids={item_ids}" # 拼接请求 # 获取视频源路径 res_json = session.get(item_info_url, headers=headers).json() vedio_url = res_json['item_list'][0]['video']['play_addr']['url_list'][0] video_name = res_json['item_list'][0]['share_info']['share_title'].split('#')[0].split('@')[0].replace(' ', '') video_name = exchange_char(video_name) # 英文标点符号无法作为文件名 file_path = video_name + fileFormat # 下载视频 down_video(vedio_url, file_path) # 获取视频列表查询参数 def get_list_querypara(share_url): # 真实url url_real = requests.head(share_url).headers['Location'] # 302 后的url # 开启代理 bmp_server = Server(".\browsermob-proxy-2.1.4\bin\browsermob-proxy.bat") bmp_server.start() bmp_proxy = bmp_server.create_proxy() # 配置 Proxy 启动 WebDriver chrome_options = Options() # 开启无窗口模式 chrome_options.add_argument('--headless') # 禁用扩展插件,因为我也不是太懂,总之没了这句,浏览器会报警提示如下图。魔法,勿动。 chrome_options.add_argument('--ignore-certificate-errors') # bmp_proxy.proxy返回的是localhost:8081端口 chrome_options.add_argument('--proxy-server={0}'.format(bmp_proxy.proxy)) # 如果Selenium驱动放在了python.exe同级目录下,executable_path参数可以省略 # driver = webdriver.Chrome(executable_path="D:Apythonchromedriver.exe", options=chrome_options) driver = webdriver.Chrome(chrome_options=chrome_options) # 个人理解是new一个空的har准备接收爬取网站的交互信息 bmp_proxy.new_har("douyin", options={'captureHeaders': True, 'captureContent': True}) # 模拟浏览器 driver.get(url_real) # 请求的har json_har = bmp_proxy.har # 输出 har 到文件 # import json # result_json = json.dumps(json_har, indent=4) # with open("douyin.json", "w", errors="igone") as f: # f.write(result_json) dict_query = {} for entry in json_har['log']['entries']: entry_url = entry['request']['url'] # 根据URL找到数据接口 if url_api_list in entry_url: for query in entry['request']['queryString']: dict_query[query['name']] = query['value'] break else: continue # 请求结果,uper主ID html = driver.page_source doc = PyQuery(html) uper_id = doc('p.shortid').text() bmp_server.stop() driver.quit() # 为 uper 新建目录 dir_path = save_path + '\' + uper_id create_dir(dir_path) return {'dict_query': dict_query, 'dir_path': dir_path} # 下载视频列表 def down_list(dict_info): dict_query = dict_info['dict_query'] dir_path = dict_info['dir_path'] if dict_query: # 视频列表 url_json = url_api_list + '?' + urlencode(dict_query) # print(' ###API请求URL:' + url_json) json_video_list = session.get(url_json, headers=headers).json() dict_info['dict_query']['max_cursor'] = json_video_list['max_cursor'] # 更新参数 if json_video_list['has_more']: # 过滤空列表 if json_video_list['aweme_list']: # 遍历下载 for aweme in json_video_list['aweme_list']: # aweme_id = aweme['aweme_id'] vedio_url = aweme['video']['play_addr']['url_list'][0] video_name = aweme['desc'].split('#')[0].split('@')[0].replace(' ', '') video_name = exchange_char(video_name) # 英文标点符号无法作为文件名 file_path = dir_path + '\' + video_name + fileFormat if os.path.isfile(file_path): global down_count down_count += 1 msg = ' ***已存在' + str(down_count) + ':' + os.path.basename(file_path) print(msg) pass else: time.sleep(random.random() * 1) # 设置访问时间间隔,防止下载过于频繁 down_video(vedio_url, file_path) else: # 下载完毕,下一页 down_list(dict_info) else: # 空列表,下一页 down_list(dict_info) else: print(' ###全部下载完毕。') else: print(' queryString 错误,无法开始下载') if __name__ == '__main__': # 保存路径 save_path = 'D:\douyin_down' # 文件格式 fileFormat = '.mp4' # 下载统计 down_count = 0 # 视频列表api url_api_list = 'https://www.iesdouyin.com/web/api/v2/aweme/post/' # 创建一个请求头 headers = { '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' } session = requests.Session() # 单个分享视频下载 # down_url_video = "https://v.douyin.com/efvrSgd/" # 视频分享路径 # down_one(down_url_video) # Uper所有视频下载 down_url_uper = "https://v.douyin.com/eftqWpm/" # uper主页分享URL dict_uper = get_list_querypara(down_url_uper) down_list(dict_uper)