Python3 爬虫学习笔记 C13【验证码对抗系列 — 滑动验证码】


Python3 爬虫学习笔记第十三章 —— 【验证码对抗系列 — 滑动验证码】

文章目录

  • 【13.1】关于滑动验证码
  • 【13.2】滑动验证码攻克思路
  • 【13.3】模拟登录 bilibili — 总体思路
  • 【13.4】主函数
  • 【13.5】初始化函数
  • 【13.6】登录函数
  • 【13.7】验证码元素查找函数
  • 【13.8】元素可见性设置函数
  • 【13.9】验证码截图函数
  • 【13.10】滑动函数
  • 【13.11】计算滑块移动距离函数
  • 【13.12】像素判断函数
  • 【13.13】构造移动轨迹函数
  • 【13.14】模拟拖动函数
  • 【13.15】效果实现动图
  • 【13.16】完整代码


【13.1】关于滑动验证码

滑动验证码属于行为式验证码,需要通过用户的操作行为来完成验证,一般是根据提示用鼠标将滑块拖动到指定的位置完成验证,此类验证码背景图片采用多种图像加密技术,且添加了很多随机效果,能有效防止OCR文字识别,另外,验证码上的文字采用了随机印刷技术,能够随机采用多种字体、多种变形的实时随机印刷,防止暴力破解;斗鱼、哔哩哔哩、淘宝等平台都使用了滑动验证码

02


【13.2】滑动验证码攻克思路

利用自动化测试工具 Selenium 直接模拟人的行为方式来完成验证,首先要分析页面,想办法找到滑动验证码的完整图片、带有缺口的图片和需要滑动的图片,通过对比原始的图片和带滑块缺口的图片的像素,像素不同的地方就是缺口位置,计算出滑块缺口的位置,得到所需要滑动的距离,最后利用 Selenium 进行对滑块的拖拽,拖拽时要模仿人的行为,由于有个对准过程,所以是先快后慢,匀速移动、随机速度移动都不会成功

以下以哔哩哔哩为例来做模拟登录练习


【13.3】模拟登录 bilibili — 总体思路

首先使用 Selenium 模拟登陆 bilibili,自动输入账号密码,查找到登陆按钮并点击,使其出现滑动验证码,此时分析页面,滑动验证组件是由3个 canvas 组成,分别代表完整图片、带有缺口的图片和需要滑动的图片,3个 canvas 元素包含 CSS display 属性,display:block 为可见,display:none 为不可见,分别获取三张图片时要将其他两张图片设置为 display:none,获取元素位置后即可对图片截图并保存,通过图片像素对比,找到缺口位置即为滑块要移动的距离,随后构造滑动轨迹,按照先加速后减速的方式移动滑块完成验证。

整个程序包含的函数:

def init(): 初始化函数,定义全局变量
def login(): 登录函数,输入账号密码并点击登录
def find_element(): 验证码元素查找函数,查找三张图的元素
def hide_element(): 设置元素不可见函数
def show_element(): 设置元素可见函数
def save_screenshot(): 验证码截图函数,截取三张图并保存
def slide(): 滑动函数
def is_pixel_equal(): 像素判断函数,寻找缺口位置
def get_distance(): 计算滑块移动距离函数
def get_track(): 构造移动轨迹函数
def move_to_gap(): 模拟拖动函数

整个程序用到的库:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
import time
import random

【13.4】主函数

if __name__ == '__main__':init()login()find_element()slide()

【13.5】初始化函数

def init():global url, browser, username, password, waiturl = 'https://passport.bilibili.com/login'path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'chrome_options = Options()chrome_options.add_argument('--start-maximized')browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)username = '155********'password = '***********'wait = WebDriverWait(browser, 20)

global 关键字定义了全局变量,随后是登录页面url、谷歌浏览器驱动的目录path、实例化 Chrome 浏览器、设置浏览器分辨率最大化、用户名、密码、WebDriverWait() 方法设置等待超时


【13.6】登录函数

def login():browser.get(url)user = wait.until(EC.presence_of_element_located((By.ID, 'login-username')))passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd')))user.send_keys(username)passwd.send_keys(password)login_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a.btn.btn-login')))time.sleep(random.random() * 3)login_btn.click()

等待用户名输入框和密码输入框对应的 ID 节点加载出来,分析页面可知,用户名输入框 id="login-username",密码输入框 id="login-passwd",获取这两个节点,调用 send_keys() 方法输入用户名和密码,随后获取登录按钮,分析页面可知登录按钮 class="btn btn-login",随机产生一个数并将其扩大三倍作为暂停时间,最后调用 click() 方法实现登录按钮的点击


