#58 增加QWebEngineView截图支持
This commit is contained in:
parent
eba99f8fe5
commit
49b0fb189a
8 changed files with 236 additions and 1 deletions
|
@ -58,6 +58,7 @@ encoding//QTreeWidget/ParsingJson.py=utf-8
|
||||||
encoding//QTreeWidget/testTreeWidget.py=utf-8
|
encoding//QTreeWidget/testTreeWidget.py=utf-8
|
||||||
encoding//QWebEngineView/GetCookie.py=utf-8
|
encoding//QWebEngineView/GetCookie.py=utf-8
|
||||||
encoding//QWebEngineView/JsSignals.py=utf-8
|
encoding//QWebEngineView/JsSignals.py=utf-8
|
||||||
|
encoding//QWebEngineView/ScreenShotPage.py=utf-8
|
||||||
encoding//QWebView/DreamTree.py=utf-8
|
encoding//QWebView/DreamTree.py=utf-8
|
||||||
encoding//QWebView/GetCookie.py=utf-8
|
encoding//QWebView/GetCookie.py=utf-8
|
||||||
encoding//QWebView/JsSignals.py=utf-8
|
encoding//QWebView/JsSignals.py=utf-8
|
||||||
|
|
20
QWebEngineView/Data/html2canvas.min.js
vendored
Normal file
20
QWebEngineView/Data/html2canvas.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
QWebEngineView/Data/jquery.js
vendored
Normal file
4
QWebEngineView/Data/jquery.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
QWebEngineView/Data/promise-7.0.4.min.js
vendored
Normal file
2
QWebEngineView/Data/promise-7.0.4.min.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
!function n(t,e,r){function o(u,f){if(!e[u]){if(!t[u]){var c="function"==typeof require&&require;if(!f&&c)return c(u,!0);if(i)return i(u,!0);var s=new Error("Cannot find module '"+u+"'");throw s.code="MODULE_NOT_FOUND",s}var l=e[u]={exports:{}};t[u][0].call(l.exports,function(n){var e=t[u][1][n];return o(e?e:n)},l,l.exports,n,t,e,r)}return e[u].exports}for(var i="function"==typeof require&&require,u=0;u<r.length;u++)o(r[u]);return o}({1:[function(n,t,e){"use strict";function r(){}function o(n){try{return n.then}catch(t){return d=t,w}}function i(n,t){try{return n(t)}catch(e){return d=e,w}}function u(n,t,e){try{n(t,e)}catch(r){return d=r,w}}function f(n){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof n)throw new TypeError("not a function");this._37=0,this._12=null,this._59=[],n!==r&&v(n,this)}function c(n,t,e){return new n.constructor(function(o,i){var u=new f(r);u.then(o,i),s(n,new p(t,e,u))})}function s(n,t){for(;3===n._37;)n=n._12;return 0===n._37?void n._59.push(t):void y(function(){var e=1===n._37?t.onFulfilled:t.onRejected;if(null===e)return void(1===n._37?l(t.promise,n._12):a(t.promise,n._12));var r=i(e,n._12);r===w?a(t.promise,d):l(t.promise,r)})}function l(n,t){if(t===n)return a(n,new TypeError("A promise cannot be resolved with itself."));if(t&&("object"==typeof t||"function"==typeof t)){var e=o(t);if(e===w)return a(n,d);if(e===n.then&&t instanceof f)return n._37=3,n._12=t,void h(n);if("function"==typeof e)return void v(e.bind(t),n)}n._37=1,n._12=t,h(n)}function a(n,t){n._37=2,n._12=t,h(n)}function h(n){for(var t=0;t<n._59.length;t++)s(n,n._59[t]);n._59=null}function p(n,t,e){this.onFulfilled="function"==typeof n?n:null,this.onRejected="function"==typeof t?t:null,this.promise=e}function v(n,t){var e=!1,r=u(n,function(n){e||(e=!0,l(t,n))},function(n){e||(e=!0,a(t,n))});e||r!==w||(e=!0,a(t,d))}var y=n("asap/raw"),d=null,w={};t.exports=f,f._99=r,f.prototype.then=function(n,t){if(this.constructor!==f)return c(this,n,t);var e=new f(r);return s(this,new p(n,t,e)),e}},{"asap/raw":4}],2:[function(n,t,e){"use strict";function r(n){var t=new o(o._99);return t._37=1,t._12=n,t}var o=n("./core.js");t.exports=o;var i=r(!0),u=r(!1),f=r(null),c=r(void 0),s=r(0),l=r("");o.resolve=function(n){if(n instanceof o)return n;if(null===n)return f;if(void 0===n)return c;if(n===!0)return i;if(n===!1)return u;if(0===n)return s;if(""===n)return l;if("object"==typeof n||"function"==typeof n)try{var t=n.then;if("function"==typeof t)return new o(t.bind(n))}catch(e){return new o(function(n,t){t(e)})}return r(n)},o.all=function(n){var t=Array.prototype.slice.call(n);return new o(function(n,e){function r(u,f){if(f&&("object"==typeof f||"function"==typeof f)){if(f instanceof o&&f.then===o.prototype.then){for(;3===f._37;)f=f._12;return 1===f._37?r(u,f._12):(2===f._37&&e(f._12),void f.then(function(n){r(u,n)},e))}var c=f.then;if("function"==typeof c){var s=new o(c.bind(f));return void s.then(function(n){r(u,n)},e)}}t[u]=f,0===--i&&n(t)}if(0===t.length)return n([]);for(var i=t.length,u=0;u<t.length;u++)r(u,t[u])})},o.reject=function(n){return new o(function(t,e){e(n)})},o.race=function(n){return new o(function(t,e){n.forEach(function(n){o.resolve(n).then(t,e)})})},o.prototype["catch"]=function(n){return this.then(null,n)}},{"./core.js":1}],3:[function(n,t,e){"use strict";function r(){if(c.length)throw c.shift()}function o(n){var t;t=f.length?f.pop():new i,t.task=n,u(t)}function i(){this.task=null}var u=n("./raw"),f=[],c=[],s=u.makeRequestCallFromTimer(r);t.exports=o,i.prototype.call=function(){try{this.task.call()}catch(n){o.onerror?o.onerror(n):(c.push(n),s())}finally{this.task=null,f[f.length]=this}}},{"./raw":4}],4:[function(n,t,e){(function(n){"use strict";function e(n){f.length||(u(),c=!0),f[f.length]=n}function r(){for(;s<f.length;){var n=s;if(s+=1,f[n].call(),s>l){for(var t=0,e=f.length-s;e>t;t++)f[t]=f[t+s];f.length-=s,s=0}}f.length=0,s=0,c=!1}function o(n){var t=1,e=new a(n),r=document.createTextNode("");return e.observe(r,{characterData:!0}),function(){t=-t,r.data=t}}function i(n){return function(){function t(){clearTimeout(e),clearInterval(r),n()}var e=setTimeout(t,0),r=setInterval(t,50)}}t.exports=e;var u,f=[],c=!1,s=0,l=1024,a=n.MutationObserver||n.WebKitMutationObserver;u="function"==typeof a?o(r):i(r),e.requestFlush=u,e.makeRequestCallFromTimer=i}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],5:[function(n,t,e){"function"!=typeof Promise.prototype.done&&(Promise.prototype.done=function(n,t){var e=arguments.length?this.then.apply(this,arguments):this;e.then(null,function(n){setTimeout(function(){throw n},0)})})},{}],6:[function(n,t,e){n("asap");"undefined"==typeof Promise&&(Promise=n("./lib/core.js"),n("./lib/es6-extensions.js")),n("./polyfill-done.js")},{"./lib/core.js":1,"./lib/es6-extensions.js":2,"./polyfill-done.js":5,asap:3}]},{},[6]);
|
||||||
|
//# sourceMappingURL=/polyfills/promise-7.0.4.min.js.map
|
|
@ -3,6 +3,7 @@
|
||||||
- 目录
|
- 目录
|
||||||
- [获取Cookie](#1获取Cookie)
|
- [获取Cookie](#1获取Cookie)
|
||||||
- [和Js交互操作](#2和Js交互操作)
|
- [和Js交互操作](#2和Js交互操作)
|
||||||
|
- [网页整体截图](#3网页整体截图)
|
||||||
|
|
||||||
## 1、获取Cookie
|
## 1、获取Cookie
|
||||||
[运行 GetCookie.py](GetCookie.py)
|
[运行 GetCookie.py](GetCookie.py)
|
||||||
|
@ -19,3 +20,11 @@
|
||||||
具体看代码中的注释
|
具体看代码中的注释
|
||||||
|
|
||||||
![JsSignals](ScreenShot/JsSignals.gif)
|
![JsSignals](ScreenShot/JsSignals.gif)
|
||||||
|
|
||||||
|
## 3、网页整体截图
|
||||||
|
[运行 ScreenShotPage.py](ScreenShotPage.py)
|
||||||
|
|
||||||
|
1. 方式1:目前通过不完美方法(先调整`QWebEngineView`的大小为`QWebEnginePage`的内容大小,等待一定时间后截图再还原大小)
|
||||||
|
2. 方式2:通过js库`html2canvas`对指定元素截图,得到`base64`编码的数据并调用接口函数传递到py代码中
|
||||||
|
|
||||||
|
![ScreenShotPage](ScreenShot/ScreenShotPage.gif)
|
BIN
QWebEngineView/ScreenShot/ScreenShotPage.gif
Normal file
BIN
QWebEngineView/ScreenShot/ScreenShotPage.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 359 KiB |
198
QWebEngineView/ScreenShotPage.py
Normal file
198
QWebEngineView/ScreenShotPage.py
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Created on 2019年7月8日
|
||||||
|
@author: Irony
|
||||||
|
@site: https://pyqt5.com https://github.com/PyQt5
|
||||||
|
@email: 892768447@qq.com
|
||||||
|
@file: ScreenShotPage
|
||||||
|
@description: 网页整体截图
|
||||||
|
"""
|
||||||
|
import base64
|
||||||
|
import cgitb
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QUrl, Qt, pyqtSlot, QSize, QTimer
|
||||||
|
from PyQt5.QtGui import QImage, QPainter, QIcon, QPixmap
|
||||||
|
from PyQt5.QtWebChannel import QWebChannel
|
||||||
|
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
|
||||||
|
from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QPushButton,\
|
||||||
|
QGroupBox, QLineEdit, QHBoxLayout, QListWidget, QListWidgetItem,\
|
||||||
|
QProgressDialog
|
||||||
|
|
||||||
|
|
||||||
|
__Author__ = "Irony"
|
||||||
|
__Copyright__ = "Copyright (c) 2019"
|
||||||
|
__Version__ = "Version 1.0"
|
||||||
|
|
||||||
|
# 对部分内容进行截图
|
||||||
|
CODE = """
|
||||||
|
var el = $("%s");
|
||||||
|
html2canvas(el[0], {
|
||||||
|
width: el.outerWidth(true),
|
||||||
|
windowWidth: el.outerWidth(true),
|
||||||
|
}).then(function(canvas) {
|
||||||
|
window._self.saveImage(canvas.toDataURL());
|
||||||
|
});
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 创建交互桥梁脚本
|
||||||
|
CreateBridge = """
|
||||||
|
new QWebChannel(qt.webChannelTransport,
|
||||||
|
function(channel) {
|
||||||
|
window._self = channel.objects._self;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class Window(QWidget):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(Window, self).__init__(*args, **kwargs)
|
||||||
|
self.resize(600, 400)
|
||||||
|
layout = QHBoxLayout(self)
|
||||||
|
|
||||||
|
# 左侧
|
||||||
|
widgetLeft = QWidget(self)
|
||||||
|
layoutLeft = QVBoxLayout(widgetLeft)
|
||||||
|
# 右侧
|
||||||
|
self.widgetRight = QListWidget(
|
||||||
|
self, minimumWidth=200, iconSize=QSize(150, 150))
|
||||||
|
self.widgetRight.setViewMode(QListWidget.IconMode)
|
||||||
|
layout.addWidget(widgetLeft)
|
||||||
|
layout.addWidget(self.widgetRight)
|
||||||
|
|
||||||
|
self.webView = QWebEngineView()
|
||||||
|
layoutLeft.addWidget(self.webView)
|
||||||
|
|
||||||
|
# 截图方式一
|
||||||
|
groupBox1 = QGroupBox('截图方式一', self)
|
||||||
|
layout1 = QVBoxLayout(groupBox1)
|
||||||
|
layout1.addWidget(QPushButton('截图1', self, clicked=self.onScreenShot1))
|
||||||
|
layoutLeft.addWidget(groupBox1)
|
||||||
|
|
||||||
|
# 截图方式二(采用js)
|
||||||
|
groupBox2 = QGroupBox('截图方式二', self)
|
||||||
|
layout2 = QVBoxLayout(groupBox2)
|
||||||
|
self.codeEdit = QLineEdit(
|
||||||
|
'body', groupBox2, placeholderText='请输入需要截图的元素、ID或者class:如body、#id .class')
|
||||||
|
layout2.addWidget(self.codeEdit)
|
||||||
|
self.btnMethod2 = QPushButton(
|
||||||
|
'', self, clicked=self.onScreenShot2, enabled=False)
|
||||||
|
layout2.addWidget(self.btnMethod2)
|
||||||
|
layoutLeft.addWidget(groupBox2)
|
||||||
|
|
||||||
|
# 提供访问接口
|
||||||
|
self.channel = QWebChannel(self)
|
||||||
|
# 把自身对象传递进去
|
||||||
|
self.channel.registerObject('_self', self)
|
||||||
|
# 设置交互接口
|
||||||
|
self.webView.page().setWebChannel(self.channel)
|
||||||
|
# 支持截图
|
||||||
|
settings = QWebEngineSettings.globalSettings()
|
||||||
|
settings.setAttribute(QWebEngineSettings.ScreenCaptureEnabled, True)
|
||||||
|
self.webView.loadStarted.connect(self.onLoadStarted)
|
||||||
|
self.webView.loadFinished.connect(self.onLoadFinished)
|
||||||
|
self.webView.load(QUrl("https://pyqt5.com"))
|
||||||
|
|
||||||
|
def onLoadStarted(self):
|
||||||
|
print('load started')
|
||||||
|
self.btnMethod2.setEnabled(False)
|
||||||
|
self.btnMethod2.setText('暂时无法使用(等待页面加载完成)')
|
||||||
|
|
||||||
|
@pyqtSlot(bool)
|
||||||
|
def onLoadFinished(self, finished):
|
||||||
|
if not finished:
|
||||||
|
return
|
||||||
|
print('load finished')
|
||||||
|
# 注入脚本
|
||||||
|
page = self.webView.page()
|
||||||
|
# 执行qwebchannel,jquery,promise,html2canvas
|
||||||
|
page.runJavaScript(
|
||||||
|
open('Data/qwebchannel.js', 'rb').read().decode())
|
||||||
|
page.runJavaScript(
|
||||||
|
open('Data/jquery.js', 'rb').read().decode())
|
||||||
|
# page.runJavaScript(
|
||||||
|
# open('Data/promise-7.0.4.min.js', 'rb').read().decode())
|
||||||
|
page.runJavaScript(
|
||||||
|
open('Data/html2canvas.min.js', 'rb').read().decode())
|
||||||
|
page.runJavaScript(CreateBridge)
|
||||||
|
print('inject js ok')
|
||||||
|
self.btnMethod2.setText('截图2')
|
||||||
|
self.btnMethod2.setEnabled(True)
|
||||||
|
|
||||||
|
def onScreenShot1(self):
|
||||||
|
# 截图方式1
|
||||||
|
page = self.webView.page()
|
||||||
|
oldSize = self.webView.size()
|
||||||
|
self.webView.resize(page.contentsSize().toSize())
|
||||||
|
|
||||||
|
def doScreenShot():
|
||||||
|
rect = self.webView.contentsRect()
|
||||||
|
size = rect.size()
|
||||||
|
image = QImage(size, QImage.Format_ARGB32_Premultiplied)
|
||||||
|
image.fill(Qt.transparent)
|
||||||
|
|
||||||
|
painter = QPainter()
|
||||||
|
painter.begin(image)
|
||||||
|
painter.setRenderHint(QPainter.Antialiasing, True)
|
||||||
|
painter.setRenderHint(QPainter.TextAntialiasing, True)
|
||||||
|
painter.setRenderHint(QPainter.SmoothPixmapTransform, True)
|
||||||
|
|
||||||
|
self.webView.render(painter)
|
||||||
|
painter.end()
|
||||||
|
self.webView.resize(oldSize)
|
||||||
|
|
||||||
|
# 添加到左侧list中
|
||||||
|
item = QListWidgetItem(self.widgetRight)
|
||||||
|
image = QPixmap.fromImage(image)
|
||||||
|
item.setIcon(QIcon(image))
|
||||||
|
item.setData(Qt.UserRole + 1, image)
|
||||||
|
|
||||||
|
# 先等一下再截图吧
|
||||||
|
QTimer.singleShot(2000, doScreenShot)
|
||||||
|
|
||||||
|
def onScreenShot2(self):
|
||||||
|
# 截图方式2
|
||||||
|
code = self.codeEdit.text().strip()
|
||||||
|
if not code:
|
||||||
|
return
|
||||||
|
self.progressdialog = QProgressDialog(self, windowTitle='正在截图中')
|
||||||
|
self.progressdialog.setRange(0, 0)
|
||||||
|
self.webView.page().runJavaScript(CODE % code)
|
||||||
|
self.progressdialog.exec_()
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def saveImage(self, image):
|
||||||
|
self.progressdialog.close()
|
||||||
|
# data:image/png;base64,iVBORw0KG....
|
||||||
|
if not image.startswith('data:image'):
|
||||||
|
return
|
||||||
|
data = base64.b64decode(image.split(';base64,')[1])
|
||||||
|
image = QPixmap()
|
||||||
|
image.loadFromData(data)
|
||||||
|
# 添加到左侧list中
|
||||||
|
item = QListWidgetItem(self.widgetRight)
|
||||||
|
item.setIcon(QIcon(image))
|
||||||
|
item.setData(Qt.UserRole + 1, image)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 开启F12 控制台功能,需要单独通过浏览器打开这个页面
|
||||||
|
# 这里可以做个保护, 发布软件,启动时把这个环境变量删掉。防止他人通过环境变量开启
|
||||||
|
os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = '9966'
|
||||||
|
sys.excepthook = cgitb.enable(1, None, 5, '')
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
w = Window()
|
||||||
|
w.show()
|
||||||
|
|
||||||
|
# 打开调试页面
|
||||||
|
dw = QWebEngineView()
|
||||||
|
dw.setWindowTitle('开发人员工具')
|
||||||
|
dw.load(QUrl('http://127.0.0.1:9966'))
|
||||||
|
dw.show()
|
||||||
|
dw.move(100, 100)
|
||||||
|
sys.exit(app.exec_())
|
|
@ -132,6 +132,7 @@ https://pyqt5.com 社区是专门针对PyQt5学习和提升开设的博客网站
|
||||||
- [QWebEngineView](QWebEngineView)
|
- [QWebEngineView](QWebEngineView)
|
||||||
- [获取Cookie](QWebEngineView/GetCookie.py)
|
- [获取Cookie](QWebEngineView/GetCookie.py)
|
||||||
- [和Js交互操作](QWebEngineView/JsSignals.py)
|
- [和Js交互操作](QWebEngineView/JsSignals.py)
|
||||||
|
- [网页整体截图](QWebEngineView/ScreenShotPage.py)
|
||||||
- [浏览器下载文件](Test/partner_625781186/6.QWebEngineView下载文件)
|
- [浏览器下载文件](Test/partner_625781186/6.QWebEngineView下载文件)
|
||||||
- [打印网页](Test/partner_625781186/17_打印预览qwebengineview)
|
- [打印网页](Test/partner_625781186/17_打印预览qwebengineview)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue