利用PIL和Selenium实现页面元素截图

时间:2021-07-12 21:31:18

预备

照张相片

selenium.webdriver可以实现对显示页面的截图:

from selenium import webdriver

dr = webdriver.Firefox()
dr.get('http://store.steampowered.com/')
dr.save_screenshot('D:\\page.png')

利用PIL和Selenium实现页面元素截图

利用PIL和Selenium实现页面元素截图

实际浏览器界面和截图结果

可以发现截图结果是浏览器内当前的显示内容。

让我想想...那只要让需要截图的元素出现在当前页面上,再从得到的截图里再把要的元素截取出来不就好啦?

那问题是怎么才能让当前元素先让我们看见呢?

让提线木偶动起来

在js中,页面可以滚动到特定元素:

item_obj = document.getElementsByClassName('store_capsule_frame');
item = item_obj[0];
item.style.border = '2px solid red';
item.scrollIntoView();

在浏览器的控制台执行上述代码

利用PIL和Selenium实现页面元素截图

可以看到页面会滚动到鉴赏家推荐这一栏,并且我给该元素加了个红线条的边框。边框内的内容就是我们要截图保存的元素。

scrollIntoView方法的参数是一个布尔值,默认为True,指的是否与顶部对齐。true则页面从元素顶部开始,false则页面会与底部对齐。

在selenium中,webdriver有execute_script的方法

可以向webdriver中直接注入任何js代码,操作页面,并且可以传入参数。

我们可以把上面的js代码注入到webdriver中,实现同样的效果。(这里稍微改动一下,用webdriver来获取元素)

item = dr.find_element('class name', 'store_capsule_frame')
dr.execute_script('arguments[0].scrollIntoView();', item)

js代码中的arguments是execute_script方法后面的参数集合,这里我们将定位的元素传入,效果如上图。

现在可以让想要的元素显示出来啦,自然也可以截出一张对应的页面图片啦。

那么又该如何定位元素在图片中的位置并裁剪出来呢?

来切相片吧

先画好位置

webdriver获取的页面元素封装了对裁剪很有用的信息。这里会用到它的locaion属性。

item.location  # {'x': 247, 'y': 1959}
item.size # {'width': , 'height': }

可以看到元素的location保存在一个字典中,分别是该元素相对整个html的x轴和y轴距离。size属性亦然。

因为截图时会以元素顶部为起始线,元素左上顶点在截图中的位置就是(item.location['x'],0),右下顶点位置就是(item.locaion['x'] + item.size['width'], item.size['height'])

学会剪裁

PIL是python中的图像处理库,pillow是其的一个分支,其核心仍然是PIL,但更易用。用pip即可安装。

来剪切刚刚保存的图片:

from PIL import Image
img = Image.open("D:\\page.png")
crop_size = (0,0,300,300)
cropped = img.crop(crop_size)
cropped.save('D:\\cropped.png')

先将图片读入到内存,存储到一个Image对象中。

crop方法返回一个长方形的Image对象,需要传入要剪切的尺寸,类型为一个元组。四个值分别是裁剪出的图片距离原图片左边、上边、右边、下边的距离,说简单点,其实就是裁剪结果左上顶点的x,y坐标以及右下定点的x,y坐标(以原图的左上顶点为原点)。

上面的操作会从原图片的左上角裁剪出一个300*300的正方形。

利用PIL和Selenium实现页面元素截图

初步代码

def get_element_screenshot(driver, by, value, path):
from selenium import webdriver
from PIL import Image
ele = driver.find_element(by, value)
driver.execute_script('arguments[0].scrollIntoView();', ele)
if driver.save_screenshot(path):
img = Image.open(path+'_Page.png')
size = (ele.location['x'],0,ele.location['x']+ele.size['width'],ele.size['height'])
cropped = img.crop(size)
cropped.save(path+'Cropped.png')
del img, cropped
print('Image is cropped successfully.')
return True
else:
print('Image not saved.')
return False

跌落的坑

我在截图过程中,发现按照页面元素的实际大小去截取而得的图片总是要小很多。原先以为是截取时选择的高和宽不对,毕竟js中height值根据padding和boder是否包含分为了clientHeight、offsetHeight等值。但查找多时,仍是没有结果,无论怎样改变,都无法准确截取。去浏览器的控制台中选中元素查看,发现元素的高宽和实际的像素值差了一些,实际值是其1.5倍。突然想到是否和分辨率有关,就去看了一下,是1920 * 1080。在控制台中用window.screen.availHeight获得可用高度,刚好是1.5倍。我以为症结就是这个,就搜索了一下,准备用win32api模块中的GetSystemMetrics方法获得屏幕分辨率,在除以可用高/宽度,拿到一个比例。但是执行该方法查询后发现结果是1280和720。我觉得很奇怪,不是1920 * 1080吗?又去显示设置去瞧了一下,才忽然知道应该是这缩放设置惹的祸,我将缩放设置为了150%。调为100%后,再去截取图片,便没有任何问题了。

费时不少,虽然错误很小,但中间也复习了height的相关知识,也学习了些win32模块的一些小知识点,算是绕路式学习。

是以为记。

利用PIL和Selenium实现页面元素截图

尚未解决的问题

scrollIntoView方法执行后,已经到页面底部,但是元素顶部并未与页面顶部对齐,这时的元素定位方法就要随之改变,但还未写,留待之后改进。

最终代码

def get_screenshot(driver, ele, path):
"""
:param driver: selenium webdriver object
:param by: method to find element
:param value: element value
:param path: path to save picture
:return:
"""
from PIL import Image # Decide whether a scroll bar exists
js_ret = driver.execute_script('''
var bounding_top = arguments[0].getBoundingClientRect();
if(document.documentElement.scrollHeight > document.documentElement.clientHeight){
arguments[0].scrollIntoView();
var rect_obj = arguments[0].getBoundingClientRect();
if(rect_obj.top == 0){
return 'scroll-and-on-the-top';
}else{
var size_array = new Array(4);
size_array[0] = rect_obj.x;
size_array[1] = rect_obj.y;
size_array[2] = rect_obj.right;
size_array[3] = rect_obj.bottom;
return size_array;
}
}else{
return 'no-scroll';
}
''', ele)
driver.execute_script('arguments[0].scrollIntoView();', ele)
if driver.save_screenshot(path):
img = Image.open(path)
_x = ele.location['x']
_y = ele.location['y']
_h = ele.size['height']
_w = ele.size['width']
if js_ret == 'scroll-and-on-the-top':
size = (_x, 0, _x + _w, _h)
elif js_ret == 'no-scroll':
size = (_x, _y, _x + _w, _y + _h)
else:
print(js_ret)
size = tuple(js_ret)
cropped = img.crop(size)
cropped.save(path)
del img, cropped
print('Image is cropped successfully and saved to %s.' % path)
return True
else:
print('Image not saved due to invalid file path. Screen shot failed.')
return False

参考

关于js中getBoundingClientRect()方法的说明,戳这里:

https://www.cnblogs.com/Songyc/p/4458570.html