【13.7】验证码元素查找函数

def find_element():c_background = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute')))c_slice = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute')))c_full_bg = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute')))hide_element(c_slice)save_screenshot(c_background, 'back')show_element(c_slice)save_screenshot(c_slice, 'slice')show_element(c_full_bg)save_screenshot(c_full_bg, 'full')

我们要获取验证码的三张图片,分别是完整的图片、带有缺口的图片和需要滑动的图片,分析页面代码,这三张图片是由 3 个 canvas 组成,3 个 canvas 元素包含 CSS display 属性,display:block 为可见,display:none 为不可见,在分别获取三张图片时要将其他两张图片设置为 display:none,这样做才能单独提取到每张图片,定位三张图片的 class 分别为:带有缺口的图片(c_background):geetest_canvas_bg geetest_absolute、需要滑动的图片(c_slice):geetest_canvas_slice geetest_absolute、完整图片(c_full_bg):geetest_canvas_fullbg geetest_fade geetest_absolute,随后传值给 save_screenshot() 函数,进一步对验证码进行处理

02


【13.8】元素可见性设置函数

# 设置元素不可见
def hide_element(element):browser.execute_script("arguments[0].style=arguments[1]", element, "display: none;")# 设置元素可见
def show_element(element):browser.execute_script("arguments[0].style=arguments[1]", element, "display: block;")

【13.9】验证码截图函数

def save_screenshot(obj, name):try:pic_url = browser.save_screenshot('.\\bilibili.png')print("%s:截图成功!" % pic_url)left = obj.location['x']top = obj.location['y']right = left + obj.size['width']bottom = top + obj.size['height']print('图:' + name)print('Left %s' % left)print('Top %s' % top)print('Right %s' % right)print('Bottom %s' % bottom)print('')im = Image.open('.\\bilibili.png')im = im.crop((left, top, right, bottom))file_name = 'bili_' + name + '.png'im.save(file_name)except BaseException as msg:print("%s:截图失败!" % msg)

location 属性可以返回该图片对象在浏览器中的位置,坐标轴是以屏幕左上角为原点,x轴向右递增,y轴向下递增,size 属性可以返回该图片对象的高度和宽度,由此可以得到验证码的位置信息,首先调用 save_screenshot() 属性对整个页面截图并保存,然后向 crop() 方法传入验证码的位置信息,由位置信息再对验证码进行剪裁并保存


【13.10】滑动函数

def slide():distance = get_distance(Image.open('.\\bili_back.png'), Image.open('.\\bili_full.png'))print('计算偏移量为:%s Px' % distance)trace = get_trace(distance - 5)move_to_gap(trace)time.sleep(3)

get_distance() 函数传入完整的图片和缺口图片,计算滑块需要滑动的距离,再把距离信息传入 get_trace() 函数,构造滑块的移动轨迹,最后根据轨迹信息调用 move_to_gap() 函数移动滑块完成验证


【13.11】计算滑块移动距离函数

def get_distance(bg_image, fullbg_image):distance = 60for i in range(distance, fullbg_image.size[0]):for j in range(fullbg_image.size[1]):if not is_pixel_equal(fullbg_image, bg_image, i, j):return i

get_distance() 方法即获取缺口位置的方法,此方法的参数是两张图片,一张为完整的图片,另一张为带缺口的图片,distance 为滑块的初始位置,遍历两张图片的每个像素,利用 is_pixel_equal() 像素判断函数判断两张图片同一位置的像素是否相同,比较两张图 RGB 的绝对值是否均小于定义的阈值 threshold,如果绝对值均在阈值之内,则代表像素点相同,继续遍历,否则代表不相同的像素点,即缺口的位置


【13.12】像素判断函数

def is_pixel_equal(bg_image, fullbg_image, x, y):bg_pixel = bg_image.load()[x, y]fullbg_pixel = fullbg_image.load()[x, y]threshold = 60if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs(bg_pixel[2] - fullbg_pixel[2] < threshold)):return Trueelse:return False

将完整图片和缺口图片两个对象分别赋值给变量 bg_image和 fullbg_image,接下来对比图片获取缺口。我们在这里遍历图片的每个坐标点,获取两张图片对应像素点的 RGB 数据,判断像素的各个颜色之差,abs() 用于取绝对值,如果二者的 RGB 数据差距在一定范围内,那就代表两个像素相同,继续比对下一个像素点,如果差距超过一定范围,则代表像素点不同,当前位置即为缺口位置


