此小节项目源码:simple-proxy-pool
Eggjs 是阿里开源的一个企业级的 Nodejs web 基础框架。使用 Eggjs 再结合官网文档过一遍,就可以很容易上手了。
采用 Eggjs 搭建项目的框架, 《EggJs 官网文档》。
零、总结
一、初始化项目
# 初始化项目
$ npx egg-init simple-proxy-pool --type=simple
$ cd simple-proxy-pool
$ npm i
# npm安装慢的话,可以使用cnpm
$ npm i -g cnpm
$ cnpm i
# 启动项目
$ npm run dev
$ open localhost:7001
二、目录结构
这里可以去看下《Eggjs 目录结构说明》, 这里有详细的解释。
项目开发中,会用到的目录有主要是以下这些
app/router.js
用于配置 URL 路由规则
app/controller/**
用于解析用户的输入,处理后返回相应的结果
app/service/**
用于编写业务逻辑层,可选,建议使用
config/config.{env}.js
用于编写配置文件
config/plugin.js
用于配置需要加载的插件 【数据库插件,模板渲染插件等】
app/schedule/**
用于定时任务 【定时抓取数据,定时清洗无用数据】
三、 写一个简单的爬虫
爬虫,简单的理解,就是发出一个 http 请求,拿到页面的 html 内容,然后解析 html 内容,得到自己想要的数据。
这里需要使用三个 npm 库
-
request
用来发出 http 请求, -
request-promise
promise 形式的 request 库, promise 化主要是为了方便 配置 async 和 await 使用 -
iconv-lite
html 页面的编码如果是 gbk 的时候,会乱码,因此需要用这个库解码一下。
# 安装需要的库
cnpm i --save request request-promise iconv-lite
# 重新启动项目
cnpm run dev
// simple-proxy-pool/app/controller/home.js
'use strict'
const Controller = require('egg').Controller
const iconv = require('iconv-lite')
const rp = require('request-promise')
class HomeController extends Controller {
/**
* 抓取代理IP
* @param {number} [ipCount=100] 代理数量
* @return {array} 抓取回来的代理列表
*/
async get66ipData(ipCount = 100) {
try {
// 抓取的页面地址
const apiURL = `http://www.66ip.cn/nmtq.php?getnum=${ipCount}&isp=0&anonymoustype=0&start=&ports=&export=&ipaddress=&area=0&proxytype=2&api=66ip`
// 设置HTTP请求参数
const options = {
method: 'GET',
url: apiURL,
gzip: true,
headers: {
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4',
'User-Agent':
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',
referer: 'http://www.66ip.cn/',
Cookie:
'yd_cookie=35c72298-25ad-4321acee5c54def5342d0a3b37ebf11f72c2; _ydclearance=9b6308ea1deb3b3b72c14f64-142a-4adf-9107-694c8922918b-1543373100; Hm_lvt_1761fabf3c988e7f04bec51acd4073f4=1541736200,1542009395,1542297692,1543365902; Hm_lpvt_1761fabf3c988e7f04bec51acd4073f4=1543365915',
Host: 'www.66ip.cn'
}
}
// 发出http请求
let data = await rp(options)
// 如果是 gbk ,则使用icnov 来转
// node环境不支持 gbk,因此会乱码
if (/meta.+charset=gbk/i.test(data)) {
data = iconv.decode(data, 'gbk')
}
// 清洗出代理ip
const proxyList = data.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,4}/g)
// 把数据解析成约定的格式(数据库的格式)
const newList = []
proxyList.forEach(item => {
newList.push({
ip: item.split(':')[0],
port: item.split(':')[1],
anonymous: '高匿',
source: 'http://www.66ip.cn/'
})
})
return newList
} catch (error) {
console.warn('get proxy list error ...')
return error
}
}
async index() {
// 因为获取代理ip是异步的,因此配合promise化的request, 可以直接使用 await来处理异步
// 不用像早起写成功回调
this.ctx.body = await this.get66ipData()
}
}
module.exports = HomeController
注意点:抓取数据的时候,可能因此没有代理 ip 网站的有反爬虫机制,因此需要做一些特殊的处理。 比如这个网站 需要携带 cookie ,否则会报 521 错误。
运行结果
此小节项目源码:simple-proxy-pool