【问题标题】:Is there any way of getting XPATH of the page in Pyqt5 in browser?有没有办法在浏览器中获取 Pyqt5 中页面的 XPATH?
【发布时间】:2020-03-02 15:59:24
【问题描述】:

我正在使用 pyqt5。我希望用户单击嵌入在我的 pyqt5 应用程序中的浏览器以获取他/她单击它的元素的 XPATH。

知道它是如何完成的,或者是否可行?

【问题讨论】:

    标签: python python-3.x pyqt pyqt5


    【解决方案1】:

    为了回答这个问题,我使用了以下答案:

    加入所有这些部分,您将获得以下解决方案:

    ├── main.py
    └── xpath_from_element.js
    

    ma​​in.py

    import os
    
    from PyQt5 import QtCore, QtWidgets, QtWebEngineWidgets, QtWebChannel
    
    from jinja2 import Template
    
    CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
    
    
    class Element(QtCore.QObject):
        def __init__(self, name, parent=None):
            super(Element, self).__init__(parent)
            self._name = name
    
        @property
        def name(self):
            return self._name
    
        def script(self):
            return ""
    
    
    class WebEnginePage(QtWebEngineWidgets.QWebEnginePage):
        def __init__(self, parent=None):
            super(WebEnginePage, self).__init__(parent)
            self.loadFinished.connect(self.onLoadFinished)
            self._objects = []
            self._scripts = []
    
        def add_object(self, obj):
            self._objects.append(obj)
    
        @QtCore.pyqtSlot(bool)
        def onLoadFinished(self, ok):
            print("Finished loading: ", ok)
            if ok:
                self.load_qwebchannel()
                self.add_objects()
    
        def load_qwebchannel(self):
            file = QtCore.QFile(":/qtwebchannel/qwebchannel.js")
            if file.open(QtCore.QIODevice.ReadOnly):
                content = file.readAll()
                file.close()
                self.runJavaScript(content.data().decode())
            if self.webChannel() is None:
                channel = QtWebChannel.QWebChannel(self)
                self.setWebChannel(channel)
    
        def add_objects(self):
            if self.webChannel() is not None:
                objects = {obj.name: obj for obj in self._objects}
                self.webChannel().registerObjects(objects)
                _script = """
                {% for obj in objects %}
                var {{obj}};
                {% endfor %}
                new QWebChannel(qt.webChannelTransport, function (channel) {
                {% for obj in objects %}
                    {{obj}} = channel.objects.{{obj}};
                {% endfor %}
                }); 
                """
                self.runJavaScript(Template(_script).render(objects=objects.keys()))
                for obj in self._objects:
                    if isinstance(obj, Element):
                        self.runJavaScript(obj.script())
    
    
    class Helper(Element):
        xpathClicked = QtCore.pyqtSignal(str)
    
        def script(self):
            js = ""
            file = QtCore.QFile(os.path.join(CURRENT_DIR, "xpath_from_element.js"))
            if file.open(QtCore.QIODevice.ReadOnly):
                content = file.readAll()
                file.close()
                js = content.data().decode()
    
            js += """
            document.addEventListener('click', function(e) {
                e = e || window.event;
                var target = e.target || e.srcElement;
                var xpath = Elements.DOMPath.xPath(target, false); 
                {{name}}.receive_xpath(xpath);
            }, false);"""
            return Template(js).render(name=self.name)
    
        @QtCore.pyqtSlot(str)
        def receive_xpath(self, xpath):
            self.xpathClicked.emit(xpath)
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
    
        xpath_helper = Helper("xpath_helper")
        xpath_helper.xpathClicked.connect(lambda xpath: print("clicked", xpath))
        view = QtWebEngineWidgets.QWebEngineView()
        page = WebEnginePage()
        page.add_object(xpath_helper)
        view.setPage(page)
        view.load(QtCore.QUrl("https://www.qt.io"))
        view.show()
        sys.exit(app.exec_())
    

    xpath_from_element.js

    // Copyright 2018 The Chromium Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    
    Elements = {};
    Elements.DOMPath = {};
    
    /**
     * @param {!Node} node
     * @param {boolean=} optimized
     * @return {string}
     */
    Elements.DOMPath.xPath = function (node, optimized) {
        if (node.nodeType === Node.DOCUMENT_NODE) {
            return '/';
        }
    
        const steps = [];
        let contextNode = node;
        while (contextNode) {
            const step = Elements.DOMPath._xPathValue(contextNode, optimized);
            if (!step) {
                break;
            }  // Error - bail out early.
            steps.push(step);
            if (step.optimized) {
                break;
            }
            contextNode = contextNode.parentNode;
        }
    
        steps.reverse();
        return (steps.length && steps[0].optimized ? '' : '/') + steps.join('/');
    };
    
    /**
     * @param {!Node} node
     * @param {boolean=} optimized
     * @return {?Elements.DOMPath.Step}
     */
    Elements.DOMPath._xPathValue = function (node, optimized) {
        let ownValue;
        const ownIndex = Elements.DOMPath._xPathIndex(node);
        if (ownIndex === -1) {
            return null;
        }  // Error.
    
        switch (node.nodeType) {
            case Node.ELEMENT_NODE:
                if (optimized && node.getAttribute('id')) {
                    return new Elements.DOMPath.Step('//*[@id="' + node.getAttribute('id') + '"]', true);
                }
                ownValue = node.localName;
                break;
            case Node.ATTRIBUTE_NODE:
                ownValue = '@' + node.nodeName;
                break;
            case Node.TEXT_NODE:
            case Node.CDATA_SECTION_NODE:
                ownValue = 'text()';
                break;
            case Node.PROCESSING_INSTRUCTION_NODE:
                ownValue = 'processing-instruction()';
                break;
            case Node.COMMENT_NODE:
                ownValue = 'comment()';
                break;
            case Node.DOCUMENT_NODE:
                ownValue = '';
                break;
            default:
                ownValue = '';
                break;
        }
    
        if (ownIndex > 0) {
            ownValue += '[' + ownIndex + ']';
        }
    
        return new Elements.DOMPath.Step(ownValue, node.nodeType === Node.DOCUMENT_NODE);
    };
    
    /**
     * @param {!Node} node
     * @return {number}
     */
    Elements.DOMPath._xPathIndex = function (node) {
        // Returns -1 in case of error, 0 if no siblings matching the same expression,
        // <XPath index among the same expression-matching sibling nodes> otherwise.
        function areNodesSimilar(left, right) {
            if (left === right) {
                return true;
            }
    
            if (left.nodeType === Node.ELEMENT_NODE && right.nodeType === Node.ELEMENT_NODE) {
                return left.localName === right.localName;
            }
    
            if (left.nodeType === right.nodeType) {
                return true;
            }
    
            // XPath treats CDATA as text nodes.
            const leftType = left.nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : left.nodeType;
            const rightType = right.nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : right.nodeType;
            return leftType === rightType;
        }
    
        const siblings = node.parentNode ? node.parentNode.children : null;
        if (!siblings) {
            return 0;
        }  // Root node - no siblings.
        let hasSameNamedElements;
        for (let i = 0; i < siblings.length; ++i) {
            if (areNodesSimilar(node, siblings[i]) && siblings[i] !== node) {
                hasSameNamedElements = true;
                break;
            }
        }
        if (!hasSameNamedElements) {
            return 0;
        }
        let ownIndex = 1;  // XPath indices start with 1.
        for (let i = 0; i < siblings.length; ++i) {
            if (areNodesSimilar(node, siblings[i])) {
                if (siblings[i] === node) {
                    return ownIndex;
                }
                ++ownIndex;
            }
        }
        return -1;  // An error occurred: |node| not found in parent's children.
    };
    
    /**
     * @unrestricted
     */
    Elements.DOMPath.Step = class {
        /**
         * @param {string} value
         * @param {boolean} optimized
         */
        constructor(value, optimized) {
            this.value = value;
            this.optimized = optimized || false;
        }
    
        /**
         * @override
         * @return {string}
         */
        toString() {
            return this.value;
        }
    };
    

    【讨论】:

    • 如果我希望用户单击元素,但不允许他通过单击按钮走得更远并离开原始页面,该怎么办。简而言之,我只想停留在同一页面上而不重定向到另一个页面。 @eyllansec
    • @AndrewWhiteman mmm,这部分依赖于 javascript 并且超出了问题范围,因为您想要一个特定的功能,所以我建议您在这个方向找到一个解决方案。
    • 那我应该问另一个问题吗? @eyllansec
    猜你喜欢
    • 2020-06-16
    • 1970-01-01
    • 1970-01-01
    • 2014-10-26
    • 2021-08-21
    • 2011-03-03
    • 1970-01-01
    • 2010-12-01
    • 2020-05-20
    相关资源
    最近更新 更多