【13.13】构造移动轨迹函数

def get_trace(distance):trace = []faster_distance = distance * (4 / 5)start, v0, t = 0, 0, 0.1while start < distance:if start < faster_distance:a = 10else:a = -10move = v0 * t + 1 / 2 * a * t * tv = v0 + a * tv0 = vstart += movetrace.append(round(move))return trace

get_trace() 方法传入的参数为移动的总距离,返回的是运动轨迹,运动轨迹用 trace 表示,它是一个列表,列表的每个元素代表每次移动多少距离,利用 Selenium 进行对滑块的拖拽时要模仿人的行为,由于有个对准过程,所以是先快后慢,匀速移动、随机速度移动都不会成功,因此要设置一个加速和减速的距离,这里设置加速距离 faster_distance 是总距离 distance 的4/5倍,滑块滑动的加速度用 a 来表示,当前速度用 v 表示,初速度用 v0 表示,位移用 move 表示,所需时间用 t 表示,它们之间满足以下关系:

move = v0 * t + 0.5 * a * t * t 
v = v0 + a * t

设置初始位置、初始速度、时间间隔分别为0, 0, 0.1,加速阶段和减速阶段的加速度分别设置为10和-10,直到运动轨迹达到总距离时,循环终止,最后得到的 trace 记录了每个时间间隔移动了多少位移,这样滑块的运动轨迹就得到了


【13.14】模拟拖动函数

def move_to_gap(trace):slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button')))ActionChains(browser).click_and_hold(slider).perform()for x in trace:ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()time.sleep(0.5)ActionChains(browser).release().perform()

传入的参数为运动轨迹,首先查找到滑动按钮,然后调用 ActionChains 的 click_and_hold() 方法按住拖动底部滑块,perform() 方法用于执行,遍历运动轨迹获取每小段位移距离,调用 move_by_offset() 方法移动此位移,最后调用 release() 方法松开鼠标即可


【13.15】效果实现动图

最终实现效果图:(关键信息已经过打码处理)
03


【13.16】完整代码

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver import ActionChains
import time
import random
from PIL import Imagedef init():global url, browser, username, password, waiturl = 'https://passport.bilibili.com/login'path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'chrome_options = Options()chrome_options.add_argument('--start-maximized')browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)username = '155********'password = '***********'wait = WebDriverWait(browser, 20)def login():browser.get(url)user = wait.until(EC.presence_of_element_located((By.ID, 'login-username')))passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd')))user.send_keys(username)passwd.send_keys(password)login_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a.btn.btn-login')))time.sleep(random.random() * 3)login_btn.click()def find_element():c_background = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute')))c_slice = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute')))c_full_bg = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute')))hide_element(c_slice)save_screenshot(c_background, 'back')show_element(c_slice)save_screenshot(c_slice, 'slice')show_element(c_full_bg)save_screenshot(c_full_bg, 'full')def hide_element(element):browser.execute_script("arguments[0].style=arguments[1]", element, "display: none;")def show_element(element):browser.execute_script("arguments[0].style=arguments[1]", element, "display: block;")def save_screenshot(obj, name):try:pic_url = browser.save_screenshot('.\\bilibili.png')print("%s:截图成功!" % pic_url)left = obj.location['x']top = obj.location['y']right = left + obj.size['width']bottom = top + obj.size['height']print('图:' + name)print('Left %s' % left)print('Top %s' % top)print('Right %s' % right)print('Bottom %s' % bottom)print('')im = Image.open('.\\bilibili.png')im = im.crop((left, top, right, bottom))file_name = 'bili_' + name + '.png'im.save(file_name)except BaseException as msg:print("%s:截图失败!" % msg)def slide():distance = get_distance(Image.open('.\\bili_back.png'), Image.open('.\\bili_full.png'))print('计算偏移量为:%s Px' % distance)trace = get_trace(distance - 5)move_to_gap(trace)time.sleep(3)def get_distance(bg_image, fullbg_image):distance = 60for i in range(distance, fullbg_image.size[0]):for j in range(fullbg_image.size[1]):if not is_pixel_equal(fullbg_image, bg_image, i, j):return idef is_pixel_equal(bg_image, fullbg_image, x, y):bg_pixel = bg_image.load()[x, y]fullbg_pixel = fullbg_image.load()[x, y]threshold = 60if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs(bg_pixel[2] - fullbg_pixel[2] < threshold)):return Trueelse:return Falsedef get_trace(distance):trace = []faster_distance = distance * (4 / 5)start, v0, t = 0, 0, 0.1while start < distance:if start < faster_distance:a = 20else:a = -20move = v0 * t + 1 / 2 * a * t * tv = v0 + a * tv0 = vstart += movetrace.append(round(move))return tracedef move_to_gap(trace):slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button')))ActionChains(browser).click_and_hold(slider).perform()for x in trace:ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()time.sleep(0.5)ActionChains(browser).release().perform()if __name__ == '__main__':init()login()find_element()slide()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/437929.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Sharepoin学习笔记—架构系列—06 Sharepoint服务(Services)与服务应用程序框架(Service Application Framework) 1

