node爬虫入门

news/2024/7/19 12:18:35 标签: 爬虫, 前端, node.js

本教程仅用于学习,不要用于商业。以往通常使用请求获取页面(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
在这里插入图片描述


http://www.niftyadmin.cn/n/534742.html

相关文章

react项目增加commit的eslint校验

环境 husky&#xff1a;8.0.0 eslint&#xff1a;8.3.0 lint-staged&#xff1a;13.0.3 react&#xff1a;18.2.0 首先创建react项目 使用create-react-app创建项目&#xff0c;没有安装过的使用下面命令安装 npm install -g create-react-app创建项目 create-react-ap…

上手阿里低代码引擎lowcode-engine

目标 实现表格页面简单的增删改查 步骤 打开页面&#xff08;antd 高级组件 formily 表单组件&#xff09; https://lowcode-engine.cn/demo/antd-pro-with-formily.html 组件库》高级组件》CRUD表格&#xff0c;拖拽到页面 修改表格列&#xff0c;列标题和数据字段 点击前…

ecshop 嵌入地图加载不了问题

在ecshop 添加一个标识商家地理位置信息。 百度地图&#xff0c;加载不出来&#xff0c;查了下发现跟 js/transport.js 与 js/utils.js 两个文件有关在需要插入地图的地方去掉这两个文件的引用 地图就可以用&#xff0c;但这两个文件跟商品评论有关&#xff0c;去掉了 用不了评…

异步 多线程 线程池

线程池&#xff1a;IIS维护一个线程池&#xff0c;当请求抵达&#xff0c;会从池中获取一个空闲线程处理他&#xff0c;处理完毕后&#xff0c;线程不会回收&#xff0c;而是重新释放到池中。但线程池有最大容量&#xff0c;当超过容量时&#xff0c;新的请求会被放到一个请求队…

搭建react项目遇到的问题2022

环境 create-react-app&#xff1a;5.0.1 node&#xff1a;14.17.6 webpack&#xff1a;5.64.4 react&#xff1a;18.2.0 react-router-dom&#xff1a;6.4.3 eslint&#xff1a;8.26.0 husky&#xff1a;8.0.1 lint-staged&#xff1a;13.0.3 antd&#xff1a;4.24.0…

Error While Loading Shared Libraries, Cannot Open Shared Object File

In the “I wish the Internet had an actual correct answer” category comes a question from a Windows colleague trying to build software on Linux. He asks “I’m trying to do some web performance testing and I compiled weighttp and the libev libraries, whic…

数组对象排序

数组对象排序 对数字型数组进行排序可以进行冒泡排序&#xff0c;也可以给sort传一个比较函数&#xff0c;但是数组对象怎么排序&#xff0c;很多人就蒙了&#xff0c;其实它也可以通过给sort传一个比较函数来实现。 var arr [{name: "zs", age: 10}, {name: &quo…

关于MIUI6下使用Widget调用Toast的一个问题

编写了一个Widget程序&#xff0c;在继承AppWidgetProvider类中调用Toast&#xff0c;发现如下问题&#xff1a;在小米2&#xff0c;MIUI Version&#xff1a;MIUI5.6.4|Beta&#xff0c; Android Version&#xff1a;5.0.2 LRX22G上&#xff0c;logcat提示&#xff1a;com.XXX…