1. 程式人生 > >Python爬蟲大戰、 Scrapy分散式原理以及分散式部署

Python爬蟲大戰、 Scrapy分散式原理以及分散式部署

Python爬蟲大戰

爬蟲與發爬蟲的廝殺,一方為了拿到資料,一方為了防止爬蟲拿到資料,誰是最後的贏家?

重新理解爬蟲中的一些概念

爬蟲:自動獲取網站資料的程式

反爬蟲:使用技術手段防止爬蟲程式爬取資料

誤傷:反爬蟲技術將普通使用者識別為爬蟲,這種情況多出現在封ip中,例如學校網路、小區網路再或者網路網路都是共享一個公共ip,這個時候如果是封ip就會導致很多正常訪問的使用者也無法獲取到資料。所以相對來說封ip的策略不是特別好,通常都是禁止某ip一段時間訪問。

成本:反爬蟲也是需要人力和機器成本

攔截:成功攔截爬蟲,一般攔截率越高,誤傷率也就越高

反爬蟲的目的

初學者寫的爬蟲:簡單粗暴,不管對端伺服器的壓力,甚至會把網站爬掛掉了

資料保護:很多的資料對某些公司網站來說是比較重要的不希望被別人爬取

商業競爭問題:這裡舉個例子是關於京東和天貓,假如京東內部通過程式爬取天貓所有的商品資訊,從而做對應策略這樣對天貓來說就造成了非常大的競爭

爬蟲與反爬蟲大戰

上有政策下有對策,下面整理了常見的爬蟲大戰策略

Scrapy分散式原理

關於Scrapy工作流程

Scrapy單機架構

上圖的架構其實就是一種單機架構,只在本機維護一個爬取佇列,Scheduler進行排程,而要實現多型伺服器共同爬取資料關鍵就是共享爬取佇列。

分散式架構

我將上圖進行再次更改

這裡重要的就是我的佇列通過什麼維護?

這裡一般我們通過Redis為維護,Redis,非關係型資料庫,Key-Value形式儲存,結構靈活。

並且redis是記憶體中的資料結構儲存系統,處理速度快,提供佇列集合等多種儲存結構,方便佇列維護

如何去重?

這裡藉助redis的集合,redis提供集合資料結構,在redis集合中儲存每個request的指紋

在向request佇列中加入Request前先驗證這個Request的指紋是否已經加入集合中。如果已經存在則不新增到request佇列中,如果不存在,則將request加入到佇列並將指紋加入集合

如何防止中斷?如果某個slave因為特殊原因宕機,如何解決?

這裡是做了啟動判斷,在每臺slave的Scrapy啟動的時候都會判斷當前redis request佇列是否為空

如果不為空,則從佇列中獲取下一個request執行爬取。如果為空則重新開始爬取,第一臺叢集執行爬取向佇列中新增request

如何實現上述這種架構?

這裡有一個scrapy-redis的庫,為我們提供了上述的這些功能

scrapy-redis改寫了Scrapy的排程器,佇列等元件,利用他可以方便的實現Scrapy分散式架構

關於scrapy-redis的地址:https://github.com/rmax/scrapy-redis

搭建分散式爬蟲

參考官網地址:https://scrapy-redis.readthedocs.io/en/stable/

前提是要安裝scrapy_redis模組:pip install scrapy_redis

這裡的爬蟲程式碼是用的之前寫過的爬取知乎使用者資訊的爬蟲

修改該settings中的配置資訊:

替換scrapy排程器

SCHEDULER = "scrapy_redis.scheduler.Scheduler"

新增去重的class

DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"

新增pipeline

如果新增這行配置,每次爬取的資料也都會入到redis資料庫中,所以一般這裡不做這個配置

ITEM_PIPELINES = {

'scrapy_redis.pipelines.RedisPipeline': 300

}

共享的爬取佇列,這裡用需要redis的連線資訊

這裡的user:pass表示使用者名稱和密碼,如果沒有則為空就可以

REDIS_URL = 'redis://user:[email protected]:9001'

設定為為True則不會清空redis裡的dupefilter和requests佇列

這樣設定後指紋和請求佇列則會一直儲存在redis資料庫中,預設為False,一般不進行設定

SCHEDULER_PERSIST = True

設定重啟爬蟲時是否清空爬取佇列

這樣每次重啟爬蟲都會清空指紋和請求佇列,一般設定為False

SCHEDULER_FLUSH_ON_START=True

分散式

