04 selenium网页截长图(滚动截图+拼接)

1,889 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情

「selenium实战专栏」将记录selenium实战(Python版)过程,以及各类问题的解决方案。

大致规划如下:

  • 利用Element UI组件库联系对各种元素的操作
  • 利用一个真实网站进行部分页面UI自动化实战

使用版本如下:

  • Python 3.10.6
  • selenium 4.0.5

上节通过ElementUI提供的超链接元素练习了如何获取超链接元素,并且通过一种新的调试方案,即获取所有的元素后,通过执行js语句给元素修改样式标红,效果如下图(感兴趣的可查看专栏文章):

但是这引发了另外一个问题,「禁用状态」里的三个元素也被标红了,但是截图只截了当前窗口,所以下面的元素无法完整截图,同样,如果页面下方还有获取的元素,同样在截图中无法看见,因此这里可以进行进一步优化,即网页滚动截图

第一种方案

主要思路如下:

  • 获取到所有的元素,执行JS语句将元素标红
  • 滑动屏幕,循环截取当前窗口的图片
  • 通过PIL将图片进行拼接

第一个步骤可以查看上一节,本节主要处理后面两步。

滑动屏幕,循环截取当前窗口的图片

首先给大家介绍几个js语句:

  • window.innerHieght 获取窗口高度
  • window.scroll(x,y) 将窗口原点滑动到坐标(x,y)的位置。
  • document.body.scrollHeight 获取网页滑动高度

网页上的坐标如下图所示,所以想要向下滑动窗口,就需要设置y的值不断增加,直到滑动到底部。

具体截图代码如下:

service = Service(executable_path='/Users/huyanping/Softwares/chromedriver')
driver = webdriver.Chrome(service=service)

driver.get("https://element-plus.gitee.io/zh-CN/component/link.html")

# 隐式等待,暂时可以先不用管
driver.implicitly_wait(10)
# element-ui页面会请求一些外部页面,导致需要很长时间的等待,
# 可以设置超时时间避免需要等待很长时间,但是可能会导致元素还没加载出来,可以根据自己的网络对超时时间进行调整
driver.set_page_load_timeout(6)

# 通过JS语句设置元素属性style arguments是参数
js = "arguments[0].setAttribute('style', arguments[1]);"
# css语句,给元素添加边框
style = "border: 5px solid red;"
elements = driver.find_elements(By.PARTIAL_LINK_TEXT, 'i')
for index in range(len(elements)):
    # 执行JS语句,将元素作为参数传递
    driver.execute_script(js, elements[index], style)

# 获取网页高度
body_height = driver.execute_script('return document.body.scrollHeight;')
window_height = driver.execute_script('return window.innerHeight;')
js = "window.scroll(0,arguments[0]*arguments[1])"
i = 0
while i * window_height < body_height:
    driver.execute_script(js, window_height, i)
    driver.get_screenshot_as_file(f"{i}.png")
    i += 1


# 因为点击操作执行完成后,很快就会关闭浏览器无法看到效果,调试的时候可以先注释掉
# 但是要记得自己手动关闭浏览器,避免开很多的浏览器未关闭消耗电脑内存
# driver.quit()

通过PIL将图片进行拼接

首先通过命令pip install Pillow安装图像处理依赖,循环将生成的图片进行两两拼接。 拼接代码如下:

def image_Splicing(img_1, img_2, res_img, flag='y'):
    img1 = Image.open(img_1)
    img2 = Image.open(img_2)
    size1, size2 = img1.size, img2.size
    if flag == 'x':
        joint = Image.new("RGB", (size1[0] + size2[0], size1[1]))
        loc1, loc2 = (0, 0), (size1[0], 0)
    else:
        joint = Image.new("RGB", (size1[0], size2[1] + size1[1]))
        loc1, loc2 = (0, 0), (0, size1[1])
    joint.paste(img1, loc1)
    joint.paste(img2, loc2)
    joint.save(res_img)

运行脚本可以看到图片拼接完成后生成了新的图片。

脚本优化(selenium隐藏元素)

从拼接的图片可以看到,在两个图片的交接处,存在元素被覆盖的问题,这个跟具体的网站有关系。手动上滑观察网站,可以发现它的“导航栏”是固定在顶部的,因此拼接时会被其遮挡。

这里提供一种方法用于隐藏这个“导航栏”元素,首先获取到导航栏元素,然后通过执行js语句给元素设置样式display:none;,这样该元素则会被隐藏,然后再进行滚动截图。

同样是在浏览器右键找到“导航栏”元素,通过分析DOM结构,可以发现它是一个<header>标签,因此可以尝试一种新的方式——TagName来获取元素,具体代码如下:

js = "arguments[0].setAttribute('style', arguments[1]);"
style = "display:none;"
# 将导航栏元素隐藏
header_element=driver.find_element(By.TAG_NAME,'header')
driver.execute_script(js, header_element, style)

再次执行脚本就可以看到拼接处没有任何的元素遮挡了。

完整代码如下,对于Element UI这个网站来说,代码还有可优化的部分,比如,仅指对可滑动元素截图等,欢迎大家评论区留言~

from PIL import Image
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By


def image_Splicing(img_1, img_2, res_img, flag='y'):
    img1 = Image.open(img_1)
    img2 = Image.open(img_2)
    size1, size2 = img1.size, img2.size
    if flag == 'x':
        joint = Image.new("RGB", (size1[0] + size2[0], size1[1]))
        loc1, loc2 = (0, 0), (size1[0], 0)
    else:
        joint = Image.new("RGB", (size1[0], size2[1] + size1[1]))
        loc1, loc2 = (0, 0), (0, size1[1])
    joint.paste(img1, loc1)
    joint.paste(img2, loc2)
    joint.save(res_img)


service = Service(executable_path='/Users/huyanping/Softwares/chromedriver')
driver = webdriver.Chrome(service=service)

driver.get("https://element-plus.gitee.io/zh-CN/component/link.html")

# 隐式等待,暂时可以先不用管
driver.implicitly_wait(10)
# element-ui页面会请求一些外部页面,导致需要很长时间的等待,
# 可以设置超时时间避免需要等待很长时间,但是可能会导致元素还没加载出来,可以根据自己的网络对超时时间进行调整
driver.set_page_load_timeout(6)

# 通过JS语句设置元素属性style arguments是参数
js = "arguments[0].setAttribute('style', arguments[1]);"
# css语句,隐藏元素
style = "display:none;"
# 将导航栏元素隐藏
header_element = driver.find_element(By.TAG_NAME, 'header')
driver.execute_script(js, header_element, style)

# css语句,给元素添加边框
style = "border: 5px solid red;"
elements = driver.find_elements(By.PARTIAL_LINK_TEXT, 'i')
for index in range(len(elements)):
    # 执行JS语句,将元素作为参数传递
    driver.execute_script(js, elements[index], style)

# 获取网页高度
body_height = driver.execute_script('return document.body.scrollHeight;')
window_height = driver.execute_script('return window.innerHeight;')
js = "window.scroll(0,arguments[0]*arguments[1])"
i = 0
while i * window_height < body_height:
    driver.execute_script(js, window_height, i)
    driver.get_screenshot_as_file(f"{i}.png")
    i += 1

res_img = "0.png"

for j in range(1, i-1):
    image_Splicing(res_img, f"{j}.png", res_img)

# 因为点击操作执行完成后,很快就会关闭浏览器无法看到效果,调试的时候可以先注释掉
# 但是要记得自己手动关闭浏览器,避免开很多的浏览器未关闭消耗电脑内存
# driver.quit()