說這是一個爬蟲有點說大話了,但這個名字又恰到好處,因此在前面加了」簡易「兩個字,代表
這是一個閹割的爬蟲,簡單的使用或者玩玩兒仍是能夠的。
公司最近有新的業務要去抓取競品的數據,看了以前的同窗寫的抓取系統,存在必定的問題,
規則性太強了,不管是擴展性仍是通用性發面都稍微弱了點,以前的系統必需要你搞個列表,
而後從這個列表去爬取,沒有深度的概念,這對爬蟲來講簡直是硬傷。所以,我決定搞一個
稍微通用點的爬蟲,加入深度的概念,擴展性通用型方面也提高下。php
咱們這裏約定下,要處理的內容(多是url,用戶名之類的)咱們都叫他實體(entity)。
考慮到擴展性這裏採用了隊列的概念,待處理的實體所有存儲在隊列中,每次處理的時候,
從隊列中拿出一個實體,處理完成以後存儲,並將新抓取到的實體存入隊列中。固然了這裏
還須要作存儲去重處理,入隊去重處理,防止處理程序作無用功。html
+--------+ +-----------+ +----------+ | entity | | enqueue | | result | | list | | uniq list | | uniq list| | | | | | | | | | | | | | | | | | | | | | | | | +--------+ +-----------+ +----------+
當每一個實體進入隊列的時候入隊排重隊列
設置入隊實體標誌爲一後邊再也不入隊,當處理完
實體,獲得結果數據,處理完結果數據以後將結果詩句標誌如結果數據排重list
,固然了
,這裏你也能夠作更新處理,代碼中能夠作到兼容。git
+-------+ | 開始 | +---+---+ | v +-------+ enqueue deep爲1的實體 | init |--------------------------------> +---+---+ set 已經入過隊列 flag | v +---------+ empty queue +------+ +------>| dequeue +------------->| 結束 | | +----+----+ +------+ | | | | | | | v | +---------------+ enqueue deep爲deep+1的實體 | | handle entity |------------------------------> | +-------+-------+ set 已經入過隊列 flag | | | | | v | +---------------+ set 已經處理過結果 flag | | handle result |--------------------------> | +-------+-------+ | | +------------+
爲了爬取某些網站,最怕的就是封ip,封了ip入過沒有代理就只能呵呵呵了。所以,爬取
策略仍是很重要的。github
爬取以前能夠先在網上搜搜待爬取網站的相關信息,看看以前有沒有前輩爬取過,吸取他
門的經驗。而後就是是本身仔細分析網站請求了,看看他們網站請求的時候會不會帶上特
定的參數?未登陸狀態會不會有相關的cookie?最後就是嘗試了,制定一個儘量高的抓
取頻率。redis
若是待爬取網站必需要登陸的話,能夠註冊一批帳號,而後模擬登錄成功,輪流去請求,
若是登陸須要驗證碼的話就更麻煩了,能夠嘗試手動登陸,而後保存cookie的方式(固然
,有能力能夠試試ocr識別)。固然登錄了仍是須要考慮上一段說的問題,不是說登錄了就
萬事大吉,有些網站登陸以後抓取頻率過快會封掉帳號。json
因此,儘量仍是找個不須要登陸的方法,登陸被封帳號,申請帳號、換帳號比較麻煩。數組
初始數據源選擇也很重要。我要作的是一個天天抓取一次,因此我找的是帶抓取網站每日
更新的地方,這樣初始化的動做就能夠做爲全自動的,基本不用我去管理,爬取會從每日
更新的地方自動進行。markdown
抓取深度也很重要,這個要根據具體的網站、需求、及已經抓取到的內容肯定,儘量全
的將網站的數據抓過來。cookie
在生產環境運行以後又改了幾個地方。dom
第一就是隊列這裏,改成了相似棧的結構。由於以前的隊列,deep小的實體老是先執行,
這樣會致使隊列中內容愈來愈多,內存佔用很大,如今改成棧的結構,遞歸的先處理完一個
實體的因此深度,而後在處理下一個實體。好比說初始10個實體(deep=1),最大爬取深度
是3,每個實體下面有10個子實體,而後他們隊列最大長度分別是:
隊列(lpush,rpop) => 1000個 修改以後的隊列(lpush,lpop) => 28個
上面的兩種方式能夠達到一樣的效果,可是能夠看到隊列中的長度差了不少,因此改成第二
中方式了。
最大深度限制是在入隊的時候處理的,若是超過最大深度,直接丟棄。另外對隊列最大長度
也作了限制,讓制意外狀況出現問題。
下面就是又長又無聊的代碼了,原本想發在github,又以爲項目有點小,想一想仍是直接貼出來吧,很差的地方還望看朋友們直言不諱,不論是代碼仍是設計。
原文:http://www.cnblogs.com/iforever/p/6903636.htmlabstract class SpiderBase { /** * @var 處理隊列中數據的休息時間開始區間 */ public $startMS = 1000000; /** * @var 處理隊列中數據的休息時間結束區間 */ public $endMS = 3000000; /** * @var 最大爬取深度 */ public $maxDeep = 1; /** * @var 隊列最大長度,默認1w */ public $maxQueueLen = 10000; /** * @desc 給隊列中插入一個待處理的實體 * 插入以前調用 @see isEnqueu 判斷是否已經若是隊列 * 直插入沒若是隊列的 * * @param $deep 插入實體在爬蟲中的深度 * @param $entity 插入的實體內容 * @return bool 是否插入成功 */ abstract public function enqueue($deep, $entity); /** * @desc 從隊列中取出一個待處理的實體 * 返回值示例,實體內容格式可自行定義 * [ * "deep" => 3, * "entity" => "balabala" * ] * * @return array */ abstract public function dequeue(); /** * @desc 獲取待處理隊列長度 * * @return int */ abstract public function queueLen(); /** * @desc 判斷隊列是否能夠繼續入隊 * * @param $params mixed * @return bool */ abstract public function canEnqueue($params); /** * @desc 判斷一個待處理實體是否已經進入隊列 * * @param $entity 實體 * @return bool 是否已經進入隊列 */ abstract public function isEnqueue($entity); /** * @desc 設置一個實體已經進入隊列標誌 * * @param $entity 實體 * @return bool 是否插入成功 */ abstract public function setEnqueue($entity); /** * @desc 判斷一個惟一的抓取到的信息是否已經保存過 * * @param $entity mixed 用於判斷的信息 * @return bool 是否已經保存過 */ abstract public function isSaved($entity); /** * @desc 設置一個對象已經保存 * * @param $entity mixed 是否保存的一句 * @return bool 是否設置成功 */ abstract public function setSaved($entity); /** * @desc 保存抓取到的內容 * 這裏保存以前會判斷是否保存過,若是保存過就不保存了 * 若是設置了更新,則會更新 * * @param $uniqInfo mixed 抓取到的要保存的信息 * @param $update bool 保存過的話是否更新 * @return bool */ abstract public function save($uniqInfo, $update); /** * @desc 處理實體的內容 * 這裏會調用enqueue * * @param $item 實體數組,@see dequeue 的返回值 * @return */ abstract public function handle($item); /** * @desc 隨機停頓時間 * * @param $startMs 隨機區間開始微妙 * @param $endMs 隨機區間結束微妙 * @return bool */ public function randomSleep($startMS, $endMS) { $rand = rand($startMS, $endMS); usleep($rand); return true; } /** * @desc 修改默認停頓時間開始區間值 * * @param $ms int 微妙 * @return obj $this */ public function setStartMS($ms) { $this->startMS = $ms; return $this; } /** * @desc 修改默認停頓時間結束區間值 * * @param $ms int 微妙 * @return obj $this */ public function setEndMS($ms) { $this->endMS = $ms; return $this; } /** * @desc 設置隊列最長長度,溢出後丟棄 * * @param $len int 隊列最大長度 */ public function setMaxQueueLen($len) { $this->maxQueueLen = $len; return $this; } /** * @desc 設置爬取最深層級 * 入隊列的時候判斷層級,若是超過層級不作入隊操做 * * @param $maxDeep 爬取最深層級 * @return obj */ public function setMaxDeep($maxDeep) { $this->maxDeep = $maxDeep; return $this; } public function run() { while ($this->queueLen()) { $item = $this->dequeue(); if (empty($item)) continue; $item = json_decode($item, true); if (empty($item) || empty($item["deep"]) || empty($item["entity"])) continue; $this->handle($item); $this->randomSleep($this->startMS, $this->endMS); } } /** * @desc 經過curl獲取連接內容 * * @param $url string 連接地址 * @param $curlOptions array curl配置信息 * @return mixed */ public function getContent($url, $curlOptions = []) { $ch = curl_init(); curl_setopt_array($ch, $curlOptions); curl_setopt($ch, CURLOPT_URL, $url); if (!isset($curlOptions[CURLOPT_HEADER])) curl_setopt($ch, CURLOPT_HEADER, 0); if (!isset($curlOptions[CURLOPT_RETURNTRANSFER])) curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); if (!isset($curlOptions[CURLOPT_USERAGENT])) curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Macintosh; Intel Mac"); $content = curl_exec($ch); if ($errorNo = curl_errno($ch)) { $errorInfo = curl_error($ch); echo "curl error : errorNo[{$errorNo}], errorInfo[{$errorInfo}]\n"; curl_close($ch); return false; } $httpCode = curl_getinfo($ch,CURLINFO_HTTP_CODE); curl_close($ch); if (200 != $httpCode) { echo "http code error : {$httpCode}, $url, [$content]\n"; return false; } return $content; } } abstract class RedisDbSpider extends SpiderBase { protected $queueName = ""; protected $isQueueName = ""; protected $isSaved = ""; public function __construct($objRedis = null, $objDb = null, $configs = []) { $this->objRedis = $objRedis; $this->objDb = $objDb; foreach ($configs as $name => $value) { if (isset($this->$name)) { $this->$name = $value; } } } public function enqueue($deep, $entities) { if (!$this->canEnqueue(["deep"=>$deep]))