PHP, Python, Node.js 都可以寫爬蟲,但如果你如果確定目標為一次多頁型的抓取,那一次併發會比一次抓一頁有效率許多,譬如說你拿到一個網址,透過這個網址可以解出有 20 頁的資料要抓,如果在單一 process 裡面寫迴圈跑 20,程式執行邏輯上是 送出請求 -> 拿到資料(對或錯) -> 送出請求 這樣的循環,假設一個頁面要抓 3 秒,這個 process 最起碼要花 60 秒完成,如果你一次併發 20 個請求,就是 3 秒完成,假設你為了資源一次併發限制為 10 個請求,也僅僅只需要 6 秒完成,跟 60 秒差距是很大的,PHP 原生除非裝其他的相關套件,否則沒有這種異步的寫法,聽說 PHP 7 已經內建有 Thread,在目前還不算普遍的情況下,我們就先不探討,以下示範 Python 跟 Node.js 的作法
job.php
<?php $num = $_GET['num']; $seconds = 3; if (isset($_GET['seconds'])) { $seconds = $_GET['seconds']; } sleep($seconds); echo $num.':'.$seconds;
我先在 server 上寫一段簡單的程式碼,他可以指定你回應延遲時間,這樣可以測出非同步的效果
crawler.py
# -*- coding: utf-8 -*- from gevent import monkey monkey.patch_all() from gevent.pool import Pool import urllib2 def download(url): response = urllib2.urlopen(url).read() print(response) return response seconds = [1, 3, 2] urls = [ "http://chan15.info/job.php?num=%s&seconds=%s" % (i, seconds[i - 1]) for i in range(1, 4) ] pool = Pool(10) result = pool.map(download, urls) print(result)
這是 python 的部份,我利用 gevent 的套件來實現 multithread,一次跑三次請求,number 順序為 1, 2, 3,而秒數延遲為 1, 3, 2,也就是說 number 2 跑最久,但我需要得到正確的 number 順序為 1, 2, 3,這樣的結果才是正確的
$ time python crawler.py 1:1 3:2 2:3 ['1:1', '2:3', '3:2'] real 0m3.291s user 0m0.248s sys 0m0.040s
我們可以看出返回時間的確是 1, 3, 2,最後結果為 1, 2, 3,秒數為 0m3.291s,正確的順序以及併發時間
crawler.js
const util = require('util'); const request = require('request'); const getUrl = async (url) => { return new Promise((resolve, reject) => { request(url, (err, res, body) => { console.log(body); resolve(body); }); }) }; const main = async () => { const url = 'http://chan15.info/job.php?num=%s&seconds=%s'; const numbers = [1, 2, 3]; const seconds = [1, 3, 2]; const jobs = []; numbers.forEach((second, index) => { jobs.push(getUrl(util.format(url, second, seconds[index]))); }); Promise.all(jobs).then((result) => { console.log(result); }); }; main();
node.js 部份我使用了 request 去做請求,node.js 本來就是 async 的設計,所以搭配 async / await 跟 promise 的寫法即可
$ time node crawler.js 1:1 3:2 2:3 [ '1:1', '2:3', '3:2' ] real 0m3.320s user 0m0.294s sys 0m0.024s
執行結果跟 python 是一樣的
crawler.js 批次的寫法
const util = require('util'); const request = require('request'); const batch = require( 'batch-promise' ); const url = 'http://chan15.info/job.php?num=%s&seconds=%s'; const numbers = [1, 2, 3]; const jobs = []; numbers.forEach((second, index) => { jobs.push((resolve, reject) => { const target = util.format(url, second, 3) request(target, (err, res, body) => { console.log(body); resolve(body); }); }); }); batch(jobs, 10).then((result) => { console.log(result); });
多利用了 batch
這個套件