Sharepoint服务是Sharepoint的重要组成&#xff0c;可以说Sharepoint的许多网站功能都是基于这些服务构架起来的。这里把Sharepoint服务的相关要点总结一下。 1、什么是 SharePoint 服务&#xff1f; SharePoint 服务是一项 IT 服务&#xff0c;它是运行在后台&#xff0c;为调…

Python3 爬虫学习笔记 C14【验证码对抗系列 — 点触验证码】

Python3 爬虫学习笔记第十四章 —— 【验证码对抗系列 — 点触验证码】文章目录【14.1】关于点触验证码【14.2】点触验证码攻克思路【14.3】模拟登录 12306 — 总体思路【14.4】主函数【14.5】初始化函数【14.6】破解入口函数【14.7】账号密码输入函数【14.8】页面截图函数【14…

Sharepoin学习笔记—架构系列—07nSharepoint服务(Services)与服务应用程序框架(Service Application Framework) 2

上一篇我们以问答的方式明确了Sharepoint服务的一些概念&#xff0c;这里我们重点来看两个方面:Sharepoint服务器构架对象模型以及Sharepoint 服务应用程序的某些拓扑结构 一、Sharepoint服务器构架对象模型 转存失败重新上传取消 二、Sharepoint 服务应用程序的某些拓扑结…

Sharepoin学习笔记—架构系列--08 Sharepoint的数据模型(DataModel)、数据管理(Data Management)与查询(Query System)

Sharepoint Foundation中的首要数据结构就是列表(List), 每个List属于某种List Type&#xff0c;与此类似&#xff0c;每个列表中的列(Column)属于某种FieldType&#xff0c;而每一条列表记录(List Item)属于某种Content Type.至于外部数据(External Data)&#xff0c;即来自于…

执行 redis-dump 报错:ERROR (Errno::ENOENT): No such file or directory - ps -o rss= -p xxxxx

redis-dump 命令用于 Redis 数据的导出&#xff0c;执行该命令时报错如下&#xff1a; C:\Users\Lenovo>redis-dump -u 127.0.0.1:6379 ERROR (Errno::ENOENT): No such file or directory - ps -o rss -p 3944解决方法&#xff1a;找到 Ruby 安装目录下的 dump.rb 文件&a…

Sharepoint学习笔记 –架构系列—09 Sharepoint的服务器端对象模型(Server Object Model) 1.物理对象层次结构

关于Sharepoint的服务器端对象模型的内容很庞大很繁杂&#xff0c;而事实上&#xff0c;我们在这里只把最关键的对象梳理一下&#xff0c;我们会从三个体系来大致描述它们。 这三个体系分别是&#xff1a; 1、物理对象层次结构(Physical Objects Hierarchy) 2、内容层次结构(Co…

执行 redis-dump 报错:Error connecting to Redis on localhost:6379 (Redis::TimeoutError)

拿本地的 Redis 做测试&#xff0c;运行在 6379 端口上&#xff0c;使用 redis-dump -u localhost:6379 命令用于数据的导出&#xff0c;执行该命令时报错如下&#xff1a; C:\Users\Lenovo>redis-dump -u localhost:6379 Error connecting to Redis on localhost:6379 (Re…

Sharepoint学习笔记 –架构系列—10 Sharepoint的服务器端对象模型(Server Object Model) 2.内容层次结构

Sharepoint的内容层次结构&#xff08;Content Hierarchy&#xff09;包括表示可发布数据项(publishable items)&#xff0c;如列表项的类&#xff0c;还包括表示嵌套的数据容器(nested containers of data),如列表、内容数据库、网站、网站集以及称为"Web 应用程序"…

Sharepoint学习笔记 –架构系列—11 Sharepoint的服务器端对象模型(Server Object Model) 3.服务层次结构

前面我们看了一下Sharepoint服务器对象模型的物理对象层次(Physical Objects Hierarchy)和对象内容层次(Content Hierarchy)中的相关类&#xff0c;这里来看看服务层次结构&#xff0c;其中包括表示 Web 服务(WebService)、Windows 服务(WindowService)、其他类型的服务(Icomin…

