场景
- 昨天试了一下爬取根据网页查询参数的不同而变化的页面,今天来试试爬取单页面应用,url不发生变化,只是页面内的按钮点击导致数据的重新请求。
主要实现思路
- 利用Puppeteer可以模拟用户点击操作,等待接口返回等各种优秀的API,可以保证在数据结束后完成页面数据提取。
代码实现,以开源众包的页面为例
- 开源众包这个页面挺适合用来做示例,因为通过下一页的按钮去调用ajax请求,当到达最后一页时,下一页按钮会自动有一个disabled属性,我们就可以根据这个disabled属性来判断是否还有下一页。
const common = async (workFunc) => {
const startTime = +new Date();
console.log(`进入方法`);
const browser = await puppeteer.launch();
const page = await browser.newPage();
typeof workFunc === 'function' && await workFunc(page);
await page.close();
await browser.close();
console.log('方法结束,耗费时长:', +new Date() - startTime);
};
const crawler = async (url, selectors) => {
const list = [];
await common(async (page) => {
await page.goto(url);
async function runOnce() {
const result = await page.evaluate((selectors) => {
const res = [];
selectors.forEach(selector => {
const { key, value, field } = selector;
const domList = document.querySelectorAll(value);
Array.prototype.slice.apply(domList).forEach((dom, index) => {
const newVal = dom[field] || dom.innerText;
res[index] = res[index] || {};
res[index][key] = newVal;
})
})
return res;
}, selectors);
list.push(...result);
const disabled = await page.$eval('.btn-next', el => el.disabled);
console.log('一页数据已获取,当前下一页按钮状态:', result[0], disabled);
if (!disabled) {
// 触发按钮点击,使用page.click就是不能触发按钮的点击,只能用这个骚操作
await page.$eval('.btn-next', el => el.click());
// 等待接口完成
await page.waitForResponse(response => {
return response.url().includes('contractor-browse-project-and-reward') && response.status() === 200;
})
await runOnce();
}
}
await runOnce();
})
return list;
}
// 使用
const url = 'https://zb.oschina.net/projects/list.html';
const selectors = [
{
key: 'title',
value: '.el-row .title',
field: 'innerText'
},
{
key: 'tags',
value: '.el-row .tags',
field: 'innerText'
},
{
key: 'money',
value: '.el-row .money',
field: 'innerText'
},
{
key: 'skills',
value: '.el-row .skills',
field: 'innerText'
},
{
key: 'bidding',
value: '.el-row .bidding',
field: 'innerText'
},
{
key: 'pubtime',
value: '.el-row .pubtime',
field: 'innerText'
}
];
crawler(url, selectors).then(result => {
console.log(result);
fs.writeFile('项目.json', JSON.stringify(result), 'utf-8', err => {
if(err) {
console.log(err);
return;
}
});
})
结果展示
-
小结
- 不得不承认,Puppeteer对于爬取这种以前很难爬取的单页面应用来说,确实提供了不少便利。