node爬虫入门

本教程仅用于学习,不要用于商业。以往通常使用请求获取页面(request、superagent…)+操作网页提取需要的数据(cheerio)的方式来写爬虫,现在已经基本被废掉了,因为很多网站都是通过异步请求获取数据然后渲染页面,这样使我们请求获取的页面不是最终展示的页面,怎么处理这个问题呢?接下来跟着我来学习一下。

案例

爬取掘金首页前端页面前10篇文章

环境

  • 谷歌浏览器
  • node

核心类库

puppeteer

文档

http://www.puppeteerjs.com/

安装

npm i puppeteer -S

第一步获取网页内容

const puppeteer = require('puppeteer');

puppeteer.launch({
  // 在导航期间忽略 HTTPS 错误
	ignoreHTTPSErrors: true,
  // 不在 无头模式 下运行浏览器
	headless: false,
  // 将 Puppeteer 操作减少指定的毫秒数
  slowMo: 250,
  // 等待浏览器实例启动的最长时间,0禁用超时
	timeout: 0
}).then(async browser => {
  // 一个新的 Page 对象
	let page = await browser.newPage();
  // 启用js
    await page.setJavaScriptEnabled(true);
  // 打开URL
	await page.goto("https://juejin.cn/frontend");
    console.log(await page.content())
  // 关闭页面
	await page.close();
  // 关闭浏览器
    await browser.close();
})

上面代码可以获取到整个页面,测试发现已经解决了问题,其实puppeteer有专门解决这个问题的方法page.waitForSelector等待指定的选择器匹配的元素出现在页面中。

const puppeteer = require('puppeteer');

puppeteer.launch({
  // 在导航期间忽略 HTTPS 错误
	ignoreHTTPSErrors: true,
  // 不在 无头模式 下运行浏览器
	headless: false,
  // 将 Puppeteer 操作减少指定的毫秒数
  slowMo: 250,
  // 等待浏览器实例启动的最长时间,0禁用超时
	timeout: 0
}).then(async browser => {
  // 一个新的 Page 对象
	let page = await browser.newPage();
  // 启用js
    await page.setJavaScriptEnabled(true);
  // 打开URL
	await page.goto("https://juejin.cn/frontend");
  // 等待列表出现
    await page.waitForSelector('.entry-list-wrap .entry-list .item');
    console.log(await page.content())
  // 关闭页面
	await page.close();
  // 关闭浏览器
    await browser.close();
})

第二步获取列表中标题和链接

const puppeteer = require('puppeteer');

puppeteer.launch({
  // 在导航期间忽略 HTTPS 错误
	ignoreHTTPSErrors: true,
  // 不在 无头模式 下运行浏览器
	headless: false,
  // 将 Puppeteer 操作减少指定的毫秒数
    slowMo: 250,
  // 等待浏览器实例启动的最长时间,0禁用超时
	timeout: 0
}).then(async browser => {
  // 一个新的 Page 对象
	let page = await browser.newPage();
  // 启用js
    await page.setJavaScriptEnabled(true);
  // 打开URL
	await page.goto("https://juejin.cn/frontend");
  // 等待列表出现
    await page.waitForSelector('.entry-list-wrap .entry-list .item');
  // 获取列表中标题和链接
    const list = await page.$$eval('.entry-list-wrap .entry-list .title-row a', list => {
        return list.map(item => {
			return {
                title: item.innerText,
                href: item.href,
            }
		});
	});
    
    console.log(list.length)
    console.log(list)
  // 关闭页面
	await page.close();
  // 关闭浏览器
    await browser.close();
})

第三步获取文章详情

const puppeteer = require('puppeteer');

