puppeteer 爬取网易云网站问题 - CNode技术社区

puppeteer 爬取网易云网站问题
发布于 7 年前 作者 zhongshiji 5427 次浏览 来自 问答

转自 @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_SELECTORnull, 导致往后运行不下去,请问是什么原因?

12 回复

@所有人

试试不用它的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的文档并没有看见这个东西。

@zhongshiji

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 好的,谢谢,查到了。

回到顶部

AltStyle によって変換されたページ (->オリジナル) /