【问题标题】:getting property from ElementHandle从 ElementHandle 获取属性
【发布时间】:2021-01-12 00:45:57
【问题描述】:

我在 Node.js 模块中使用 Puppeteer。我使用 XPath 选择器检索 HTML 元素,需要提取 text 属性。

目前我使用:

    // Get the element
    let ele = await element.$x(`//div[@class="g"][${i}]/div/div/h3/a`);

    // Get the text property
    const title = await(await ele[0].getProperty('text')).jsonValue();

有什么方法可以做到这一点而不至于如此冗长?

【问题讨论】:

  • 我完全同意这种获取 text 属性的方式非常丑陋。

标签: node.js puppeteer


【解决方案1】:

...或者编写一个小辅助函数。

public async GetProperty(element: ElementHandle, property: string): Promise<string> {
    return await (await element.getProperty(property)).jsonValue();
}

使用:

let inner = await GetProperty(ele, 'innerHTML');

【讨论】:

    【解决方案2】:

    我宁愿为缺少的方法扩展 ElementHandle,例如:

    //  puppeteer@1.9.0
    let { ElementHandle } = require( "puppeteer/lib/ExecutionContext" );
    // puppeteer@1.12 
    if ( ElementHandle === undefined ) {
      ElementHandle = require( "puppeteer/lib/JSHandle" ).ElementHandle;
    }
    
    /**
     * Set value on a select element
     * @param {string} value
     * @returns {Promise<Undefined>}
     */
    ElementHandle.prototype.select = async function( value ) {
      await this._page.evaluateHandle( ( el, value ) => {
          const event = new Event( "change", { bubbles: true });
          event.simulated = true;
          el.querySelector( `option[value="${ value }"]` ).selected = true;
          el.dispatchEvent( event );
      }, this, value );
    };
    
    /**
     * Check if element is visible in the DOM
     * @returns {Promise<Boolean>}
     **/
    ElementHandle.prototype.isVisible = async function(){
      return (await this.boundingBox() !== null);
    };
    
    /**
     * Get element attribute
     * @param {string} attr
     * @returns {Promise<String>}
     */
    ElementHandle.prototype.getAttr = async function( attr ){
      const handle = await this._page.evaluateHandle( ( el, attr ) => el.getAttribute( attr ), this, attr );
      return await handle.jsonValue();
    };
    
    /**
     * Get element property
     * @param {string} prop
     * @returns {Promise<String>}
     */
    ElementHandle.prototype.getProp = async function( prop ){
      const handle = await this._page.evaluateHandle( ( el, prop ) => el[ prop ], this, prop );
      return await handle.jsonValue();
    };
    

    只要在代码中导入此模块一次,您就可以按如下方式使用句柄:

    const elh = await page.$( `#testTarget` );
    console.log( await elh.isVisible() );
    console.log( await elh.getAttr( "class" ) );
    console.log( await elh.getProp( "innerHTML" ) );
    

    【讨论】:

    • 我需要将第一行更改为 const ElementHandle = require( "puppeteer/lib/ElementHandle" ).ElementHandle;用于节点 v8 和最新的 puppeteer
    • 哇!!那个好漂亮。非常感谢您的精彩回答。我不明白,人们会想象 ElementHandle 和 JSHandle 对象有更简单的 API 可以使用它们!我不知道,也许这对他们来说是可行的。
    • 这对我有用,但它破坏了我的代码库的其他部分,这些部分也依赖于 Puppeteer.. :( 一旦我包含这个,我就会收到错误 const ElementHandle = require( "puppeteer/lib/cjs/puppeteer/common/JSHandle" ).ElementHandle;
    【解决方案3】:

    在接受的答案中提到了page.eval(),但是,对于puppeteer,这种方法从未存在过,我认为真正的意思实际上是page.evaluate()

    但是,使用page.evaluate() 需要您将操作分成两部分(一是获取元素,一是选择值)。

    有什么方法可以不那么冗长吗?

    在这种情况下,page.$eval() 似乎更合适,因为它允许您直接将选择器作为参数传递,从而减少需要引入的操作或变量的数量:

    现在,在您的特定情况下,您不仅希望在整个页面上执行$eval,而且还希望在ElementHandle 上执行,因为May 9, 2018 可以通过elementHandle.$eval() 执行:

    此方法在元素内运行 document.querySelector 并将其作为第一个参数传递给 pageFunction。

    这转化为您的示例如下(这里使用 css 选择器而不是 xpath):

    await elementHandle.$eval('/div/div/h3/a', el => el.text);
    

    【讨论】:

    • @luwes,我没有编写选择器,我只是从 OP 中获取它以匹配 OP 中提供的示例。我回答的重点不是选择器,而是提到$eval 因此,请考虑删除您的反对票。
    • 我会删除反对票,但现在似乎已锁定,对此感到抱歉。只是我尝试了上面的代码,但它不起作用,只是想让其他人知道代码不正确,因为 xpath 与元素/css 选择器不同。
    • 嗨@luwes,好吧,不用担心,您对xpath 的评论实际上是正确的,因此我将示例代码更改为css 选择器。我认为现在删除反对票应该在编辑后起作用。如果没有,没关系:)
    【解决方案4】:

    我更喜欢使用eval() 函数,这样我就可以使用更少冗长的代码:

    page.eval(() => {
    
        let element = document.querySelector('#mySelector')
        return element.innerText
    
    }).then(text => {
        console.log(text)
    })
    

    您还可以传递您之前抓取的元素,例如 ele var:

    使用 Promise 语法

    page.eval(element => {
        return element.innerText
    }, ele).then(text => {
        // Do whatever you want with text
    })
    

    使用异步/等待语法

    const text = await page.eval(element => element.innerText), ele) 
    // Do whatever you want with text
    

    【讨论】:

    • TypeError: page.eval is not a function。你的意思是page.evaluate()?您可以直接在句柄上调用evaluatesomeHandle.evaluate(el =&gt; el.innerText),以避免将其作为第二个参数传递。
    【解决方案5】:

    我的方式

    async function getVisibleHandle(selector, page) {
    
        const elements = await page.$$(selector);
    
        let hasVisibleElement = false,
            visibleElement = '';
    
        if (!elements.length) {
            return [hasVisibleElement, visibleElement];
        }
    
        let i = 0;
        for (let element of elements) {
            const isVisibleHandle = await page.evaluateHandle((e) => {
                const style = window.getComputedStyle(e);
                return (style && style.display !== 'none' &&
                    style.visibility !== 'hidden' && style.opacity !== '0');
            }, element);
            var visible = await isVisibleHandle.jsonValue();
            const box = await element.boxModel();
            if (visible && box) {
                hasVisibleElement = true;
                visibleElement = elements[i];
                break;
            }
            i++;
        }
    
        return [hasVisibleElement, visibleElement];
    }
    

    用法

    let selector = "a[href='https://example.com/']";
    
    let visibleHandle = await getVisibleHandle(selector, page);
    
    if (visibleHandle[1]) {
    
       await Promise.all([
         visibleHandle[1].click(),
         page.waitForNavigation()
       ]);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-09-10
      • 1970-01-01
      • 1970-01-01
      • 2012-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多