从网页中提取结构化数据:Puppeteer和Cheerio的高级技巧

亿牛云代理


## 导语

网页数据抓取是一种从网页中提取有用信息的技术,它可以用于各种目的,如数据分析、竞争情报、内容聚合等。然而,网页数据抓取并不是一件容易的事情,因为网页的结构和内容可能会随时变化,而且有些网站会采用反爬虫措施,阻止或限制爬虫的访问。因此,我们需要使用一些高级的技巧,来提高爬虫的效率和稳定性。

## 概述

在本文中,我们将介绍两个常用的网页数据抓取工具:Puppeteer和Cheerio。Puppeteer是一个基于Node.js的无头浏览器库,它可以模拟浏览器的行为,如打开网页、点击元素、填写表单等。Cheerio是一个基于jQuery的HTML解析库,它可以方便地从HTML文档中提取数据,如选择器、属性、文本等。我们将结合这两个工具,展示如何从网页中提取结构化数据,并给出一些高级技巧,如使用代理IP、处理动态内容、优化性能等。

## 正文

### 使用代理IP提高爬虫效果

代理IP是一种隐藏真实IP地址的方法,它可以让爬虫伪装成不同的用户或地区访问网站,从而避免被封禁或限速。使用代理IP的方法有很多,例如使用第三方服务、自建代理池等。在本文中,我们将使用亿牛云爬虫代理作为示例,它提供了稳定、快速、安全的代理IP服务,并支持多种协议和认证方式。

要使用亿牛云爬虫代理,我们需要先注册一个账号,并获取域名、端口、用户名和密码。然后,在Puppeteer中,我们可以通过设置`launch`方法的`args`参数,来指定代理IP地址和认证信息。例如:

```javascript

// 引入puppeteer模块

const puppeteer = require('puppeteer');

// 定义亿牛云 爬虫加强版代理的域名、端口、用户名和密码

const proxyDomain = 'www.16yun.cn';

const proxyPort = '9020';

const proxyUser = '16YUN';

const proxyPass = '16IP';

// 启动无头浏览器,并设置亿牛云代理IP地址和认证信息

const browser = await puppeteer.launch({

  args: [

    `--proxy-server=${proxyDomain}:${proxyPort}`,

    `--proxy-auth=${proxyUser}:${proxyPass}`

  ]

});

```

这样,我们就可以通过亿牛云爬虫代理访问任何网站了。

### 处理动态内容

动态内容是指那些不是在网页加载时就存在的内容,而是通过JavaScript或Ajax等技术在运行时生成或更新的内容。例如,有些网站会使用分页或滚动加载来显示更多数据,或者使用下拉菜单或按钮来切换不同的视图。这些动态内容对于普通的HTML解析器来说是不可见的,因此我们需要使用Puppeteer来模拟浏览器的交互行为,来触发或获取这些内容。

在Puppeteer中,我们可以使用`page`对象来操作网页。`page`对象提供了很多方法和事件,来模拟用户的输入和反馈。例如:

- `page.goto(url)`:打开一个网页

- `page.waitForSelector(selector)`:等待一个元素出现

- `page.click(selector)`:点击一个元素

- `page.evaluate(function)`:在网页中执行一个函数

- `page.on(event, handler)`:监听一个事件

使用这些方法,我们可以实现很多复杂的交互逻辑,来处理动态内容。例如,假设我们要从一个电商网站中提取商品的名称、价格和评分,但是这些数据是通过滚动加载的,我们可以使用以下代码:

```javascript

// 引入puppeteer和cheerio模块

const puppeteer = require('puppeteer');

const cheerio = require('cheerio');

// 定义目标网址

const url = 'https://www.amazon.com/s?k=iphone';

// 启动无头浏览器,并设置亿牛云代理IP地址和认证信息

const browser = await puppeteer.launch({

  args: [

    `--proxy-server=${proxyDomain}:${proxyPort}`,

    `--proxy-auth=${proxyUser}:${proxyPass}`

  ]

});

// 打开一个新的页面

const page = await browser.newPage();

// 打开目标网址

await page.goto(url);

// 定义一个空数组,用于存储提取的数据

let data = [];

// 定义一个循环,用于滚动加载更多数据

while (true) {

  // 等待商品列表出现

  await page.waitForSelector('.s-result-list');

  // 获取网页的HTML内容

  const html = await page.content();

  // 使用cheerio加载HTML内容,并提取数据

  const $ = cheerio.load(html);

  $('.s-result-item').each((index, element) => {

    // 获取商品的名称、价格和评分

    const name = $(element).find('.a-size-medium').text().trim();

    const price = $(element).find('.a-price-whole').text().trim();

    const rating = $(element).find('.a-icon-alt').text().trim();

    // 如果数据不为空,就添加到数组中

    if (name && price && rating) {

      data.push({ name, price, rating });

    }

  });

  // 打印当前提取的数据数量

  console.log(`当前提取了${data.length}条数据`);

  // 判断是否有下一页的按钮

  const nextButton = await page.$('.a-last a');

  // 如果有下一页的按钮,就点击它,并继续循环

  if (nextButton) {

    await nextButton.click();

  } else {

    // 如果没有下一页的按钮,就退出循环

    break;

  }

}

// 关闭无头浏览器

await browser.close();

// 打印最终提取的数据

console.log(data);

```

这样,我们就可以从动态内容中提取结构化数据了。

### 优化性能

性能是指爬虫的运行速度和资源消耗。性能优化是指通过一些方法,来提高爬虫的运行效率和稳定性。性能优化的方法有很多,例如:

- 减少无用的请求:有些网页会加载很多不相关的资源,如图片、视频、广告等,这些资源对于数据抓取来说是没有用的,而且会增加网络流量和内存占用。我们可以通过设置`page.setRequestInterception(true)`和`page.on('request', callback)`来拦截和过滤这些请求。例如:

```javascript

// 启用请求拦截

await page.setRequestInterception(true);

// 监听请求事件,并过滤不需要的请求类型

page.on('request', request => {

  const type = request.resourceType();

  if (type === 'image' || type === 'media' || type === 'font') {

    request.abort();

  } else {

    request.continue();

  }

});

```

- 并发多个任务:有些时候,我们需要抓取多个网页或多个网站的数据,如果我们按照顺序一个一个地抓取,那么会花费很多时间。我们可以通过使用`Promise.all()`或`Promise.map()`等方法,来并发多个任务,从而提高爬虫的速度。例如,假设我们要从三个不同的网站中提取新闻标题,我们可以使用以下代码:

```javascript

// 引入puppeteer和cheerio模块

const puppeteer = require('puppeteer');

const cheerio = require('cheerio');

// 定义三个目标网址

const urls = [

  'https://www.bbc.com/news',

  'https://www.cnn.com',

  'https://www.nytimes.com'

];

// 启动无头浏览器,并设置亿牛云代理IP地址和认证信息

const browser = await puppeteer.launch({

  args: [

    `--proxy-server=${proxyDomain}:${proxyPort}`,

    `--proxy-auth=${proxyUser}:${proxyPass}`

  ]

});

// 定义一个函数,用于从一个网址中提取新闻标题

const getNewsTitles = async (url) => {

  // 打开一个新的页面

  const page = await browser.newPage();

  // 打开目标网址

  await page.goto(url);

  // 获取网页的HTML内容

  const html = await page.content();

  // 使用cheerio加载HTML内容,并提取数据

  const $ = cheerio.load(html);

  const titles = [];

  $('h1, h2, h3').each((index, element) => {

    // 获取新闻标题,并去除空白字符

    const title = $(element).text().trim();

    // 如果标题不为空,就添加到数组中

    if (title) {

      titles.push(title);

    }

  });

  // 返回提取的数据

  return titles;

};

// 使用Promise.all()并发执行三个任务,并获取结果

const results = await Promise.all(urls.map(getNewsTitles));

// 关闭无头浏览器

await browser.close();

// 打印最终提取的数据

console.log(results);

```

这样,我们就可以同时从三个网站中提取新闻标题了。

## 案例

为了更好地理解和应用Puppeteer和Cheerio的高级技巧,我们将以一个具体的案例来进行演示。我们的目标是从豆瓣电影网站中提取最新上映的电影的名称、评分、类型和简介,并保存到一个CSV文件中。

首先,我们需要安装puppeteer、cheerio和csv-writer这三个模块:

```bash

npm install puppeteer cheerio csv-writer --save

```

然后,我们需要编写以下代码:

```javascript

// 引入puppeteer、cheerio和csv-writer模块

const puppeteer = require('puppeteer');

const cheerio = require('cheerio');

const csvWriter = require('csv-writer');

// 定义目标网址

const url = 'https://movie.douban.com/cinema/nowplaying';

// 启动无头浏览器,并设置亿牛云代理IP地址和认证信息

const browser = await puppeteer.launch({

  args: [

    `--proxy-server=${proxyDomain}:${proxyPort}`,

    `--proxy-auth=${proxyUser}:${proxyPass}`

  ]

});

// 打开一个新的页面

const page = await browser.newPage();

// 打开目标网址

await page.goto(url);

// 获取网页的HTML内容

const html = await page.content();

// 使用cheerio加载HTML内容,并提取数据

const $ = cheerio.load(html);

const data = [];

$('#nowplaying .list-item').each((index, element) => {

  // 获取电影的名称、评分、类型和简介

  const name = $(element).data('title');

  const rating = $(element).data('score');

  const genre = $(element).data('category');

  const summary = $(element).find('.stitle a').attr('title');

  // 如果数据不为空,就添加到数组中

  if (name && rating && genre && summary) {

    data.push({ name, rating, genre, summary });

  }

});

// 关闭无头浏览器

await browser.close();

// 定义CSV文件的列名和路径

const columns = [

  { id: 'name', title: '名称' },

  { id: 'rating', title: '评分' },

  { id: 'genre', title: '类型' },

  { id: 'summary', title: '简介' }

];

const path = './movies.csv';

// 创建一个CSV文件写入器,并写入数据

const writer = csvWriter.createObjectCsvWriter({ path, columns });

await writer.writeRecords(data);

// 打印完成的提示

console.log('数据已保存到movies.csv文件中');

```

最后,我们可以运行以下命令,来执行我们的代码:

```bash

node index.js

```

这样,我们就可以从豆瓣电影网站中提取最新上映的电影的数据,并保存到一个CSV文件中了。

## 结语

在本文中,我们介绍了如何使用Puppeteer和Cheerio来从网页中提取结构化数据,并给出了一些高级技巧,如使用代理IP、处理动态内容、优化性能等。我们还以一个具体的案例来进行演示,从豆瓣电影网站中提取最新上映的电影的数据,并保存到一个CSV文件中。我们希望这些技巧和案例能够对您有所启发和帮助,让您能够更好地利用网页数据抓取的技术,来实现您的目标和需求。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,233评论 6 495
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,357评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,831评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,313评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,417评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,470评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,482评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,265评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,708评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,997评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,176评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,503评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,150评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,391评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,034评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,063评论 2 352

推荐阅读更多精彩内容