前言
基于上一篇文章,我本来想着按照正常的开发流程,一定是加载首页的列表,通过传递一个id号,然后跳转到新的页面。发起请求,来查询详情页面的内容,然后。。。。。。但是我看了一眼发现,他们家的网站不按常理出牌,他们是直接在 a 标签里面 通过 href 跳到他们已经写好的页面了。好了我们我们开始分析先。
一、先分析列表数据
首先我们可以从puppteer 的 文档知道 有一个 _page.click()_ 的方法,然后执行点击后发现,的确是进到详情页面了。这时候我才发现,他们跳进来新页面是不传参数的,我们也没法实现拦截http获取相应信息的,然而问题来了,我们怎么去获取到新页面的信息。(本人学艺不精,恳请大神为我解惑。)所以我决定按照之前的思路。循环去通过 _page.goto() _来加载新页面。
不难发现他们的 a标签 都有一个 href="/xxx/xxx.html",所以我们在爬取数据得时候,可以给他们拼接一个域名前缀上去,因为是我爬取的是“第一财经”这个网站,所以根据需求,各自分析吧。
我临时写个地址去赋值了。
二、循环遍历打开页面爬数据
依旧,我们还是通过自执行函数执行
; (async () => {
let allResult = []
for (let i = 0; i < result.length; i++) {
let detailInfo = '';// 文章详情
await page.goto(result[i].detailPage, {
waitUntil: 'networkidle2'
});
}
})();
按照老思路,我们先获取到对应的元素节点。这里就要分析一下了,我发现他们详情页数据有的是视频,有的是文章,甚至有的是单张图片。这边暂时获取到文章就行,其他有需要的可以自行研究。
// 先判断元素是否存在
const element = await page.$(".m-text");
if (element) {
await page.waitForSelector('.m-text')
}
var obj = await page.evaluate((result, i) => {
let obj = {}
let $ = window.$;
var pageDom = $('#multi-text')
if (pageDom) {
detailInfo = pageDom[0].innerHTML // 我这边直接拿到html的标签+内容
}
result[i].detailInfo = detailInfo
return obj = result[i]
}, result, i)
特别要注意的一点
在page.evaluate(()=>{}) 这个里面打的 log 在我们的编辑工具是看不到的,这个是运行在我们浏览器的,所以这里打印的日志是只能在浏览器调试模式下查看。
三、把我们获取的数据push到对应的数组中。
allResult.push(obj)
await page.waitFor(10000); // 这个时间我是为了观察日志的,给时间爬取数据
await page.goBack(); // 这边是回退到前一页,意思就是我们进去新页面爬取数据完成后,关闭当前页面,再打开一个新的页面继续爬取
最后完成后把我们的浏览器关闭
page.close();
四、完整代码展示(有机会再考虑存mysql或是 mongodb)
const puppeteer = require('puppeteer')
const baseUrl = 'https://www.yicai.com';
// 等待3000毫秒
const sleep = time => new Promise(resolve => {
setTimeout(resolve, time);
})
; (async () => {
const browser = await puppeteer.launch({
headless: false, //浏览器界面启动
slowMo: 200, //放慢浏览器执行速度,方便测试观察
args: ['--no-sandbox'],
dumpio: false,
devtools: true, //开发模式
});
const page = await browser.newPage();
await page.goto(baseUrl, {
waitUntil: 'networkidle2'
});
await page.waitFor(2000); // 等待某个节点的加载
await page.waitForSelector('.u-btn')
for (let index = 0; index < 1; index++) {
await page.waitFor(2000);
await page.click('.u-btn')
}
const result = await page.evaluate(() => {
let apiUrl = 'https://www.yicai.com';
let $ = window.$;
// var items = $('.m-con a') // 不可用这个,由于页面是通过display:none or block来控制的,爬取的数据会是多个页面的叠加
var items = $('#headlist').children('a')
var links = []
if (items.length >= 1) {
items.each((index, item) => {
let it = $(item)
let articleTitle = it.find('h2').text()
let articleIntroduction = it.find('p').text()
let imageAddress = it.find('img').attr('src')
let createdTime = it.find('span').text()
let detailPage = it.attr('href')
links.push({
articleTitle,
articleIntroduction,
imageAddress,
createdTime,
detailPage: apiUrl + detailPage
})
})
}
return links
})
console.log(result);
// 暂缓1s
await sleep(1000)
; (async () => {
let allResult = []
for (let i = 0; i < result.length; i++) {
let detailInfo = '';// 文章详情
await page.goto(result[i].detailPage, {
waitUntil: 'networkidle2'
});
// 先判断元素是否存在
const element = await page.$(".m-text");
if (element) {
await page.waitForSelector('.m-text')
}
var obj = await page.evaluate((result, i) => {
let obj = {}
let $ = window.$;
var pageDom = $('#multi-text')
if (pageDom) {
detailInfo = pageDom[0].innerHTML
}
result[i].detailInfo = detailInfo
return obj = result[i]
}, result, i)
allResult.push(obj)
await page.waitFor(10000);
await page.goBack();
}
await page.close(); })();
})();
五、寻求大神指点
可能对这puppetter认识不够充分,觉得在爬取过程中,不太流畅。希望有大牛指点指点。