puppeteer.launch({
  // 在导航期间忽略 HTTPS 错误
	ignoreHTTPSErrors: true,
  // 不在 无头模式 下运行浏览器
	headless: false,
  // 将 Puppeteer 操作减少指定的毫秒数
 	 slowMo: 250,
  // 等待浏览器实例启动的最长时间,0禁用超时
	timeout: 0
}).then(async browser => {
  // 一个新的 Page 对象
	let page = await browser.newPage();
  // 启用js
  	await page.setJavaScriptEnabled(true);
  // 打开URL
	await page.goto("https://juejin.cn/frontend");
  // 等待列表出现
  	await page.waitForSelector('.entry-list-wrap .entry-list .item');
  // 获取列表中标题和链接
  	let list = await page.$$eval('.entry-list-wrap .entry-list .title-row a', list => {
    	return list.map(item => {
			return {
                title: item.innerText,
                href: item.href,
              }
		});
	});
  	list = list.slice(0, 10)
  // 获取文章详情
    for (let i = 0; i < list.length; i++) {
    	await page.goto(list[i].href);
    	list[i].content = await page.$eval('.article-content', e => e.innerHTML)
  	}
  	console.log(list[0])
  // 关闭页面
	await page.close();
  // 关闭浏览器
  	await browser.close();
})

第四步保存数据

此处不介绍存到数据库,只是简单的写一个json文件。

const puppeteer = require('puppeteer');
const fs = require('fs')
const path = require('path')
puppeteer.launch({
  // 在导航期间忽略 HTTPS 错误
	ignoreHTTPSErrors: true,
  // 不在 无头模式 下运行浏览器
	headless: false,
  // 将 Puppeteer 操作减少指定的毫秒数
  	slowMo: 250,
  // 等待浏览器实例启动的最长时间,0禁用超时
	timeout: 0
}).then(async browser => {
  // 一个新的 Page 对象
	let page = await browser.newPage();
  // 启用js
  	await page.setJavaScriptEnabled(true);
  // 打开URL
	await page.goto("https://juejin.cn/frontend");
  // 等待列表出现
  	await page.waitForSelector('.entry-list-wrap .entry-list .item');
  // 获取列表中标题和链接
  	let list = await page.$$eval('.entry-list-wrap .entry-list .title-row a', list => {
        return list.map(item => {
            return {
                title: item.innerText,
                href: item.href,
            }
		});
	});
  	list = list.slice(0, 10)
  // 获取文章详情
  	for (let i = 0; i < list.length; i++) {
        await page.goto(list[i].href);
        list[i].content = await page.$eval('.article-content', e => e.innerHTML)
    }
  // 保存数据
  	fs.writeFileSync(path.resolve(__dirname, '../out/articles.json'), JSON.stringify(list, null, 2))
  // 关闭页面
	await page.close();
  // 关闭浏览器
  	await browser.close();
})

最后进行方法抽离等处理

方法文件

const fs = require('fs')

exports.init = async function (browser) {
  let page = await browser.newPage();
  await page.setJavaScriptEnabled(true);
	await page.goto("https://juejin.cn/frontend");
  await page.waitForSelector('.entry-list-wrap .entry-list .item');
  return page;
}

exports.getList = async function (page) {
  let list = await page.$$eval('.entry-list-wrap .entry-list .title-row a', list => {
    return list.map(item => {
			return {
        title: item.innerText,
        href: item.href,
      }
		});
	});
  return list.slice(0, 10)
}

exports.getListDetail = async function (list, page) {
  for (let i = 0; i < list.length; i++) {
    await page.goto(list[i].href, {
      timeout: 0
    });
    list[i].content = await page.$eval('.article-content', e => e.innerHTML)
  }
  return list
}

exports.writeFile = async function (name, content) {
  fs.writeFileSync(name, content)
}

主文件

const puppeteer = require('puppeteer');
const path = require('path')
const methods = require('./methods')
puppeteer.launch({
  // 在导航期间忽略 HTTPS 错误
	ignoreHTTPSErrors: true,
  // 不在 无头模式 下运行浏览器
	headless: false,
  // 将 Puppeteer 操作减少指定的毫秒数
  slowMo: 250,
  // 等待浏览器实例启动的最长时间,0禁用超时
	timeout: 0
}).then(async browser => {
  try {
    // 初始化
    let page = await methods.init(browser);
    // 获取列表中标题和链接
    let list = await methods.getList(page)
    // 获取文章详情
    list = await methods.getListDetail(list, page)
    // 保存数据
    methods.writeFile(path.resolve(__dirname, '../out/articles.json'), JSON.stringify(list, null, 2))
    // 关闭页面
    await page.close();
    // 关闭浏览器
    await browser.close();
  } catch (error) {
    console.error(error)
    // 关闭浏览器
    await browser.close();
  }
})
项目地址

https://gitee.com/lydxwj/juejin
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值