pyspider all 启动失败:ValueError: Invalid configuration

执行 pyspider all 启动命令报错如下&#xff1a; C:\Users\Lenovo>pyspider all e:\python\lib\site-packages\pyspider\libs\utils.py:196: FutureWarning: timeout is not supported on your platform.warnings.warn("timeout is not supported on your platform.&…

Sharepoint学习笔记 –架构系列—12 Sharepoint的客户端对象模型(Client Object Model)

前面过了一下Sharepoint的服务器端对象模型&#xff0c;接下来就让我们大致看看Sharepoint的客户端对象模型(Client Object Model: Client OM)。 首先需要了解的就是Sharepoint的客户端模型是在Sharepoint2010才开始引入的&#xff0c;之前没有这个概念。 一、为什么要引入客户…

Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS

2022-01-25 更新&#xff1a;博客新地址&#xff1a;https://www.itbob.cn/&#xff0c;文章距上次编辑时间较远&#xff0c;部分内容可能已经过时&#xff01; 我的博客地址&#xff1a;https://www.itrhx.com/ 部署到 Coding Pages 的好处&#xff1a;国内访问速度更快&…

Git得基本使用方法add、commit、push、checkout以及Pull

一、Git是什么&#xff1f; Git是目前世界上最先进的分布式版本控制系统。 Working Directory&#xff1a;工作区 Index / Stage&#xff1a;暂存区 Repository&#xff1a;仓库区&#xff08;或本地仓库&#xff09; Remote&#xff1a;远程仓库 由于前面2篇文章已经简单得…

Coding Pages 申请 SSL 证书错误:urn:acme:error:unauthorized: Invalid response from http://xxxxx/

Coding Pages 申请 SSL/TLS 安全证书出现以下错误&#xff1a; urn:acme:error:unauthorized: Invalid response from http://www.xxxx.cn/.well-known/acme-challenge/ysOz9wW3U_GFPP8kRP4w8uknBZ9UfiUT7t2xpu9pDCw [185.199.111.153]: "\n\n \n <meta http-equiv“Co…

Hexo 博客提交百度、谷歌搜索引擎收录

2022-01-25 更新&#xff1a;博客新地址&#xff1a;https://www.itbob.cn/&#xff0c;文章距上次编辑时间较远&#xff0c;部分内容可能已经过时&#xff01; 文章目录● 写在前面&#xff08;必看&#xff09;● 查看网站是否被收录● 百度资源平台添加网站● 提交百度搜索●…

工作区、暂存区、版本库、远程仓库

一、概念 1、四个工作区域 Git本地有四个工作区域&#xff1a;工作目录&#xff08;Working Directory&#xff09;、暂存区(Stage/Index)、资源库(Repository或Git Directory)、git仓库(Remote Directory)。文件在这四个区域之间的转换关系如下&#xff1a; Workspace&#x…

Python3 爬虫学习笔记 C17【爬虫框架 pyspider — 基本使用】

Python3 爬虫学习笔记第十七章 —— 【爬虫框架 pyspider — 基本使用】文章目录【17.1】初识 pyspider【17.2】使用 pyspider【17.2.1】主界面【17.2.2】项目界面【17.3】使用 pyspider 爬取去哪儿网【17.3.1】爬取首页【17.3.2】信息匹配【17.3.3】抓取下一页数据【17.3.4】抓…

什么是RPA 现在都有哪些产品

作者&#xff1a;小金同学 链接&#xff1a;https://www.zhihu.com/question/264066539/answer/730946238 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 什么是RPA&#xff1f; 根据机器人流程自动化和人工智能研…

Sharepoint学习笔记—ECM系列—1 Content Type Syndication

我们可以通过Content Type Syndication来实现在Sharepoint Farm内的不同Site Collection之间共享内容类型(Content Types).也就是说&#xff0c;管理员可以为某个元数据服务(Metadata Service)指定一个网站集(Site Collection)来充当Content Type Hub&#xff0c;Content Type …

Python3 爬虫实战 — 猫眼电影TOP100【requests、lxml、Xpath、CSV 】

爬取时间&#xff1a;2019-09-23爬取难度&#xff1a;★☆☆☆☆☆请求链接&#xff1a;https://maoyan.com/board/4爬取目标&#xff1a;猫眼电影 TOP100 的电影名称、排名、主演、上映时间、评分、封面图地址&#xff0c;数据保存为 CSV 文件涉及知识&#xff1a;请求库 requ…