利用PIL和Selenium实现页面元素截图的更多相关文章

  1. Selenium操作页面元素

    转自:http://blog.sina.com.cn/s/blog_6966650401012a7q.html 一.输入框(text field or textarea) //找到输入框元素: Web ...

  2. selenium定位页面元素的一件趣事

    PS:本博客selenium分类不会记载selenium打开浏览器,定位元素,操作页面元素,切换到iframe,处理alter.confirm和prompt对话框这些在网上随处可见的信息:本博客此分类 ...

  3. Selenium 定位页面元素 以及总结页面常见的元素 以及总结用户常见的操作

    1. Selenium常见的定位页面元素 2.页面常见的元素 3. 用户常见的操作 1. Selenium常见的定位页面元素 driver.findElement(By.id());driver.fi ...

  4. [Selenium] 操作页面元素等待时间

    WebDriver 在操作页面元素等待时间时,提供2种等待方式:一个为显式等待,一个为隐式等待,其区别在于: 1)显式等待:明确地告诉 WebDriver 按照特定的条件进行等待,条件未达到就一直等待 ...

  5. Selenium解决页面元素不在视野范围内的问题

    当需要使用滚动条才能使页面元素显示在视野范围内时,必须用代码处理下,才能对其进行操作. 处理其实也很简单,就是调用JS函数. driver.executeScript("arguments[ ...

  6. selenium找到页面元素click没反应

    问题描述:通过调试可以看到控制台已经找到了起诉入口页面元素,可是点击“我是原告”没有反应了,也没有报错 解决办法:登录时是跳进了两层的iframe中,需要跳出iframe才能找到我是原告.

  7. 【selenium学习笔记一】python + selenium定位页面元素的办法。

    1.什么是Selenium,为什么web测试,大家都用它? Selenium设计初衷就是为web项目的验收测试再开发.内核使用的是javaScript语言编写,几乎支持所以能运行javaScript的 ...

  8. python + selenium定位页面元素的办法

    1.什么是Selenium,为什么web测试,大家都用它? Selenium设计初衷就是为web项目的验收测试再开发.内核使用的是javaScript语言编写,几乎支持所以能运行javaScript的 ...

  9. centos下利用phantomjs来完成网站页面快照截图

    最近研究了下phantomjs,感觉还是非常不错的. 首先到官网下载一个源码包 http://phantomjs.org/download.html 点击源码包下载如图: 然后在linux下将必要的一 ...

随机推荐

  1. ln 命令使用

    今天在工作中遇到了“ln -sf”命令,发觉很久没用基本忘光,遂重拾鸟哥神书温补了一把. 简单描述的话,ln是linux中用来链接文件的,存在两种不同的连接: 1) Hard Link 实现该操作很简 ...

  2. MEMORY Storage Engine MEMORY Tables TEMPORARY TABLE max_heap_table_size

    http://dev.mysql.com/doc/refman/5.7/en/create-table.html You can use the TEMPORARY keyword when crea ...

  3. Spring对Hibernate事务管理【转】

    在谈Spring事务管理之前我们想一下在我们不用Spring的时候,在Hibernate中我们是怎么进行数据操作的.在Hibernate中我们每次进行一个操作的的时候我们都是要先开启事务,然后进行数据 ...

  4. Web APIs 基于令牌TOKEN验证的实现

    Web APIs 基于令牌TOKEN验证的实现 概述: ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作.但 ...

  5. DotNetCore跨平台~为Lind.DotNetCore框架添加单元测试的意义

    回到目录 单元测试大叔认为有几下两个必要的作用,也是为什么要上单元测试的原因 组件,框架在修改和BUG解决后,进行正确性的测试,然后才能打包 业务模块,主要提现在进行业务规则的模拟上面,保证了业务逻辑 ...

  6. 牛客小白月赛13-J小A的数学题 (莫比乌斯反演)

    链接:https://ac.nowcoder.com/acm/contest/549/J来源:牛客网 题目描述 小A最近开始研究数论题了,这一次他随手写出来一个式子,∑ni=1∑mj=1gcd(i,j ...

  7. C# Quartz定时任务corn时间设置详解

    http://cron.qqe2.com/  如果不会 或者想检验自己是否写的对就  通过这个网站 检测 或自动生成 *    *         *     *      *      *      ...

  8. Java访问权限控制

    访问权限控制           java提供了访问权限修饰词,以供类库开发人员向客户端程序员指明哪些是可用的,哪些是不可用的.访问权限控制的等级,从最大权限到最小权限依次是:public.prote ...

  9. git将本地仓库强制替换掉远程仓库

    $ git remote add origin <url> $ git push --force --set-upstream origin master

  10. Centos7 linux下 安装 Redis 5&period;0

    网上找了很多文章,发现不全而且有些问题,安装很多次之后,总结一篇可以使用的,记录之. 环境:Centos7+Redis 5.0,如果环境不符合,本篇仅供参考. 1.准备工作 作者习惯软件安装包放在单独 ...