說到爬蟲,大多數程序員想到的是scrapy這樣受人歡迎的框架。scrapy的確不錯,並且有很強大的生態圈,有gerapy等優秀的可視化界面。可是,它仍是有一些不能作到的事情,例如在頁面上作翻頁點擊操做、移動端抓取等等。對於這些新的需求,能夠用Selenium、Puppeteer、Appium這些自動化測試框架繞開繁瑣的動態內容,直接模擬用戶操做進行抓取。惋惜的是,這些框架不是專門的爬蟲框架,不能對爬蟲進行集中管理,所以對於一個多達數十個爬蟲的大型項目來講有些棘手。javascript
Crawlab是一個基於Celery的分佈式通用爬蟲管理平臺,擅長將不一樣編程語言編寫的爬蟲整合在一處,方便監控和管理。Crawlab有精美的可視化界面,能對多個爬蟲進行運行和管理。任務調度引擎是自己支持分佈式架構的Celery,所以Crawlab能夠自然集成分佈式爬蟲。有一些朋友認爲Crawlab只是一個任務調度引擎,其實這樣認爲並不徹底正確。Crawlab是相似Gerapy這樣的專一於爬蟲的管理平臺。html
本文將介紹如何使用Crawlab和Puppeteer抓取主流的技術博客文章,而後用Flask+Vue搭建一個小型的技術文章聚合平臺。前端
在前一篇文章《分佈式通用爬蟲管理平臺Crawlab》已介紹了Crawlab的架構以及安裝使用,這裏快速介紹一下如何安裝、運行、使用Crawlab。java
到Crawlab的Github Repo用克隆一份到本地。node
git clone https://github.com/tikazyq/crawlab
複製代碼
安裝相應的依賴包和庫。python
cd crawlab
# 安裝python依賴
pip install -r crawlab/requirements
# 安裝前端依賴
cd frontend
npm install
複製代碼
安裝mongodb和redis-server。Crawlab將用MongoDB做爲結果集以及運行操做的儲存方式,Redis做爲Celery的任務隊列,所以須要安裝這兩個數據庫。git
在運行以前須要對Crawlab進行一些配置,配置文件爲config.py
。程序員
# project variables
PROJECT_SOURCE_FILE_FOLDER = '/Users/yeqing/projects/crawlab/spiders' # 爬蟲源碼根目錄
PROJECT_DEPLOY_FILE_FOLDER = '/var/crawlab' # 爬蟲部署根目錄
PROJECT_LOGS_FOLDER = '/var/logs/crawlab' # 日誌目錄
PROJECT_TMP_FOLDER = '/tmp' # 臨時文件目錄
# celery variables
BROKER_URL = 'redis://192.168.99.100:6379/0' # 中間者URL,鏈接redis
CELERY_RESULT_BACKEND = 'mongodb://192.168.99.100:27017/' # CELERY後臺URL
CELERY_MONGODB_BACKEND_SETTINGS = {
'database': 'crawlab_test',
'taskmeta_collection': 'tasks_celery',
}
CELERY_TIMEZONE = 'Asia/Shanghai'
CELERY_ENABLE_UTC = True
# flower variables
FLOWER_API_ENDPOINT = 'http://localhost:5555/api' # Flower服務地址
# database variables
MONGO_HOST = '192.168.99.100'
MONGO_PORT = 27017
MONGO_DB = 'crawlab_test'
# flask variables
DEBUG = True
FLASK_HOST = '127.0.0.1'
FLASK_PORT = 8000
複製代碼
啓動後端API,也就是一個Flask App,能夠直接啓動,或者用gunicorn代替。github
cd ../crawlab
python app.py
複製代碼
啓動Flower服務(抱歉目前集成Flower到App服務中,必須單獨啓動來獲取節點信息,後面的版本不須要這個操做)。redis
python ./bin/run_flower.py
複製代碼
啓動本地Worker。在其餘節點中若是想只是想執行任務的話,只須要啓動這一個服務就能夠了。
python ./bin/run_worker.py
複製代碼
啓動前端服務器。
cd ../frontend
npm run serve
複製代碼
首頁Home中能夠看到總任務數、總爬蟲數、在線節點數和總部署數,以及過去30天的任務運行數量。
點擊側邊欄的Spiders或者上方到Spiders數,能夠進入到爬蟲列表頁。
這些是爬蟲源碼根目錄PROJECT_SOURCE_FILE_FOLDER
下的爬蟲。Crawlab會自動掃描該目錄下的子目錄,將子目錄看做一個爬蟲。Action列下有一些操做選項,點擊部署Deploy按鈕將爬蟲部署到全部在線節點中。部署成功後,點擊運行Run按鈕,觸發抓取任務。這時,任務應該已經在執行了。點擊側邊欄的Tasks到任務列表,能夠看到已經調度過的爬蟲任務。
基本使用就是這些,可是Crawlab還能作到更多,你們能夠進一步探索,詳情請見Github。
Puppeteer是谷歌開源的基於Chromium和NodeJS的自動化測試工具,能夠很方便的讓程序模擬用戶的操做,對瀏覽器進行程序化控制。Puppeteer有一些經常使用操做,例如點擊,鼠標移動,滑動,截屏,下載文件等等。另外,Puppeteer很相似Selenium,能夠定位瀏覽器中網頁元素,將其數據抓取下來。所以,Puppeteer也成爲了新的爬蟲利器。
相對於Selenium,Puppeteer是新的開源項目,並且是谷歌開發,可使用不少新的特性。對於爬蟲來講,若是前端知識足夠的話,寫數據抓取邏輯簡直不能再簡單。正如其名字同樣,咱們是在操做木偶人來幫咱們抓取數據,是否是很貼切?
掘金上已經有不少關於Puppeteer的教程了(爬蟲利器 Puppeteer 實戰、Puppeteer 與 Chrome Headless —— 從入門到爬蟲),這裏只簡單介紹一下Puppeteer的安裝和使用。
安裝很簡單,就一行npm install
命令,npm會自動下載Chromium並安裝,這個時間會比較長。爲了讓安裝好的puppeteer模塊可以被全部nodejs爬蟲所共享,咱們在PROJECT_DEPLOY_FILE_FOLDER
目錄下安裝node的包。
# PROJECT_DEPLOY_FILE_FOLDER變量值
cd /var/crawlab
# 安裝puppeteer
npm i puppeteer
# 安裝mongodb
npm i mongodb
複製代碼
安裝mongodb是爲了後續的數據庫操做。
如下是Copy/Paste的一段用Puppeteer訪問簡書而後截屏的代碼,很是簡潔。
const puppeteer = require('puppeteer');
(async () => {
const browser = await (puppeteer.launch());
const page = await browser.newPage();
await page.goto('https://www.jianshu.com/u/40909ea33e50');
await page.screenshot({
path: 'jianshu.png',
type: 'png',
// quality: 100, 只對jpg有效
fullPage: true,
// 指定區域截圖,clip和fullPage二者只能設置一個
// clip: {
// x: 0,
// y: 0,
// width: 1000,
// height: 40
// }
});
browser.close();
})();
複製代碼
關於Puppeteer的經常使用操做,請移步《我經常使用的puppeteer爬蟲api》。
囉嗦了這麼久,終於到了萬衆期待的爬蟲時間了。Talk is cheap, show me the code!咦?咱們不是已經Show了很多代碼了麼...
因爲咱們的目標是創建一個技術文章聚合平臺,咱們須要去各大技術網站抓取文章。資源固然是越多越好。做爲展現用,咱們將抓取下面幾個具備表明性的網站:
研究發現這三個網站都是由Ajax獲取文章列表,生成動態內容以做爲傳統的分頁替代。這對於Puppeteer來講很容易處理,由於Puppeteer繞開了解析Ajax這一部分,瀏覽器會自動處理這樣的操做和請求,咱們只着重關注數據獲取就好了。三個網站的抓取策略基本相同,咱們以掘金爲例着重講解。
首先是引入Puppeteer和打開網頁。
const puppeteer = require('puppeteer');
const MongoClient = require('mongodb').MongoClient;
(async () => {
// browser
const browser = await (puppeteer.launch({
headless: true
}));
// define start url
const url = 'https://juejin.im';
// start a new page
const page = await browser.newPage();
...
})();
複製代碼
headless
設置爲true
可讓瀏覽器以headless的方式運行,也就是指瀏覽器不用在界面中打開,它會在後臺運行,用戶是看不到瀏覽器的。browser.newPage()
將新生成一個標籤頁。後面的操做基本就圍繞着生成的page
來進行。
接下來咱們讓瀏覽器導航到start url。
...
// navigate to url
try {
await page.goto(url, {waitUntil: 'domcontentloaded'});
await page.waitFor(2000);
} catch (e) {
console.error(e);
// close browser
browser.close();
// exit code 1 indicating an error happened
code = 1;
process.emit("exit ");
process.reallyExit(code);
return
}
...
複製代碼
這裏try
catch
的操做是爲了處理瀏覽器訪問超時的錯誤。當訪問超時時,設置exit code
爲1
表示該任務失敗了,這樣Crawlab會將該任務狀態設置爲FAILURE
。
而後咱們須要下拉頁面讓瀏覽器能夠讀取下一頁。
...
// scroll down to fetch more data
for (let i = 0; i < 100; i++) {
console.log('Pressing PageDown...');
await page.keyboard.press('PageDown', 200);
await page.waitFor(100);
}
...
複製代碼
翻頁完畢後,就開始抓取數據了。
...
// scrape data
const results = await page.evaluate(() => {
let results = [];
document.querySelectorAll('.entry-list > .item').forEach(el => {
if (!el.querySelector('.title')) return;
results.push({
url: 'https://juejin.com' + el.querySelector('.title').getAttribute('href'),
title: el.querySelector('.title').innerText
});
});
return results;
});
...
複製代碼
page.evaluate
能夠在瀏覽器Console中進行JS操做。這段代碼其實能夠直接在瀏覽器Console中直接運行。調試起來是否是方便到爽?前端工程師們,開始歡呼吧!
獲取了數據,接下來咱們須要將其儲存在數據庫中。
...
// open database connection
const client = await MongoClient.connect('mongodb://192.168.99.100:27017');
let db = await client.db('crawlab_test');
const colName = process.env.CRAWLAB_COLLECTION || 'results_juejin';
const taskId = process.env.CRAWLAB_TASK_ID;
const col = db.collection(colName);
// save to database
for (let i = 0; i < results.length; i++) {
// de-duplication
const r = await col.findOne({url: results[i]});
if (r) continue;
// assign taskID
results[i].task_id = taskId;
// insert row
await col.insertOne(results[i]);
}
...
複製代碼
這樣,咱們就將掘金最新的文章數據保存在了數據庫中。其中,咱們用url
字段作了去重處理。CRAWLAB_COLLECTION
和CRAWLAB_TASK_ID
是Crawlab傳過來的環境變量,分別是儲存的collection和任務ID。任務ID須要以task_id
爲鍵保存起來,這樣在Crawlab中就能夠將數據與任務關聯起來了。
整個爬蟲代碼以下。
const puppeteer = require('puppeteer');
const MongoClient = require('mongodb').MongoClient;
(async () => {
// browser
const browser = await (puppeteer.launch({
headless: true
}));
// define start url
const url = 'https://juejin.im';
// start a new page
const page = await browser.newPage();
// navigate to url
try {
await page.goto(url, {waitUntil: 'domcontentloaded'});
await page.waitFor(2000);
} catch (e) {
console.error(e);
// close browser
browser.close();
// exit code 1 indicating an error happened
code = 1;
process.emit("exit ");
process.reallyExit(code);
return
}
// scroll down to fetch more data
for (let i = 0; i < 100; i++) {
console.log('Pressing PageDown...');
await page.keyboard.press('PageDown', 200);
await page.waitFor(100);
}
// scrape data
const results = await page.evaluate(() => {
let results = [];
document.querySelectorAll('.entry-list > .item').forEach(el => {
if (!el.querySelector('.title')) return;
results.push({
url: 'https://juejin.com' + el.querySelector('.title').getAttribute('href'),
title: el.querySelector('.title').innerText
});
});
return results;
});
// open database connection
const client = await MongoClient.connect('mongodb://192.168.99.100:27017');
let db = await client.db('crawlab_test');
const colName = process.env.CRAWLAB_COLLECTION || 'results_juejin';
const taskId = process.env.CRAWLAB_TASK_ID;
const col = db.collection(colName);
// save to database
for (let i = 0; i < results.length; i++) {
// de-duplication
const r = await col.findOne({url: results[i]});
if (r) continue;
// assign taskID
results[i].task_id = taskId;
// insert row
await col.insertOne(results[i]);
}
console.log(`results.length: ${results.length}`);
// close database connection
client.close();
// shutdown browser
browser.close();
})();
複製代碼
這兩個網站的爬蟲代碼基本與上面的爬蟲同樣,只是一些參數不同而已。咱們的爬蟲項目結構以下。
在Crawlab中打開Spiders,咱們能夠看到剛剛編寫好的爬蟲。
點擊各個爬蟲的View查看按鈕,進入到爬蟲詳情。
在Execute Command中輸入爬蟲執行命令。對掘金爬蟲來講,是node juejin_spider.js
。輸入完畢後點擊Save保存。而後點擊Deploy部署爬蟲。最後點擊Run運行爬蟲。
點擊左上角到刷新按鈕能夠看到剛剛運行的爬蟲任務已經在運行了。點擊Create Time後能夠進入到任務詳情。Overview標籤中能夠看到任務信息,Log標籤能夠看到日誌信息,Results信息中能夠看到抓取結果。目前在Crawlab結果列表中還不支持數據導出,可是不久的版本中確定會將導出功能加入進來。
在這一小節,咱們已經可以將Crawlab運行起來,而且能用Puppeteer編寫抓取三大網站技術文章的爬蟲,而且可以用Crawlab運行爬蟲,而且讀取抓取後的數據。下一節,咱們將用Flask+Vue作一個簡單的技術文章聚合網站。能看到這裏的都是有耐心的好同窗,贊一個。
Github: tikazyq/crawlab
若是感受Crawlab還不錯的話,請加做者微信拉入開發交流羣,你們一塊兒交流關於Crawlab的使用和開發。