npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@ljw1412/web-crawler

v0.0.25

Published

基于Node.js的网络爬虫

Downloads

67

Readme

@ljw1412/web-crawler

npm version

这是一个爬虫库。

顺便学习Typescript 、以及对项目架构的思考。

  • 功能
    • [x] TS支持
    • [x] 可以并发爬取
    • [x] 自由处理返回数据
    • [x] 允许自定义使用网络库(默认使用superagent)
    • [x] 自定义请求头
    • [x] 动态页面的数据爬取
    • [X] 请求代理
    • [X] 简单的插件扩展支持
    • [X] 请求方法支持GET/POST(允许带参数)
    • [X] 自定义日志
    • 其他功能构思中……

安装

# 使用 npm
npm install @ljw1412/web-crawler
# 使用 yarn
yarn add @ljw1412/web-crawler

基础用法

const { Crawler, Page, logger } = require('@ljw1412/web-crawler')

// 默认并发数(concurrency)为1
const c = new Crawler({ concurrency: 5 })

// 设置默认回调事件
c.callback((err, { page, raw, $ }) => {
  if (err) {
    logger.error('错误', page.url, err)
  } else if ($) {
    // $ 采用 cheerio,一个专为服务端设计的实现jquery核心功能的包
    logger.info('[title]', $('title').text())
  }
})

// 添加需要爬取的页面
c.add(new Page({ type: 'html', url: 'https://jd.com' }))
// 以Page数组的形式添加
c.add([
  new Page({ type: 'html', url: 'https://www.tmall.com' }),
  new Page({ type: 'html', url: 'https://www.taobao.com' })
])
// 也可以单独设置回调事件
c.add(
  new Page({
    type: 'html',
    url: 'http://www.amazon.com',
    // 更加细致的回调处理,优先级高于默认回调(此时不会走默认回调)。
    callback: (err, { page, raw, $ }) => {
      if (err) {
        logger.error('错误', page.url, err)
      } else if ($) {
        // $ 采用 cheerio,一个专为服务端设计的实现jquery核心功能的包
        logger.info('[Good Luck!]', $('title').text())
      }
    }
  })
)

// 开始爬取
c.start()

监听绑定

除了使用callback进行结果回调处理外你还可以使用on/off进行事件处理。

  • event:
    • data
    • data.{type}
    • data#{tag}
    • error
    • end
    • log
const { Crawler, Page, logger } = require('@ljw1412/web-crawler')

const c = new Crawler()

c.add([new Page({ type: 'html', url: 'https://jd.com' }),
  new Page({ type: 'html', url: 'https://www.tmall.com' }),
  new Page({ type: 'html', tag: 'no-money', url: 'https://www.taobao.com' })
])

// 监听所有的成功回调
c.on('data', ({ page, raw, $ }) => {
  if ($) {
    logger.success('[data]', $('title').text())
  }
})

// 监听 page.type === 'html' 的成功回调
c.on('data.html', ({ page, raw, $ }) => {
  if ($) {
    logger.success('[data.html]', $('title').text())
  }
})

// 监听 tag === 'no-money' 的成功回调
c.on('data#no-money', ({ page, raw, $ }) => {
  if ($) {
    logger.success('[data#no-money]', $('title').text())
  }
})

// 监听所有的错误
c.on('error', error => {
  logger.error('[error]', error)
})

c.on('end', () => {
  logger.success('[end]')
})

c.on('log', event => {
  logger.info('[日志]', event)
})

c.start()

自定义网络库

const axios = require('axios')
const cheerio = require('cheerio')
const proxyAgent = require('proxy-agent')
const { Crawler, Page, logger } = require('@ljw1412/web-crawler')

const c = new Crawler()

async function axiosRequest(page, data) {
  const { id, type, url, timeout, headers, proxy, method, data, query, emitter } = page
  
  emitter.infoLog('Before Request', `#${id} axios:${url}`, { page })
  const options = { timeout, headers }
  if (['image', 'file'].includes(type)) options.responseType = 'arraybuffer'
  if (proxy) {
    // 请求代理处理
    options.httpAgent = new proxyAgent(proxy)
    options.httpsAgent = new proxyAgent(proxy)
    emitter.warnLog('Request Proxy', `#${id} ${url} -> ${proxy}`, { page })
  }

  let resp
  if (method === 'POST') {
    resp = await axios.post(url, data, options)
  } else {
    resp = await axios.get(url, Object.assign(options, { params: query }))
  }
  cbData.raw = resp.data

  switch (type) {
    case 'html':
      data.$ = cheerio.load(data.raw)
      break
    case 'json':
      if (typeof cbData.raw === 'object') {
        cbData.json = cbData.raw
        break
      }
      try {
        data.json = JSON.parse(data.raw)
      } catch (err) {
        emitter.errorLog(
          'SyntaxError',
          `#${id} ${url}\n$JSON解析错误: ${error.message}`,
          { error, page }
        )
      }
      break
    case 'image':
    case 'file':
      data.buffer = resp.data
      break
  }
}

