从 ElementHandle 获取 属性

getting property from ElementHandle

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

目前我使用:

    // 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();

有什么办法可以做到不那么冗长吗?

我更喜欢使用 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
})

使用 async/await 语法

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

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

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

使用:

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

我宁愿为缺少的方法扩展 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" ) );

我的方式

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()
   ]);
}

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

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

Is there any way to do this not as verbose?

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

现在在您的特定情况下,您不仅要在整个页面上而且要在 ElementHandle 上执行 $eval,这是可能的,因为 May 9, 2018 via elementHandle.$eval():

This method runs document.querySelector within the element and passes it as the first argument to pageFunction.

这转化为您的示例如下(此处使用 css select 或代替 xpath):

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