转自 @huyiyang 的代码: 获取网易云的歌词和评论
const fs = require('fs');
const puppeteer = require('puppeteer');
(async () => {
const browser = await (puppeteer.launch({ executablePath: '/Users/huqiyang/Documents/project/z/chromium/Chromium.app/Contents/MacOS/Chromium', headless: false }));
const page = await browser.newPage();
// 进入页面
await page.goto('https://music.163.com/#');
// 点击搜索框拟人输入 鬼才会想起
const musicName = '鬼才会想';
await page.type('.txt.j-flag', musicName, {delay: 0});
// 回车
await page.keyboard.press('Enter');
// 获取歌曲列表的 iframe
await page.waitFor(2000);
let iframe = await page.frames().find(f => f.name() === 'contentFrame');
const SONG_LS_SELECTOR = await iframe.$('.srchsongst');
// 获取歌曲 鬼才会想起 的地址
const selectedSongHref = await iframe.evaluate(e => {
const songList = Array.from(e.childNodes);
const idx = songList.findIndex(v => v.childNodes[1].innerText.replace(/\s/g, '') === '鬼才会想起');
return songList[idx].childNodes[1].firstChild.firstChild.firstChild.href;
}, SONG_LS_SELECTOR);
// 进入歌曲页面
await page.goto(selectedSongHref);
// 获取歌曲页面嵌套的 iframe
await page.waitFor(2000);
iframe = await page.frames().find(f => f.name() === 'contentFrame');
// 点击 展开按钮
const unfoldButton = await iframe.$('#flag_ctrl');
await unfoldButton.click();
// 获取歌词
const LYRIC_SELECTOR = await iframe.$('#lyric-content');
const lyricCtn = await iframe.evaluate(e => {
return e.innerText;
}, LYRIC_SELECTOR);
console.log(lyricCtn);
// 截图
await page.screenshot({
path: '歌曲.png',
fullPage: true,
});
// 写入文件
let writerStream = fs.createWriteStream('歌词.txt');
writerStream.write(lyricCtn, 'UTF8');
writerStream.end();
// 获取评论数量
const commentCount = await iframe.$eval('.sub.s-fc3', e => e.innerText);
console.log(commentCount);
// 获取评论
const commentList = await iframe.$$eval('.itm', elements => {
const ctn = elements.map(v => {
return v.innerText.replace(/\s/g, '');
});
return ctn;
});
console.log(commentList);
})();
在18-21行,运行到:
// 获取歌曲列表的 iframe
await page.waitFor(2000);
let iframe = await page.frames().find(f => f.name() === 'contentFrame');
const SONG_LS_SELECTOR = await iframe.$('.srchsongst');
console.log('SONG_LS_SELECTOR') // null
输出 SONG_LS_SELECTOR 为 null, 导致往后运行不下去,请问是什么原因?
@所有人
试试不用它的api, 自己在dom里找iframe呢?
const SONG_LS_SELECTOR = await page.evaluate(() => {
let iframe = document.querySelector('iframe#g_iframe');
if(!iframe) return null;
return iframe.contentWindow.document.querySelector('.srchsongst');
})
@icheer 这样也是null呢。 而且我这样:
// 获取歌曲列表的 iframe
await page.waitFor(2000);
let iframe = await page.frames().find(f => f.name() === 'contentFrame');
// const SONG_LS_SELECTOR = await iframe.$('.srchsongst');
const SONG_LS_SELECTOR = await page.evaluate(() => {
let iframe = document.querySelector('iframe#g_iframe');
console.log('这里'); // 没有输出'这里'
if(!iframe) return null;
return iframe.contentWindow.document.querySelector('.srchsongst');
});
console.log(SONG_LS_SELECTOR); // null
// =======================================================================
只是输出了一个null。
在page.evaluate() 内部的console.log, 是会打印到chromium浏览器的控制台上, 而不会打印到你的终端或powershell里
另外querySelector(‘iframe#g_iframe’) 这里写的#g_iframe是我观察网易云音乐里有这样一个iframe的id, 你还需要验证一下, 你要抓的是不是这个id
iframe 还没加载完吧。加个 on(‘load’) 或者 waitForSelector 呢?
楼上说的对, 另外, 才发现你demo中这一行:
await page.goto('https://music.163.com/#');
不等页面加载完就去操作dom, 输入歌名了…
需要改为:
await page.goto('https://music.163.com/#', { waitUntil: 'networkidle2' });
的确是因为iframe还没有加载完,原代码只是单纯
await page.waitFor(2000);
网速的关系。谢谢两位大佬 @icheer @zhuweiyou 另外请问下怎么 **加个 on(‘load’) ** 呢?在puppeteer的文档并没有看见这个东西。
await page.goto('https://music.163.com/#', { waitUntil: 'networkidle2' });
这里的waitUntil: ‘networkidle2’ 就是做这件事的, 一直等…直到页面不再有网络流量为止
如果一定要写onload, 可以在page.evaluate()里面写… 我觉得可以把evaluate当成是一场梦, 你不能传入任何参数(非得传的话, 可以通过goto, 在url里用hash传递一些信息到梦里), 只能用return传出你想要的信息, 梦醒后(evaluate执行完了), 你只记得梦中return出的信息
@icheer page.evaluate()真是个神奇的方法… 我觉得:
await page.waitFor(2000);
这太蠢了,也容易出错,于是我想用Frame.waitForSelector()方法去等待,所以我这样写:
// await page.waitFor(2000);
await page.waitForSelector('#g_iframe.srchsongst', {
visible: true
});
但是它报了超时的错,设了 timeout: 0 的话就会一直等待下去。 请问用Frame.waitForSelector或者page.waitForSelector去 等待iframe里某个选择器加载完毕 的正确姿势是什么呢?
@zhongshiji 文档有啊,可以直接 .on(‘load’)
@zhuweiyou 好的,谢谢,查到了。