// 覆盖默认请求方法
c.default.request = axiosRequest

c.add(
  new Page({
    type: 'html',
    url: 'http://www.google.com',
    proxy: 'socks5://127.0.0.1:1086'
  })
)

c.callback((err, { page, raw, $ }) => {
  if (err) {
    logger.error('错误', page.url, err)
  } else if ($) {
    logger.info('[title]', $('title').text())
  }
})

c.start()

自定义请求头

const c = new Crawler({
  concurrency: 5,
  // 设置默认的请求头,所有被添加的Page都会使用。
  headers: {
    'User-Agent':
      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36'
  }
})

const page = new Page({
  type: 'image',
  url: 'XXX',
  // 设置页面特有的请求头,将与默认请求头进行合并。
  // 优先级高于默认请求头(即同名属性将覆盖默认请求头)。
  headers: {
    'User-Agent':
      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10; rv:33.0) Gecko/20100101 Firefox/33.0',
    Referer: 'https://localhost'
  }
})

动态页面爬取

现在很多页面都是用了SPA。此时使用普通请求的方式去获取页面数据,会出现页面节点未渲染的情况。

此时你可以将Page的javascript设置为true,此时该请求将会以无头浏览器的模式去加载页面(因为会执行页面脚本代码因此性能会有所下降)。

import { Crawler, Page, logger } from '../src/index'

const c = new Crawler()

c.add(
  new Page({
    type: 'html',
    url: 'https://www.baidu.com',
    javascript: true,
    callback: (err, { page, raw, $ }) => {
      if (err) {
        logger.error(err.message)
        return
      }
      logger.success('[请求成功]', raw)
    }
  })
)

c.start()

代理请求

// 为全局设置默认代理,向该爬虫队列添加页面时,如果未设置页面代理,那么页面会使用默认代理。
new Crawler({proxy: 'socks5://127.0.0.1:1086'})

// 为单个页面设置代理,优先级高于全局默认代理。
// javascript 动态模式暂不支持代理请求。
new Page({
  type: 'html',
  url: 'https://www.google.com',
  proxy: 'socks5://127.0.0.1:1086'
})

API

new Page([options])

创建一个页面实例。

参数:

  • options
    • timeout 超时时间(ms),默认值 20 * 1000
    • headers 设置请求头。
    • callback 请求完成后的回调 (err, data) => void
    • type 页面类型(html, image, file, json或自定义字符串)。
    • url 页面资源地址。
    • javascript 是否加载页面的js。
    • tag 标签,用于回调标记。
    • marker 自定义标记数据对象。
    • method 请求方法,可选值GET/POST,默认GET
    • query GET的请求数据
    • data POST的请求数据

new Crawler([options])

创建一个新的爬虫实例。

参数:

  • options 爬虫基础配置(对象的属性均为选填)。
    • timeout 超时时间(ms),默认值 20 * 1000
    • headers 设置请求头
    • callback 请求完成后的回调 (err, data) => void
    • end 爬虫结束事件 ()=>void
    • concurrency 允许的并发数量
    • worker 自定义请求方法 (page, done) => void,最后使用执行回调done(err, data)
    • browerConfig 同puppeteer.launch([options])中的options
    • hideDefaultLog 隐藏默认控制台日志输出,默认值false

Crawler.use(plugin)

  • @param plugin <(Crawler) => void>

crawler.on(event,listener)

crawler.off(event,listener)

  • @param event <string>
  • @param listener <Function>
  • @return this <Crawler>

事件绑定/解绑,使用方法见监听绑定

crawler.timeout(timeout)

  • @param timeout <number>
  • @return this <Crawler>

设置超时时间,单位毫秒(ms)。但是优先级低于Page实例中的timeout

可以在运行时进行修改,对后面添加的页面有效。

crawler.callback(callback)

  • @param callback (err, data) => void
  • @return this <Crawler>

设置回调方法,格式同构造函数传参的options.callback。但是优先级低于Page实例中的callback

可以在运行时进行修改,对后面添加的页面有效。

crawler.filter(filter)

  • @param filter (page: Page) => boolean
  • @return this <Crawler>

设置过滤方法。返回值为false将不会加入爬虫队列。可以在运行时进行修改,对后面添加的页面有效。

crawler.add(page)

  • @param page <Page>
  • @return this <Crawler>

crawler.add(pages)

  • @param page <Page[]>
  • @return this <Crawler>

向队列中添加请求一个或多个页面(Page)。

crawler.addPage(pageOptions)

  • @param pageOptions <PageOptions>
  • @return this <Crawler>

crawler.addPage(pagesOptions)

  • @param pagesOptions <PageOptions[]>
  • @return this <Crawler>

向队列中添加请求一个或多个页面。这里传的是Page构造器的参数而不是Page实例。

crawler.start()

开始爬取。

crawler.pause()

暂停爬取。

crawler.stop()

停止爬取。


插件

web-crawler-axios-plugin