很多人问写爬虫用什么语言比较好,其实就和谈恋爱一样,没有最好的,只有最合适的,选择你最熟悉最顺手的语言就好。今年各种需求不断,写了几个爬虫,都是用的 nodejs。

这里总结一些用 nodejs 写爬虫的常用手段,学会了,就能爬取大部分网页了。开发爬虫的技巧很多也是复用的,记录下来,日后能省不少事。

基本请求网页方法

got 发送 Get 和 Post 请求,返回值均为 Promise,可以使用 async/await 和 Promise.all 来控制流程。

// 习惯使用 got
const got = require('got');

// get
client.get(url);

// post formdata
const FormData = require('form-data');
const form = new FormData();
form.append('name', 'admin');
got.post(url, {
    body: form
});

// post json
got.post(url, {
    json: {
        page: 2
    },
    responseType: 'json'
});

伪装浏览器

通过设置 User-Agent 来模拟浏览器行为。现在很多服务器都会检查 User-Agent 来进行初步的反爬,设置 User-Agent 是很重要的一步。

got.get(url, {
    headers: {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36'
    }
})

也可以通过下面的方式,来为每个请求带上 User-Agent,当然其他参数也是一样的。

// 所有通过 client 发起的请求均会带上 extend 里传入的参数
const client = got.extend({
    headers: {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36'
    }
});

client.get(url);

取消自动重定向

这里要说明一点,got 默认会自动进行重定向,并返回重定向后的返回值,而部分网站需要处理动态的 Cookie 才能得到正确的结果,自动重定向可能会使用不正确的 Cookie 导致结果永远不对,因此还是老老实实手动重定向吧。

client.get(url, {
    followRedirect: false // 取消自动重定向
});

Cookie 通常用于用户的身份鉴别,偶尔也用于加密反爬。写爬虫经常需要和它打交道,但是读取 Cookie 的值不是很方便,这里提供一个方法来简单的获取所需要的 Cookie 值,该方法同样适用于获取 Set-Cookie 的值。

// 来自 stackoverflow: https://stackoverflow.com/questions/5142337/read-a-javascript-cookie-by-name
// 稍微做了点修改
function getCookie(cookiename, cookies) {
    // Get name followed by anything except a semicolon
    let cookiestring = RegExp(cookiename+"=[^;]+").exec(cookies);
    // Return everything after the equal sign, or an empty string if the cookie name not found
    return decodeURIComponent(!!cookiestring ? cookiestring.toString().replace(/^[^=]+./,"") : "");
}

Sleep

做爬虫也要讲文明,要在不影响对方服务的前提下进行,所以要控制爬虫的速率。但是 nodejs 本身并没有像 python 那样的 time.sleep() 方法,因此要自己实现一个。

function sleep(time = 0) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    });
}

页面解析

可以使用正则表达式来解析页面,比任何解析器都要强大。

想学习和在线测试正则表达式,强烈推荐到 RegExr,全是简单的英文,很好懂。

除了正则,npm 上有很多做 HTML 解析的包,最常用也最多人用的就是 cheerio

用法很简单,和 jquery 一摸一样。

const cheerio = require('cheerio');
// 设置 decodeEntities 为 false,为了防止中文被解码。
const $ = cheerio.load(html, { decodeEntities: false });
// 然后就把 $ 当作 jquery 用就好了
$('body').text();

当然,你也可以用 jquery,只不过需要 mock window 和 document 对象,所以需要配合 jsdom 来食用。

const { JSDOM } = require( "jsdom" );
const { window } = new JSDOM( "" );
const $ = require( "jquery" )( window );

使用代理

使用爬虫经常会碰到 IP 被封的情况,各种原因,要看具体的反爬机制,要是不幸中招,就需要使用代理来换一个 IP 继续爬。此外,一些国外网站可能被 GFW 阻挡,导致无法访问,如:P站,这时候就要使用魔法上网,同样需要通过代理的方式进行。

教你魔法上网:正确的科学上网方式

got 有一个 agent 参数,配合 tunnel 可以实现代理。

const tunnel = require('tunnel');
 
got.get(url, {
    agent: {
        https: tunnel.httpsOverHttp({
            proxy: {
                host: '127.0.0.1:6152' // 代理地址及端口
            }
        })
    }
});

多进程并行爬取

javascript 是单线程的,nodejs 因此也一样,但是现在普遍多核的大背景下,nodejs 早就意识到了这点,为了能够充分的“压榨”机器的性能,nodejs 推出一个内置模块 – cluster。没错,单机集群,由一个父进程管理一群子进程。

这样就可以通过多个子进程的方式来实现并行爬取。

const cluster = require('cluster');
const numCPUs = require('os').cpus().length; // cpu 核心数

if (cluster.isMaster) {
    for (let i = 0; i < numCPUs; i++) {
        cluster.fork();
    }
} else {
    // 爬虫逻辑
}

内容不多,但是非常实用,至少我自己在日常开发爬虫的过程中经常使用,希望对各位有帮助吧。