掘金 后端 ( ) • 2024-05-17 13:25

theme: channing-cyan

一、准备工作:

下载node,会在终端上使用

创建文件夹,将此文件夹初始化为后端项目:

image.png

npm init -y

成功后,你的文件夹里会有一个项目描述文件package.json。

image.png

需要一个入口文件main.js,在文件中添加。

image.png

二、写爬虫思路:

  • 发起 HTTP 请求:使用 request-promise 模块发起 HTTP 请求,获取豆瓣电影 Top250 页面的 HTML 内容。

安装 request-promise模块

npm i request-promise

image.png 在package.json文件里有依赖就说明安装成功了 image.png

  • 解析 HTML 内容:使用 cheerio 模块将获取到的 HTML 内容解析成 DOM 树,以便后续的数据提取。

安装cheerio模块

npm i cheerio

image.png

  • 提取电影信息:通过解析后的 DOM 树,从每个电影节点中提取电影的标题、信息和评分等内容。

  • 处理分页:循环遍历每一页,调用 getPage 函数获取每一页的电影信息,并将结果存储到一个数组中。

  • 写入 JSON 文件:将存储电影信息的数组写入到名为 output.json 的 JSON 文件中。

三、代码解析:

1. 首先,通过 require 关键字引入了需要的模块:

  • request-promise 用于发起 HTTP 请求,并以 Promise 形式返回响应数据。
  • cheerio 是一个类似 jQuery 的库,用于在服务器端解析 HTML 和操作 DOM。
  • fs 是 Node.js 的文件系统模块,用于读写文件。
  • util 是 Node.js 内置模块,提供一些实用函数。
// 引入所需的模块 
let request = require('request-promise') // 使用 request-promise 模块发送 HTTP 请求
let cheerio = require('cheerio') // 使用 cheerio 模块解析 HTML
let fs = require('fs') // 使用 fs 模块进行文件操作
const util = require('util') // 使用 util 模块提供的实用函数

2. 定义了一个空数组 movies 用于存储电影信息,以及一个变量 basicUrl,表示豆瓣电影Top250页面的基本 URL。

// 存储电影信息的数组 
let movies = [] 
// 豆瓣电影Top250页面的基本 URL 
let basicUrl = 'https://movie.douban.com/top250'

3. 定义了一个名为 once 的函数,其作用是确保传入的回调函数只被调用一次。

// 确保回调函数只被调用一次的函数
let once = function (cb) {
    let active = false
    if (!active) {
        cb()
        active = true
        }
}

4. 定义了一个名为 log 的函数,它接受一个参数 item,在函数内部调用了 once 函数来确保 console.log(item) 只被调用一次。

// 打印信息,并确保只打印一次
function log(item) {
    once(() => {
    console.log(item)
    })
}

5. 定义了一个名为 getMovieInfo 的函数,接受一个参数 node,表示一个 HTML 节点,然后使用 cheerio 库解析该节点,从中提取电影的标题、信息和评分,并返回一个包含这些信息的对象。

// 解析电影信息的函数
function getMovieInfo (node) {
    // 使用 cheerio 加载 HTML 节点
    let $ = cheerio.load(node)
    // 选择电影标题元素并获取文本内容
    let titles = $('.info .hd span')
    titles = ([]).map.call(titles, t => {
        return $(t).text()
    })
    // 选择电影信息元素并获取文本内容
    let bd = $('.info .bd')
    let info = bd.find('p').text()
    // 选择电影评分元素并获取文本内容
    let score = bd.find('.star .rating_num').text()
    // 返回包含电影信息的对象
    return { titles, info, score }
}

6. 定义了一个名为 getPage 的异步函数,用于获取指定 URL 页面的 HTML 内容,并从中解析出电影信息。该函数接受两个参数:url 表示要爬取的页面 URL,num 表示当前页面的页码。在函数内部,使用 request-promise 发起 HTTP 请求,获取页面内容,然后使用 cheerio 加载页面,并通过选择器定位到电影信息所在的节点,最终返回一个包含电影信息的数组。

// 异步函数:获取指定 URL 页面的电影信息
async function getPage (url, num) {
    // 发送 HTTP 请求获取页面内容
    let html = await request({
        url
    })
    // 打印连接成功的提示信息和当前页码
    console.log('连接成功!', `正在爬取第${num+1}页数据`)
    // 使用 cheerio 加载页面内容
    let $ = cheerio.load(html)
    // 选择电影节点元素
    let movieNodes = $('#content .article .grid_view').find('.item')
    // 将每个电影节点解析为电影信息,并存入数组中
    let movieList = ([]).map.call(movieNodes, node => {
        return getMovieInfo(node)
    })
    // 返回包含电影信息的数组
    return movieList
}

7. 定义了一个名为 main 的异步函数,作为程序的入口。在 main 函数中,首先设定了 count 变量表示要爬取的页面数量(每页包含25部电影),然后通过循环依次爬取每一页的电影信息,将结果存入 list 数组中。最后,将 list 数组中的电影信息写入到一个名为 output.json 的 JSON 文件中,并在写入完成后打印提示信息。

async function main () {
    // 要爬取的页面数量
    let count = 25
    let list  = []
    
    // 循环爬取每一页的电影信息
    for (let i = 0 ; i < count ; i++) {
        // 构造当前页的 URL
        let url = basicUrl + `?start=${25*i}`
        // 获取当前页的电影信息,并存入数组中
        list.push(... await getPage(url, i))
    }
    
    // 打印获取的电影信息数量
    console.log(list.length)
    
    // 将电影信息写入 JSON 文件
    fs.writeFile('./output.json', JSON.stringify(list), 'utf-8', () => {
        console.log('生成json文件成功!')
    })
}

8. 最后,调用 main 函数启动整个爬虫程序,开始爬取豆瓣电影Top250页面的电影信息。

// 启动爬虫程序 main()

9.运行成功后会有一个 output.json 文件

image.png 打开output.json文件:

image.png

四、整体代码:

let request = require('request-promise') // 需要安装
let cheerio = require('cheerio') // 需要安装
let fs = require('fs')
const util = require('util')
let movies = []
let basicUrl = 'https://movie.douban.com/top250'
let once = function (cb) {
    let active = false
    if (!active) {
        cb()
        active = true
        }
    }
    function log(item) {
        once(() => {
        console.log(item)
        })
    }
    function getMovieInfo (node) {
        let $ = cheerio.load(node)
        let titles = $('.info .hd span')
        titles = ([]).map.call(titles, t => {
        return $(t).text()
    })
    let bd = $('.info .bd')
    let info = bd.find('p').text()
    let score = bd.find('.star .rating_num').text()
    return { titles, info, score }
}
async function getPage (url, num) {
    let html = await request({
        url
    })
    console.log('连接成功!', `正在爬取第${num+1}页数据`)
    let $ = cheerio.load(html)
    let movieNodes = $('#content .article .grid_view').find('.item')
    let movieList = ([]).map.call(movieNodes, node => {
        return getMovieInfo(node)
    })
    return movieList
}
async function main () {
    let count = 25
    let list  = []
    for (let i = 0 ; i < count ; i++) {
        let url = basicUrl + `?start=${25*i}`
        list.push(... await getPage(url, i))
    }
    console.log(list.length)
    fs.writeFile('./output.json', JSON.stringify(list), 'utf-8', () => {
        console.log('生成json文件成功!')
    })
}
main()