@ljw1412/web-crawler-lite
v0.1.4
Published
基于Node.js的网络爬虫(轻量版),该版本不支持动态页面爬取。
Downloads
99
Maintainers
Readme
@ljw1412/web-crawler-lite
这是一个爬虫库。
顺便学习Typescript
、以及对项目架构的思考。
- 功能
- [x] TS 支持
- [x] 可以并发爬取
- [x] 自由处理返回数据
- [x] 允许自定义使用网络库(默认使用
superagent
) - [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',
},
})
代理请求
// 为全局设置默认代理,向该爬虫队列添加页面时,如果未设置页面代理,那么页面会使用默认代理。
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
。 - delay 延迟返回请求的时间(ms)。
- headers 设置请求头。
- callback 请求完成后的回调
(err, data) => void
。 - type 页面类型(
html, image, file, json
或自定义字符串)。 - url 页面资源地址。
- tag 标签,用于回调标记。
- marker 自定义标记数据对象。
- method 请求方法,可选值 GET/POST,默认 GET
- query GET 的请求数据
- data POST 的请求数据
- timeout 超时时间(ms),默认值
new Crawler([options])
创建一个新的爬虫实例。
参数:
- options 爬虫基础配置(对象的属性均为选填)。
- timeout 超时时间(ms),默认值
20 * 1000
。 - delay 延迟返回请求的时间(ms)。
- headers 设置请求头
- callback 请求完成后的回调
(err, data) => void
。 - end 爬虫结束事件
()=>void
- concurrency 允许的并发数量
- worker 自定义请求方法
(page, done) => void
,最后使用执行回调done(err, data)
。 - browerConfig 同
puppeteer.launch([options])
中的options
。 - hideDefaultLog 隐藏默认控制台日志输出,默认值
false
- timeout 超时时间(ms),默认值
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()
停止爬取。
crawler.stopAndDrain()
停止爬取并返回结束事件。