Playwright实战,爬取微软Rust入门教程视频
之前介绍过 pyppeteer, 这次打算用它下载微软出品的 Rust 入门教程视频:Beginner's Series to Rust[1] ,先尝试保存个截图,
竟然被告知所用浏览器已经不再支持,更新 pyppeteer 和浏览器后依然无效,从官网得知已经停止更新,推荐使用 playwright-python[2]。那今天就用 Playwright 来完成视频的下载。
Playwright 简介
Playwright 是专门为适应端到端测试的需要而创建的, 主要功能特点如下,
任何浏览器 • 任何平台 • 一个 API
跨浏览器。Playwright 支持所有现代渲染引擎,包括 Chromium、WebKit 和 Firefox。
跨平台。在 Windows、Linux 和 macOS 上进行本地或 CI、无头或有头测试。
跨语言。在 TypeScript、JavaScript、Python、.NET、Java 中使用 Playwright API 。
测试移动网络。适用于 Android 和 Mobile Safari 的 Google Chrome 原生移动仿真。相同的渲染引擎适用于您的桌面和云端。
弹性 • 没有不稳定的测试
自动等待。Playwright 在执行操作之前等待元素可操作。它还有一组丰富的自省事件。两者的结合消除了人为设置超时的需要(造成不稳定测试的主要原因)。
网络优先的断言。剧作家断言是专门为动态网络创建的。检查会自动重试,直到满足必要的条件。
追踪。配置测试重试策略,捕获执行跟踪、视频、屏幕截图以消除碎片。
没有取舍 • 没有限制
浏览器在不同进程中运行属于不同来源的 Web 内容。Playwright 与现代浏览器架构保持一致,并在进程外运行测试。这使 Playwright 摆脱了典型的进程内测试运行程序限制。
多一切。跨多个选项卡、多个来源和多个用户的测试场景。为不同的用户创建具有不同上下文的场景并在您的服务器上运行它们,所有这些都在一次测试中完成。
可信事件。悬停元素,与动态控件交互,产生可信事件。Playwright 使用与真实用户无法区分的真实浏览器输入管道。
测试帧,穿透 Shadow DOM。Playwright 选择器穿透 shadow DOM 并允许无缝输入帧。
完全隔离 • 快速执行
浏览器上下文。Playwright 为每个测试创建一个浏览器上下文。浏览器上下文相当于一个全新的浏览器配置文件。这提供了零开销的完整测试隔离。创建一个新的浏览器上下文只需要几毫秒。
只登录一次。保存上下文的身份验证状态并在所有测试中重用它。这绕过了每个测试中的重复登录操作,但提供了独立测试的完全隔离。
强大的工具
代码生成器。通过记录您的操作来生成测试。将它们保存为任何语言。
Playwright 检查员。检查页面,生成选择器,逐步执行测试,查看点击点,浏览执行日志。
跟踪查看器。捕获所有信息以调查测试失败。Playwright 跟踪包含测试执行截屏视频、实时 DOM 快照、动作浏览器、测试源等等。
安装 Playwright
pip install -U playwright
安装浏览器等
playwright install
看看都装了些啥
ls $HOME/.cache/ms-playwright/
# 装了Chromium,FFMPEG,FireFox和Webkit
chromium-1028 ffmpeg-1007 firefox-1357 webkit-1724
如果是在 Linux Server 上运行下面的代码,还需要安装虚拟桌面所需的依赖,
playwright install-deps
或者手工安装如下包(Chromium 的最小依赖)
sudo apt install -y libatk1.0-0 \
libatk-bridge2.0-0\
libcups2\
libatspi2.0-0\
libxcomposite1\
libxdamage1\
libgbm1\
代码初体验
访问http://whatsmyuseragent.org/,并保存截图,
import asyncio
from playwright.async_api import async_playwright
url ='http://whatsmyuseragent.org/'
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.goto(url)
await page.screenshot(path=f'whatsmyuseragent.png')
await browser.close()
asyncio.run(main())
在 notebook 里会报错
解决办法, 先安装nest_asyncio
, 并执行下面代码,
# pip install -U nest_asyncio
import nest_asyncio
nest_asyncio.apply()
现在应该可以顺利得到截图了
下载视频
现在开始下载 Beginner's Series to Rust[3] 的全部视频 仍然使用上面的代码,只是换了下链接,看是否能否得到想要的图片,
很遗憾,只有部分内容,经过研究后,是页面没有加载完全, 修改代码再试试
url = "https://learn.microsoft.com/en-us/shows/beginners-series-to-rust/"
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
page = await browser.new_page()
await page.goto(url)
# load the page and wait for the page to load
await page.wait_for_load_state("networkidle")
# 不加下面这行判断,可能会出现页面导航不出现的情况
await page.wait_for_selector("button.pagination-next", state="attached")
await page.screenshot(path=f'beginners-series-to-rust.png',full_page=True)
await browser.close()
asyncio.run(main())
这次 OK 了,
下面代码是抽取上面每个卡片对应的课程的名字和链接,
async def extract_data(page):
"""extract_data
抽取每节课程的链接和名称
Parameters
----------
page : Page
Page 实例
Returns
-------
Union[Dict, None]
"""
videos = []
all_items = await page.query_selector_all("article")
for item in all_items:
video = {}
name_el = await item.query_selector("span.title")
if name_el:
name = await name_el.inner_text()
video['name'] = name.replace(" | Beginner's Series to: Rust",'')
a = await item.query_selector("a")
if a:
href = await a.get_attribute('href')
video['link'] = href
if video:
videos.append(video)
return videos
这段代码则是抽取视频页面的视频地址,
async def extract_video_link(page,href):
"""extract_video_link
抽取每节课程的mp4链接
Parameters
----------
page : Page
Page 实例
href : str
课程页面链接
Returns
-------
Union[str, None]
"""
link = f"https://learn.microsoft.com{href}"
await page.goto(link)
await page.wait_for_load_state("networkidle")
# 等待 video 部分的加载
video = await page.wait_for_selector("video", state="attached")
video = await page.query_selector("video")
if video:
src = await video.get_attribute("src")
return src
执行整个爬虫动作,把视频的链接和名字记录到 data 变量里,
import asyncio
from playwright.async_api import async_playwright
data = []
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch()
# context = await browser.new_context()
page = await browser.new_page()
await page.goto(url)
#load the page and wait for the page to load
await page.wait_for_load_state("networkidle")
await page.wait_for_selector("button.pagination-next", state="attached")
data.extend(await extract_data(page))
item = await page.query_selector("button.pagination-next")
await item.click()
await page.wait_for_load_state("networkidle")
data.extend(await extract_data(page))
for video in data:
page = await browser.new_page()
src = await extract_video_link(page,video['link'])
print(src)
video['src'] = src
# await content.close()
await browser.close()
asyncio.run(main())
或得到所有的视频链接后,可以开始下一步下载视频里,这个推荐使用wget
或者requests
下载视频
import requests
def download(url,name):
"""download
下载视频
Parameters
----------
url : str
视频链接
name : str
保存视频的名字
Returns
-------
Union[Dict, None]
"""
res = requests.get(url)
if res.status_code==200:
content = res.content
with open(name, 'wb') as f:
f.write(content)
for video in data:
name =video['name'].replace(':',' -').replace('/','_').replace('?','')
name = f"{name}.mp4"
url = video['src']
download(url,name)
至此, 35 个视频下载完成。
从 YouTube 下载字幕
下载完成后,发现微软把视频也上传到了 Youtube, 不过下载比较慢(而且分辨率也差些),就选择只下载字幕(除了 16 节有中文字幕,其它均只有英文字幕)
到这里, 整个 Rust 入门视频下载完毕,稍晚会传到 B 站 如果你想下载全部视频和字幕到本地,关注并私信"rust 入门"获得下载链接
参考资料
Beginner's Series to Rust: https://learn.microsoft.com/en-us/shows/beginners-series-to-rust/
[2]playwright-python: https://github.com/microsoft/playwright-python
[3]Beginner's Series to Rust: https://learn.microsoft.com/en-us/shows/beginners-series-to-rust/