场景
- 昨天试了一下爬取根据网页查询参数的不同而变化的页面,今天来试试爬取单页面应用,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对于爬取这种以前很难爬取的单页面应用来说,确实提供了不少便利。