將上述更改後的程式碼拷貝的各個伺服器,當然關於資料庫這裡可以在每個伺服器上都安裝資料,也可以共用一個數據,我這裡方面是連線的同一個mongodb資料庫,當然各個伺服器上也不能忘記:

所有的伺服器都要安裝scrapy,scrapy_redis,pymongo

這樣執行各個爬蟲程式啟動後,在redis資料庫就可以看到如下內容,dupefilter是指紋佇列,requests是請求佇列

Scrapy分散式部署

這個scrapyd的github地址:https://github.com/scrapy/scrapyd

當在遠端主機上安裝了scrapyd並啟動之後,就會再遠端主機上啟動一個web服務,預設是6800埠,這樣我們就可以通過http請求的方式,通過介面的方式管理我們scrapy專案,這樣就不需要在一個一個電腦連線拷貝過著通過git,關於scrapyd官方文件地址:http://scrapyd.readthedocs.io/en/stable/

安裝scrapyd

安裝scrapyd:pip install scrapyd

這裡我在另外一臺ubuntu linux虛擬機器中同樣安裝scrapy以及scrapyd等包,保證所要執行的爬蟲需要的包都完成安裝,這樣我們就有了兩臺linux,包括上篇文章中我們已經有的linux環境

在這裡有個小問題需要注意,預設scrapyd啟動是通過scrapyd就可以直接啟動,這裡bind繫結的ip地址是127.0.0.1埠是:6800,這裡為了其他虛擬機器訪問講ip地址設定為0.0.0.0

scrapyd的配置檔案:/usr/local/lib/python3.5/dist-packages/scrapyd/default_scrapyd.conf

這樣我們就可以通過瀏覽器訪問:

關於部署

如何通過scrapyd部署專案,這裡官方文件提供一個地址:https://github.com/scrapy/scrapyd-client,即通過scrapyd-client進行操作

這裡的scrapyd-client主要實現以下內容:

把我們原生代碼打包生成egg檔案

根據我們配置的url上傳到遠端伺服器上

我們將我們本地的scrapy專案中scrapy.cfg配置檔案進行配置:

我們其實還可以設定使用者名稱和密碼,不過這裡沒什麼必要,只設置了url

這裡設定url一定要注意:url = http://192.168.1.9:6800/addversion.json

最後的addversion.json不能少

我們在本地安裝pip install scrapy_client,安裝完成後執行:scrapyd-deploy

zhaofandeMBP:zhihu_user zhaofan$ scrapyd-deployPacking version 1502177138Deploying to project "zhihu_user" in http://192.168.1.9:6800/addversion.jsonServer response (200):{"node_name": "fan-VirtualBox", "status": "ok", "version": "1502177138", "spiders": 1, "project": "zhihu_user"}zhaofandeMBP:zhihu_user zhaofan$看到status:200表示已經成功

關於常用操作API

listprojects.json列出上傳的專案列表

zhaofandeMBP:zhihu_user zhaofan$ curl http://192.168.1.9:6800/listprojects.json{"node_name": "fan-VirtualBox", "status": "ok", "projects": ["zhihu_user"]}zhaofandeMBP:zhihu_user zhaofan$listversions.json列出有某個上傳專案的版本

zhaofandeMBP:zhihu_user zhaofan$ curl http://192.168.1.9:6800/listversions.json?project=zhihu_user{"node_name": "fan-VirtualBox", "status": "ok", "versions": ["1502177138"]}zhaofandeMBP:zhihu_user zhaofan$schedule.json遠端任務的啟動

下面我們啟動的三次就表示我們啟動了三個任務,也就是三個排程任務來執行zhihu這個爬蟲

zhaofandeMBP:zhihu_user zhaofan$ curl http://192.168.1.9:6800/schedule.json -d project=zhihu_user -d spider=zhihu{"node_name": "fan-VirtualBox", "status": "ok", "jobid": "97f1b5027c0e11e7b07a080027bbde73"}zhaofandeMBP:zhihu_user zhaofan$ curl http://192.168.1.9:6800/schedule.json -d project=zhihu_user -d spider=zhihu{"node_name": "fan-VirtualBox", "status": "ok", "jobid": "99595aa87c0e11e7b07a080027bbde73"}zhaofandeMBP:zhihu_user zhaofan$ curl http://192.168.1.9:6800/schedule.json -d project=zhihu_user -d spider=zhihu{"node_name": "fan-VirtualBox", "status": "ok", "jobid": "9abb1ba27c0e11e7b07a080027bbde73"}zhaofandeMBP:zhihu_user zhaofan$同時當啟動完成後,我們可以通過頁面檢視jobs,這裡因為我遠端伺服器並沒有安裝scrapy_redis,所以顯示任務是完成了,我點開日誌並能看到詳細的日誌情況:

這裡出錯的原因就是我上面忘記在ubuntu虛擬機器安裝scrapy_redis以及pymongo模組,進行

pip install scrapy_redis pymongo安裝後重新啟動,就可以看到已經在執行的任務,同時點開Log日誌也能看到爬取到的內容:

listjobs.json列出所有的jobs任務

上面是通過頁面顯示所有的任務,這裡是通過命令獲取結果

zhaofandeMBP:zhihu_user zhaofan$ curl http://192.168.1.9:6800/listjobs.json?project=zhihu_user{"node_name": "fan-VirtualBox", "status": "ok", "running": [], "pending": [], "finished": [{"start_time": "2017-08-08 15:53:00.510050", "spider": "zhihu", "id": "97f1b5027c0e11e7b07a080027bbde73", "end_time": "2017-08-08 15:53:01.416139"}, {"start_time": "2017-08-08 15:53:05.509337", "spider": "zhihu", "id": "99595aa87c0e11e7b07a080027bbde73", "end_time": "2017-08-08 15:53:06.627125"}, {"start_time": "2017-08-08 15:53:10.509978", "spider": "zhihu", "id": "9abb1ba27c0e11e7b07a080027bbde73", "end_time": "2017-08-08 15:53:11.542001"}]}zhaofandeMBP:zhihu_user zhaofan$cancel.json取消所有執行的任務

這裡可以將上面啟動的所有jobs都可以取消:

zhaofandeMBP:zhihu_user zhaofan$ curl http://192.168.1.9:6800/cancel.json -d project=zhihu_user -d job=0f5cdabc7c1011e7b07a080027bbde73{"node_name": "fan-VirtualBox", "status": "ok", "prevstate": "running"}zhaofandeMBP:zhihu_user zhaofan$ curl http://192.168.1.9:6800/cancel.json -d project=zhihu_user -d job=63f8e12e7c1011e7b07a080027bbde73{"node_name": "fan-VirtualBox", "status": "ok", "prevstate": "running"}zhaofandeMBP:zhihu_user zhaofan$ curl http://192.168.1.9:6800/cancel.json -d project=zhihu_user -d job=63f8e12f7c1011e7b07a080027bbde73{"node_name": "fan-VirtualBox", "status": "ok", "prevstate": "running"}這樣當我們再次通過頁面檢視,就可以看到所有的任務都是finshed狀態:

我相信看了上面這幾個方法你一定會覺得真不方便還需要輸入那麼長,所以有人替你幹了件好事把這些API進行的再次封裝:https://github.com/djm/python-scrapyd-api

關於python-scrapyd-api

該模組可以讓我們直接在python程式碼中進行上述那些api的操作

首先先安裝該模組:pip install python-scrapyd-api

使用方法如下,這裡只演示了簡單的例子,其他方法其實使用很簡單按照規則寫就行:

from scrapyd_api import ScrapydAPIscrapyd = ScrapydAPI('http://192.168.1.9:6800')res = scrapyd.list_projects()res2 = scrapyd.list_jobs('zhihu_user')print(res)print(res2)Cancel a scheduled job

scrapyd.cancel('project_name', '14a6599ef67111e38a0e080027880ca6')

Delete a project and all sibling versions

scrapyd.delete_project('project_name')

Delete a version of a project

scrapyd.delete_version('project_name', 'version_name')

Request status of a job

scrapyd.job_status('project_name', '14a6599ef67111e38a0e080027880ca6')

List all jobs registered

scrapyd.list_jobs('project_name')

List all projects registered

scrapyd.list_projects()

List all spiders available to a given project

scrapyd.list_spiders('project_name')

List all versions registered to a given project

scrapyd.list_versions('project_name')

Schedule a job to run with a specific spider

scrapyd.schedule('project_name', 'spider_name')

Schedule a job to run while passing override settings

settings = {'DOWNLOAD_DELAY': 2}

Schedule a job to run while passing extra attributes to spider initialisation

scrapyd.schedule('project_name', 'spider_name', extra_attribute='value')

以上是全部程式碼,只是善於分享,不足之處請包涵!爬蟲基本的原理就是,獲取原始碼,進而獲取網頁內容。一般來說,只要你給一個入口,通過分析,可以找到無限個其他相關的你需要的資源,進而進行爬取。