天天看點

Python爬蟲開發從入門到實戰

Python爬蟲開發從入門到實戰(微課版)

第1章 緒論

爬蟲的主要目的是擷取網頁内容并解析。隻要能達到這個目的,用什麼方法都沒有問題。

關于擷取網頁,本書主要介紹了Python的兩個第三方子產品,一個是requests,另一個是爬蟲架構Scrapy。

關于解析網頁内容,本書主要介紹了3種方式——正規表達式、XPath和BeautifulSoup。兩種網頁擷取方式和3種網頁解析方式可以自由搭配,随意使用。

第2章 Python基礎

知識點
  • Python開發環境的搭建。
  • Python的基本知識、資料類型。
  • Python的條件語句和循環語句。
  • Python函數的定義和使用。
  • 基于Python的面向對象程式設計代碼。

第3章 正規表達式與檔案操作

知識點
  • 正規表達式的基本符号。
  • 如何在Python中使用正規表達式。
  • 正規表達式的提取技巧。
  • Python讀寫文本檔案和CSV檔案。

第4章 簡單的網頁爬蟲開發

知識點
  • requests的安裝和使用。
  • 多線程爬蟲的開發。
  • 爬蟲的常見算法。
多線程爬蟲的開發

在掌握了requests與正規表達式以後,就可以開始實戰爬取一些簡單的網址了。

但是,此時的爬蟲隻有一個程序、一個線程,是以稱為單線程爬蟲。單線程爬蟲每次隻通路一個頁面,不能充分利用計算機的網絡帶寬。一個頁面最多也就幾百KB,是以爬蟲在爬取一個頁面的時候,多出來的網速和從發起請求到得到源代碼中間的時間都被浪費了。

如果可以讓爬蟲同時通路10個頁面,就相當于爬取速度提高了10倍。為了達到這個目的,就需要使用多線程技術了。

微觀上的單線程,在宏觀上就像同時在做幾件事。這種機制在I/O(Input/Output,輸入/輸出)密集型的操作上影響不大,但是在CPU計算密集型的操作上面,由于隻能使用CPU的一個核,就會對性能産生非常大的影響。是以涉及計算密集型的程式,就需要使用多程序,Python的多程序不受GIL的影響。

由于爬蟲是I/O密集型的操作,特别是在請求網頁源代碼的時候,如果使用單線程來開發,會浪費大量的時間來等待網頁傳回,是以把多線程技術應用到爬蟲中,可以大大提高爬蟲的運作效率。

多程序庫(multiprocessing)

multiprocessing本身是Python的多程序庫,用來處理與多程序相關的操作。但是由于程序與程序之間不能直接共享記憶體和堆棧資源,而且啟動新的程序開銷也比線程大得多,是以使用多線程來爬取比使用多程序有更多的優勢。multiprocessing下面有一個dummy子產品,它可以讓Python的線程使用multiprocessing的各種方法。

dummy下面有一個Pool類,它用來實作線程池。這個線程池有一個map()方法,可以讓線程池裡面的所有線程都“同時”執行一個函數。

from multiprocessing.dummy import Pool as ThreadPool

# 使用map實作多線程爬蟲
pool = ThreadPool(4)
pool.map(crawler_func, data_list)
pool.close()
pool.join()
           
常見搜尋算法
  • DFS
  • BFS

在爬蟲開發的過程中,應該選擇深度優先還是廣度優先呢?這就需要根據被爬取的資料來進行選擇了。

小結

本章講解了requests的安裝和使用,以及如何使用Python的多程序庫multiprocessing來實作多線程爬蟲。

第5章 高性能HTML内容解析

知識點
  • HTML基礎結構。
  • 使用XPath從HTML源代碼中提取有用資訊。
  • 使用Beautiful Soup4從HTML源代碼中提取有用資訊。
Beautiful Soup4

Beautiful Soup4(BS4)是Python的一個第三方庫,用來從HTML和XML中提取資料。

pip install beautifulsoup4
           
小結

從網頁中提取需要的資訊,是爬蟲開發中最重要但卻最基本的操作。隻有掌握并能自由運用正規表達式、XPath與Beautiful Soup4從網頁中提取資訊,爬蟲的學習才算是入門。

XPath是一門查詢語言,它由C語言開發而來,是以速度非常快。但是XPath需要經過一段時間的練習才能靈活應用。

Beautiful Soup4是一個從網頁中提取資料的工具,它入門很容易,功能很強大,但是由于是基于Python開發的,是以速度比XPath要慢。讀者可以自行選擇喜歡的一項來作為自己主要的資料提取方式。本書選擇使用XPath,是以後面的内容都會以XPath來進行講解。

第6章 Python與資料庫

資料庫

本章将會講解MongoDB和Redis這兩個資料庫。其中MongoDB用來儲存大量資料,Redis用于作為緩存和隊列儲存臨時資料。

知識點
  • MongoDB與Redis的安裝。
  • MongoDB的增删改查操作。
  • Redis的清單與集合的操作。
在Mac OS下安裝MongoDB
brew update
brew install mongodb
#啟動MongoDB
mongod --config /usr/local/etc/mongod.conf
           
圖形化管理工具—RoboMongo

RoboMongo是一個跨平台的MongoDB管理工具,可以在圖形界面中查詢或者修改MongoDB。

資料在MongoDB中是按照“庫(Database)”—“集合(Collections)”—“文檔(Document)”的層級關系來存儲的。如果使用Python的資料結構來做類比的話,文檔相當于一個字典,集合相當于一個包含了很多字典的清單,庫相當于一個大字典,大字典裡面的每一個鍵值對都對應了一個集合,Key為集合的名字,Value就是一個集合。

PyMongo的安裝

PyMongo子產品是Python對MongoDB操作的接口包,能夠實作對MongoDB的增删改查及排序等操作。

pip install pymongo
           
PyMongo的使用

(1)使用PyMongo初始化資料庫

要使用PyMongo操作MongoDB,首先需要初始化資料庫連接配接。如果MongoDB運作在本地計算機上,而且也沒有修改端口或者添加使用者名及密碼,那麼初始化MongoClient的執行個體的時候就不需要帶參數,直接寫為:

from pymongo import MongoClient
client = MongoClient()

           

如果MongoDB是運作在其他伺服器上面的,那麼就需要使用“URI(Uniform Resource Identifier,統一資源标志符)”來指定連接配接位址。MongoDB URI的格式為:

mongodb://使用者名:密碼@伺服器IP或域名:端口

           

PyMongo初始化資料庫與集合有兩種方式。

# 方式1:
from pymongo import MongoClient
client = MongoClient()
database= client.Chapter6
collection = database.spider
# 需要注意,使用方式1的時候,代碼中的“Chapter6”和“spider”都不是變量名,它們直接就是庫的名字和集合的名字。

# 方式2:
from pymongo import MongoClient
client = MongoClient()
database = client['Chapter6'] 
collection = database['spider'] 

# 使用方式2時,在方括号中指定庫名和集合名。這種情況下,方括号裡除了直接寫普通的字元串以外,還可以寫一個變量。
           

預設情況下,MongoDB隻允許本機通路資料庫。這是因為MongoDB預設沒有通路密碼,出于安全性的考慮,不允許外網通路。

如果需要從外網通路資料庫,那麼需要修改安裝MongoDB時用到的配置檔案mongod.conf。

(2)插入資料

MongoDB的插入操作非常簡單。用到的方法為insert(參數),插入的參數就是Python的字典。插入一條資料的代碼如下。

from pymongo import MongoClient
client = MongoClient()
database = client['Chapter6']
collection = database['spider'] 
data = {'id': 123, 'name': 'kingname', 'age': 20, 'salary': 999999}
collection.insert(data)

# MongoDB會自動添加一列“_id”,這一列裡面的資料叫作ObjectId,ObjectId是在資料被插入MongoDB的瞬間,通過一定的算法計算出來的。是以,_id這一列就代表了資料插入的時間,它不重複,而且始終遞增。通過一定的算法,可以把ObjectId反向恢複為時間。

           

将多個字典放入清單中,并将清單作為insert()方法的參數,即可實作批量插入資料,代碼如下。

from pymongo import MongoClient
client = MongoClient()
database = client['Chapter6']
collection = database['spider'] 
more_data = [
    {'id': 2, 'name': '張三', 'age': 10, 'salary': 0},
    {'id': 3, 'name': '李四', 'age': 30, 'salary': -100},
    {'id': 4, 'name': '王五', 'age': 40, 'salary': 1000},
    {'id': 5, 'name': '外國人', 'age': 50, 'salary': '未知'},
]
collection.insert(more_data)
           

(3)普通查找

MongoDB的查找功能對應的方法是:

find(查詢條件, 傳回字段)
find_one(查詢條件,傳回字段)


普通查詢方法有以下3種寫法。
content = collection.find() 
content = collection.find({'age': 29})
content = collection.find({'age': 29}, {'_id': 0, 'name': 1, 'salary': 1})
           

(4)邏輯查詢

PyMongo也支援大于、小于、大于等于、小于等于、等于、不等于這類邏輯查詢。

collection.find({'age': {'$gt': 29}}) #查詢所有age > 29的記錄
collection.find({'age': {'$gte': 29, '$lte': 40}})  #查詢29 ≤ age ≤ 40的記錄
collection.find({'salary': {'$ne: 29}}) #查詢所有salary不等于29的記錄
           

(5)對查詢結果排序

# MongoDB支援對查詢到的結果進行排序。排序的方法為sort()。它的格式為:
handler.find().sort('列名', 1或-1)

# 查詢一般和find()配合在一起使用。例如:
collection.find({'age': {'$gte': 29, '$lte': 40}}).sort('age', -1)
collection.find({'age': {'$gte': 29, '$lte': 40}}).sort('age', 1)
           

(6)更新記錄

更新可使用update_one()和update_many()方法。它們的格式為:

collection.update_one(參數1, 參數2)
collection.update_many(參數1, 參數2)

           

(7)删除記錄

删除可使用delete_one()和delete_many()方法。它們的格式為:

collection.delete_one(參數)
collection.delete_many(參數)

           

(8)對查詢結果去重

去重使用distinct()方法,其格式為:

collection.distinct('列名')

           
設計一個開關

思考一個問題:如何設計一個開關,實作在不結束程式程序的情況下,從全世界任何一個有網絡的地方既能随時暫停程式,又能随時恢複程式的運作。

最簡單的方法就是用資料庫來實作。在能被程式和控制者通路的伺服器中建立一個資料庫,資料庫名為“Switch_DB”。資料庫裡面建立一個集合“Switch”,這個集合裡面隻有一個記錄,就是“Status”,它隻有兩個值,“On”和“Off”,

在Mac OS下安裝Redis
brew update
brew install redis
#運作Redis
redis-server /usr/local/etc/redis.conf
           
Redis互動環境的使用
redis-cli
           
常見操作

keys *可以檢視目前有多少的“Key”。

在爬蟲開發的過程中主要會用到Redis的清單與集合

(1)清單

Redis的清單是一個可讀可寫的雙向隊列

lpush key value1 value2 value 3…
           

如果想檢視一個清單的長度,可使用關鍵字為“llen”。這個關鍵字的第1個“l”對應的是英文“list”(清單)的首字母。

如果不删除清單中的資料,又要把資料讀出來,就需要使用關鍵字“lrange”,這裡的“l”對應的是英文“list”的首字母。”lrange”的使用格式為:

lrange key start end
# 其中,start為起始位置,end為結束位置。例如:
lrange chapter_6 0 3

# 需要特别注意的是,在Python中,切片是左閉右開區間,例如,test[0:3]表示讀清單的第0、1、2個共3個值。但是lrange的參數是一個閉區間,包括開始,也包括結束,是以在圖6-35中會包含下标為0、1、2、3的4個值。
           

(2)集合

Redis的集合與Python的集合一樣,沒有順序,值不重複。往集合中添加資料,使用的關鍵字為“sadd”。這裡的“s”對應的是英文單詞“set”(集合)。使用格式為:

sadd key value1 value2 value3
           
安裝Redis-py
pip install redis
           
MongoDB的優化建議

少讀少寫少更新

  • 建議把要插入到MongoDB中的資料先統一放到一個清單中,等積累到一定量再一次性插入。
  • 對于讀資料,在記憶體允許的情況下,應該一次性把資料讀入記憶體,盡量減少對MongoDB的讀取操作。
  • 在某些情況下,更新操作不得不逐條進行。建議,把更新這個動作改為插入。這樣就可以實作批量更新的效果了。具體來說,就是把資料批量插入到一個新的MongoDB集合中,再把原來的集合删除,最後将新的集合改為原來集合的名字。
能用Redis就不用MongoDB

為了提高效率,就需要引入Redis。由于Redis是基于記憶體的資料庫,是以即使頻繁對其讀/寫,對性能的影響也遠遠小于頻繁讀/寫MongoDB。在Redis中建立一個集合“crawled_url”,爬蟲在爬一個網址之前,先把這個網址sadd到這個集合中。如果傳回為1,那麼表示這個網址之前沒有爬過,爬蟲需要去爬取詳情頁。如果傳回0,表示這個網址之前已經爬過了,就不需要再爬了。示例代碼片段如下:

for url in url_list: #url_list為在貼吧清單頁得到的每一個文章的詳情頁網址清單
    if client.sadd('crawled_url', url) == 1:
        crawl(url)
           
練習

目标網站:http://dongyeguiwu.zuopinj.com/5525/。

目标内容:小說《白夜行》第一章到第十三章的正文内容。

任務要求:編寫兩個爬蟲,爬蟲1從http://dongyeguiwu.zuopinj.com/ 5525/擷取小說《白夜行》第一章到第十三章的網址,并将網址添加到Redis裡名為url_queue的清單中。爬蟲2從Redis裡名為url_queue的清單中讀出網址,進入網址爬取每一章的具體内容,再将内容儲存到MongoDB中。

# 1 使用XPath擷取每一章的網址,再将它們添加到Redis中。其核心代碼如下:
url_list = selector.xpath('//div[@class="book_list"]/ul/li/a/@href')
for url in url_list:
    client.lpush('url_queue', url)

# 2 對于爬取正文的爬蟲,隻要發現Redis裡的url_queue這個清單不為空,就要從裡面讀出網址,并爬取資料。是以,其代碼如下:
content_list = []
while client.llen('url_queue') > 0:
    url = client.lpop('url_queue').decode()
    source = requests.get(url).content
 
selector = html.fromstring(source)
chapter_name = selector.xpath('//div[@class="h1title"]/h1/text()')[0]
content = selector.xpath('//div[@id="htmlContent"]/p/text()')
content_list.append({'title': chapter_name, 'content': '\n'.join(content)})
handler.insert(content_list)


           
調試與運作

爬蟲1運作結束以後,Redis中應該會出現一個名為url_queue的清單,執行以下代碼:

llen url_queue

           

爬蟲2運作結束以後,Redis中的url_queue會消失,同時MongoDB中會儲存小說每一章的内容。

小結

本章主要講解了MongoDB與Redis的使用。其中,MongoDB主要用來存放爬蟲爬到的各種需要持久化儲存的資料,而Redis則用來存放各種中間資料。

通過減少頻繁讀/寫MongoDB,并使用Redis來彌補MongoDB的一些不足,可以顯著提高爬蟲的運作效率。

動手實踐

如果爬蟲1把10000個網址添加到url_queue中,爬蟲2同時運作在3台計算機上,請觀察能實作什麼效果。

第7章 異步加載與請求頭

知識點
  • 抓取異步加載的資料。
  • 僞造HTTP請求頭。
  • 模拟浏覽器擷取網站資料。
AJAX版登入頁面的爬取

通過POST送出請求解決了AJAX版登入頁面的爬取

小結

本章主要介紹了使用爬蟲擷取異步加載網頁的各種方法。對于普通的異步加載,可以使用requests直接發送AJAX請求來擷取被加載的内容。

發送的請求中可能包含一些特殊的值,這些值來自網頁源代碼或者另一個AJAX請求。

在發送請求時需要注意,應保持requests送出的請求頭與浏覽器的請求頭一緻,這樣才能更好地騙過網站伺服器達到擷取資料的目的。

對于比較複雜的異步加載,現階段可以先使用Selenium和ChromeDriver來直接加載網頁,然後就能從被加載的網頁中直接擷取到需要的内容。

第8章 模拟登入與驗證碼

知識點
  • 使用Selenium操作浏覽器實作自動登入網站。
  • 使用Cookies登入網站。
  • 模拟表單登入網站。
  • 爬蟲識别簡單的驗證碼。
模拟登入有多種實作方法
  • 使用Selenium操作浏覽器登入
  • 使用Cookies登入雖然簡單粗暴
  • 使用模拟送出表單登入雖然較為麻煩,但可以實作自動化
使用Cookies登入

Cookie是使用者使用浏覽器通路網站的時候網站存放在浏覽器中的一小段資料。Cookie的複數形式Cookies用來表示各種各樣的Cookie。它們有些用來記錄使用者的狀态資訊;有些用來記錄使用者的操作行為;還有一些,具有現代網絡最重要的功能:記錄授權資訊——使用者是否登入以及使用者登入哪個賬号。

為了不讓使用者每次通路網站都進行登入操作,浏覽器會在使用者第一次登入成功以後放一段加密的資訊在Cookies中。下次使用者通路,網站先檢查Cookies有沒有這個加密資訊,如果有并且合法,那麼就跳過登入操作,直接進入登入後的頁面。

使用Cookie來登入網頁,不僅可以繞過登入步驟,還可以繞過網站的驗證碼。

使用了requests的Session子產品。

所謂Session,是指一段會話。網站會把每一個會話的ID(Session ID)儲存在浏覽器的Cookies中用來辨別使用者的身份。requests的Session子產品可以自動儲存網站傳回的一些資訊。其實在前面章節中使用的requests.get(),在底層還是會先建立一個Session,然後用Session去通路。

對于HTTPS的網站,在requests發送請求的時候需要帶上verify=False這個參數,否則爬蟲會報錯。

帶上這個參數以後,爬蟲依然會報一個警告,這是因為沒有HTTPS的證書。

對于HTTPS的網站,在requests發送請求的時候需要帶上verify=False這個參數,否則爬蟲會報錯。

模拟表單登入
  • 通過POST送出請求解決了AJAX版登入頁面的爬取。
  • 但是在現實中,有更多的網站是使用表單送出的方式來進行登入的。

使用requests的Session子產品來模拟這個登入

驗證碼 - 肉眼打碼

1.借助浏覽器

在模拟登入中講到過Cookies,通過Cookies能實作繞過登入,進而直接通路需要登入的網站。是以,對于需要輸入驗證碼才能進行登入的網站,可以手動在浏覽器登入網站,并通過Chrome擷取Cookies,然後使用Cookies來通路網站。這樣就可以實作人工輸入一次驗證碼,然後很長時間不再登入。

2.不借助浏覽器

對于僅僅需要識别圖檔的驗證碼,可以使用這種方式——先把驗證碼下載下傳到本地,然後肉眼去識别并手動輸入給爬蟲。

驗證碼 - 自動打碼

1.Python圖像識别

Python的強大,在于它有非常多的第三方庫。 對于驗證碼識别,Python也有現成的庫來使用。開源的OCR庫pytesseract配合圖像識别引擎tesseract,可以用來将圖檔中的文字轉換為文本。這種方式在爬蟲中的應用并不多見。因為現在大部分的驗證碼都加上了幹擾的紋理,已經很少能用單機版的圖檔識别方式來識别了。是以如果使用這種方式,隻有兩種情況:網站的驗證碼極其簡單工整,使用大量的驗證碼來訓練tesseract。

(1)安裝tesseract

brew install tesseract

           

(2)安裝Python庫

要使用tesseract來進行圖像識别,需要安裝兩個第三方庫:

pip install Pillow

pip install pytesseract

# 其中,Pillow是Python中專門用來處理圖像的第三方庫,pytesseract是專門用來操作tesseract的第三方庫。
           

(3)tesseract的使用

① 導入pytesseract和Pillow。

② 打開圖檔。

③ 識别。

import pytesseract
from PIL import Image
image = Image.open('驗證碼.png')
code = pytesseract.image_to_string(image)
print(code)
           

2.打碼網站

(1)打碼網站介紹

線上驗證碼識别的網站,簡稱打碼網站。這些網站有一些是使用深度學習技術識别驗證碼,有一些是雇傭了很多人來人肉識别驗證碼。

網站提供了接口來實作驗證碼識别服務。使用打碼網站理論上可以識别任何使用輸入方式來驗證的驗證碼。

(2)使用線上打碼

在百度或者谷歌上面搜尋“驗證碼線上識别”,就可以找到很多提供線上打碼的網站。但是由于一般這種打碼網站是需要交費才能使用的,是以要注意财産安全。

雲打碼

練習:自動登入果殼網

目标網站:https://www.guokr.com。

目标内容:個人資料設定界面源代碼。

使用模拟登入與驗證碼識别的技術實作自動登入果殼網。 果殼網的登入界面有驗證碼,請使用人工或者線上打碼的方式識别驗證碼,并讓爬蟲登入。登入以後可以正确顯示“個人資料設定”界面的源代碼。

涉及的知識點:

  • 爬蟲識别驗證碼。
  • 爬蟲模拟登入。
小結

本章主要講授了模拟登入與驗證碼識别。使用Selenium實作模拟登入最為簡單。但是這種方式的弊端是運作速度慢。

使用Cookies登入可以實作一次手動、長期自動的目的。

而模拟表單登入本質就是發起POST請求來進行登入,需要使用Session子產品來儲存登入資訊。

驗證碼識别主要是使需輸入的驗證碼實作自動化。包括手動輸入與線上打碼。對于單擊、拖動的驗證碼,建議使用Cookies來進行登入。

第9章 抓包與中間人爬蟲

知識點
  • 使用Charles抓取App和微信小程式的資料包。
  • 使用mitmproxy開發中間人爬蟲。
資料抓包

所謂抓包(Package Capture),簡單來說,就是在網絡資料傳輸的過程中對資料包進行截獲、檢視、修改或轉發的過程。

如果把網絡上發送與接收的資料包了解為快遞包裹,那麼在快遞運輸的過程中檢視裡面的内容,這就是抓包。

Charles

要簡化尋找資料的過程,就需要設法直接全局搜尋網頁的所有請求的傳回資料。

為了實作這個目的,就需要使用Charles。Charles是一個跨平台的HTTP抓包工具。使用它可以像Chrome一樣截取HTTP或者HTTPS請求的資料包。

抓取HTTPS資料包

用Charles抓取HTTPS資料包時的請求會大量失敗。出現這種情況,是因為沒有安裝SSL證書導緻的。

第一步,安裝好證書:要安裝SSL證書,可選擇菜單欄的“Help”- “SSL Proxying”-“Install Charles Root Certificate”指令。

第二步,設定SSL代理:安裝好證書以後,選擇菜單欄中的“Proxy”-“SSL Proxying Settings”指令打開SSL代理設定對話框。

iOS系統的配置和使用

對于蘋果裝置,首先要保證計算機和蘋果裝置聯在同一個Wi-Fi上。選擇Charles菜單欄中的“Help”-“Local IP Address”指令,此時彈出一個對話框,顯示目前計算機的内網IP位址。

接下來設定手機。進入系統設定,選擇“無線區域網路”,然後單擊已經連接配接的這個Wi-Fi熱點右側的圓圈包圍的字母i的圖示。

第一步,在手機上設定HTTP代理。

第二步,使用iOS系統自帶的Safari浏覽器通路https://chls.pro/ssl。安裝證書。

第三步,證書信任設定

Android的配置和使用

将Charles的證書儲存到計算機桌面

微信小程式爬蟲

小程式的請求極其簡單,基本上沒有驗證資訊,即便有驗證資訊也非常脆弱。

用Python來請求小程式的背景接口進而擷取資料,比請求異步加載網頁的背景接口要容易很多。

在爬蟲開發過程中,如果目标網站有微信小程式,那麼一定要優先調查能否通過小程式的接口來抓取資料。

小程式的反爬蟲能力比網頁版的低很多。使用小程式的接口來爬資料,能極大提高爬蟲的開發效率。

Charles的局限
  • Charles隻能截獲HTTP和HTTPS的資料包,如果網站使用的是websocket或者是flashsocket,那麼Charles就無能為力。
  • 有一些App會自帶證書,使用其他證書都無法正常通路背景接口。在這種情況下,Charles自帶的證書就不能正常使用,也就沒有辦法抓取這種App的資料。
  • 有一些App的資料經過加密,App接收到資料以後在其内部進行解密。
  • 對于這種情況,Charles隻能抓取到經過加密的資料。如果無法知道資料的具體加密方法,就沒有辦法解讀Charles抓取到的資料。
中間人爬蟲

中間人(Man-in-the-Middle,MITM)攻擊是指攻擊者與通信的兩端分别建立獨立的聯系,并交換其所收到的資料,使通信的兩端認為其正在通過一個私密的連接配接與對方直接對話,但事實上整個會話都被攻擊者完全控制。

在中間人攻擊中,攻擊者可以攔截通信雙方的通話,并插入新的内容或者修改原有内容。

例如:上課傳紙條

中間人爬蟲就是利用了中間人攻擊的原理來實作資料抓取的一種爬蟲技術。

資料抓包就是中間人爬蟲的一個簡單應用。是以使用Charles也是一種中間人攻擊。

mitmproxy的介紹和安裝

要安裝mitmproxy,首先需要保證系統的Python為Python 3.5或者更高版本

brew install mitmproxy
           
mitmproxy使用
  • 運作mitmproxy會彈出對話框詢問
mitmproxy
           
  • mitmproxy的端口為8080端口,在浏覽器或者在手機上設定代理,代理IP為計算機IP,端口為8080端口。
  • 設定好代理以後,在手機上打開一個App或者打開一個網頁,可以看到mitmproxy上面有資料滾動。
  • 此時隻能通路HTTP網站,要通路HTTPS網站,還需要安裝mitmproxy的證書。在手機設定了mitmproxy的代理以後,通過手機浏覽器通路http://mitm.it/這個網址。
使用Python定制mitmproxy

mitmproxy的強大之處在于它還自帶一個mitmdump指令。這個指令可以用來運作符合一定規則的Python腳本,并在Python腳本裡面直接操作HTTP和HTTPS的請求,以及傳回的資料包。

為了自動化地監控網站或者手機發出的請求頭部資訊和Body資訊,并接收網站傳回的頭部資訊和Body資訊,就需要掌握如何在Python腳本中獲得請求和傳回的資料包。

mitmdump的使用場景
  • 網站傳回的Headers中經常有Cookies。
  • mitmdump的腳本使用print()函數把Cookies列印出來,然後通過管道傳遞給另一個普通的正常的Python腳本。
  • 在另一個腳本裡面,得到管道傳遞進來的Cookies,再把它放進Redis裡面。
需求分析

目标App:Keep。

目标内容:Keep是當下熱門的健身App,本次案例的目的是要使用抓包的方式爬取Keep的熱門動态。

涉及的知識點:

  • 使用Charles或者mitmproxy抓包。
  • 開發App爬蟲。
小結
  • 抓包是爬蟲開發過程中非常有用的一個技巧。使用Charles,可以把爬蟲的爬取範圍從網頁瞬間擴充到手機App和微信小程式。
  • 由于微信小程式的反爬蟲機制在大多數情況下都非常脆弱,是以如果目标網站有微信小程式,那麼可以大大簡化爬蟲的開發難度。
  • 當然,網站有可能會對接口的資料進行加密,App得到密文以後,使用内置的算法進行解密。對于這種情況,單純使用抓包就沒有辦法處理了,就需要使用下一章所要講到的技術來解決。
  • 使用mitmproxy可以實作爬蟲的全自動化操作。
  • 對于擁有複雜參數的網站,使用這種先抓包再送出的方式可以在一定程度上繞過網站的反爬蟲機制,進而實作資料抓取。

第10章 Android原生App爬蟲

知識點

那麼有沒有什麼辦法可以做到幾乎毫無痕迹地爬取資料呢?答案是有。當然可能有讀者會認為可以使用Selenium + ChromeDriver。這種方式隻能操作網頁。本章将要介紹針對Android原生App的爬蟲。

  • Android測試環境的搭建。
  • 使用Python操作Android手機。
  • 使用Python操作Android手機實作爬蟲。
實作原理

目前,Android App主要有兩種實作形式。第一種是Android原生App。這種App的全部或者大部分内容使用Android提供的各個接口來開發,例如Android版的微信就是一個Android原生的App。第二種是基于網頁的App。這種App本質上就是一個浏覽器,裡面的所有内容實際上都是網頁。例如,12306的App就是這樣一種基于網頁的App。

Android原生App爬蟲(以下簡稱App爬蟲)可以直接讀取Android原生App上面的文本資訊。

UI Automator Viewer

設定好環境變量以後,在終端視窗輸入“uiautomatorviewer”并按Enter鍵,如果彈出UI Automator Viewer視窗,表明環境設定成功。

Android手機連接配接到計算機上,保持手機螢幕為亮起狀态,單擊UI Automator Viewer左上角檔案夾右側的手機圖示,如果能夠看到手機螢幕出現在視窗中,則表示一切順利,環境搭建成功完成。如果在這個過程中手機彈出了任何警告視窗,都選擇“運作”或者“确定”。

使用Python操縱手機
pip install uiautomator
           

有一點需要特别說明,UI Automator Viewer與Python uiautomator不能同時使用。

與Selenium一樣,要操作手機上面的元素,首先要找到被操作的東西。以打開微信為例,首先翻到有微信的那一頁

from uiautomator import Device
device = Device()
device(text='微信').click()
           

如果計算機上面隻連接配接了一台Android手機,那麼初始化裝置連接配接隻需要使用device = Device()即可。那麼如果計算機上連接配接了很多台手機,該怎麼辦呢?此時就需要指定手機的串号。要檢視手機串号,需要在終端輸入以下指令:

adb devices -l
           

從輸出的内容可以看到手機的串号

選擇器

如何知道有哪些選擇器可供使用呢?請執行以下代碼:

from uiautomator import Device
device = Device()
print(device.dump())
           

此時終端會以XML輸出目前手機螢幕顯示的視窗布局資訊。

這裡的XML就相當于網頁中的HTML,用來描述視窗上面各個部分的布局資訊。

XML的格式與HTML非常像,格式為:<标簽 屬性1=“屬性值1” 屬性2=“屬性值2”>文本</标簽>

操作
  • 獲得螢幕文字;
  • 滾動螢幕;
  • 滑動螢幕;
  • 點選螢幕;
  • 輸入文字;
  • 判斷元素是否存在;
  • 點亮關閉螢幕;
  • 操作實體按鍵;
  • watcher。
多裝置應用(群控)

使用uiautomator來做爬蟲開發,最主要的瓶頸在于速度。因為螢幕上的元素加載是需要時間的,這個時間受到手機性能和網速的多重限制。是以比較好的辦法是使用多台Android手機實作分布式抓取。使用USBHub擴充計算機的USB口以後,一台計算機控制30台Android手機是完全沒有問題的。隻要能實作良好的排程和任務派分邏輯,就可以大大提高資料的抓取效率。

App爬蟲系統架構的形式
Python爬蟲開發從入門到實戰
練習:BOSS直聘爬蟲

任務目标:BOSS直聘App。

BOSS直聘是一個招聘App,在上面可以看到很多的工作。 App職位清單如圖10-44所示。

使用uiautomator開發一個爬蟲,從手機上爬取每一個職位的名稱、薪資、招聘公司、公司位址、工作經驗要求和學曆。

小結
  • 本章主要講解了如何通過Python操作手機來擷取Android原生App中的文字内容。Python使用uiautomator這個第三方庫來操作Android手機的UiAutomator,進而實作模拟人們對手機螢幕的任何操作行為,并直接讀取螢幕上的文字。
  • 使用uiautomator來開發爬蟲,要打通流程非常簡單。但是需要特别注意處理各種異常的情況。同時,由于手機速度的問題,應該使用多台手機構成一個叢集來提高抓取的速率。
  • 最後,如果使用手機叢集來進行資料抓取,并且需要抓取的App資料來自網絡,那麼需要考慮無線路由器的負荷。當同時連接配接無線路由器的裝置超過一定數量時,有可能導緻部分甚至所有裝置都無法上網。這就需要使用工業級路由器來緩解。
  • 無線信号互相幹擾也是一個比較麻煩的問題。使用5G信道能緩解,但一般便宜的Android手機不支援5G的Wi-Fi信道,此時能做的就是把手機盡量分開,不要放在一起。使用電磁屏蔽網,将每10個手機和一個無線路由器為一組包裹起來,也能起到一定的阻隔Wi-Fi信号的作用。
練習

使用Android手機來爬取一個原生App的資料。

第11章 Scrapy

知識點
  • 在Windows、Mac OS和Linux下搭建Scrapy環境。
  • 使用Scrapy擷取網絡源代碼。
  • 在Scrapy中通過XPath解析資料。
  • 在Scrapy中使用MongoDB。
  • 在Scrapy中使用Redis。
在Mac OS下安裝Scrapy
pip install scrapy
           
建立項目
$ scrapy startproject offcn
# offcn 是項目名

$ cd offcn
$ scrapy genspider jobbank zw.offcn.com
# jobbank 是爬蟲名
# zw.offcn.com 是爬取的網址

# 運作
$ scrapy crawl jobbank
           
那麼如何使用PyCharm來運作或者調試Scrapy的爬蟲呢?

為了實作這個目的,需要建立另外一個Python檔案。檔案名可以取任意合法的檔案名。這裡以“main.py”為例。

main.py檔案内容如下:

from scrapy import cmdline
cmdline.execute("scrapy crawl jobbank".split())
           
Scrapy的工程結構
  • spiders檔案夾:存放爬蟲檔案的檔案夾。
  • items.py:定義需要抓取的資料。
  • pipelines.py:負責資料抓取以後的處理工作。
  • settings.py:爬蟲的各種配置資訊。

但是為什麼還有items.py和pipelines.py這兩個檔案呢?這是由于Scrapy的理念是将資料爬取和資料處理分開。

items.py檔案用于定義需要爬取哪些内容。

pipelines.py檔案用于對資料做初步的處理,包括但不限于初步清洗資料、存儲資料等。在pipelines中可以将資料儲存到MongoDB。

Scrapy與MongoDB

一個Scrapy工程可以有多個爬蟲;再看items.py檔案,可以發現,在一個items.py裡面可以對不同的爬蟲定義不同的抓取内容Item。

接下來設定pipelines.py。在這個檔案中,需要寫出将資料儲存到MongoDB的代碼。而這裡的代碼,就是最簡單的初始化MongoDB的連接配接,儲存資料。

Scrapy與Redis

Scrapy是一個分布式爬蟲的架構,如果把它像普通的爬蟲一樣單機運作,它的優勢将不會被展現出來。

是以,要讓Scrapy往分布式爬蟲方向發展,就需要學習Scrapy與Redis的結合使用。Redis在Scrapy的爬蟲中作為一個隊列存在。

使用Redis緩存網頁并自動去重

pip install scrapy_redis
           
小結

本章主要講了Python分布式爬蟲架構Scrapy的安裝和使用。

Scrapy在Windows中的安裝最為煩瑣,在Mac OS中的安裝最為簡單。

由于Scrapy需要依賴非常多的第三方庫檔案,是以建議無論使用哪個系統安裝,都要将Scrapy安裝到Virtualenv建立的虛拟Python環境中,進而避免影響系統的Python環境。

使用Scrapy爬取網頁,最核心的部分是建構XPath。而其他的各種配置都是一次配好、終生使用的。由于Scrapy的理念是将資料抓取的動作和資料處理的動作分開,是以對于資料處理的各種邏輯應該讓pipeline來處理。資料抓取的部分隻需要關注如何使用XPath提取資料。資料提取完成以後,送出給pipeline處理即可。

由于Scrapy爬蟲爬取資料是異步操作,是以從一個頁面跳到另一個頁面是異步的過程,需要使用回調函數。

Scrapy爬取到的資料量非常大,是以應該使用資料庫來儲存。使用MongoDB會讓資料儲存工作變得非常簡單。

要讓Scrapy使用MongoDB,隻需要在pipeline中配置好資料儲存的流程,再在settings.py中配置好ITEM_PIPELINES和MongoDB的資訊即可。

使用Redis做緩存是從爬蟲邁向分布式爬蟲的一個起點。

Scrapy安裝scrapy_redis元件以後,就可以具備使用Redis的能力。

在Scrapy中使用Redis需要修改爬蟲的父類,需要在settings.py中設定好爬蟲的排程和去重。

同時對于Python 3,需要修改scrapy_redis的一行代碼,才能讓爬蟲正常運作。

練習

請完成網站爬蟲開發,并實作爬蟲的翻頁功能,進而可以爬到1~5頁所有的文章。下一頁的URL請使用爬蟲從目前頁面擷取,切勿根據URL的規律手動構造。

提示,對于翻頁功能,實際上相當于将回調函數的函數名寫成它自己的: callback=self.parse。parse方法可自己調用自己,不過傳入的URL是下一頁的URL。這有點像遞歸,不過遞歸用的是return,而這裡用的是yield。

請思考一個問題,在請求文章詳情頁的時候,設定了請求頭,但是Scrapy請求文章清單頁的時候,在哪裡設定請求頭?請求清單頁的時候,爬蟲直接從Redis得到網址,自動發起了請求,完全沒讓開發者自己設定請求頭。這其實是一個非常大的隐患,因為不設定請求頭,網站立刻就能知道這個請求來自Scrapy,這是非常危險的。

請讀者查詢scrapy_redis的文檔,檢視如何使用make_requests_ from_url(self, url)這個方法。

第12章 Scrapy進階應用

知識點
  • 開發Scrapy中間件。
  • 使用Scrapyd部署爬蟲。
  • Nginx的安裝和反向代理。
  • 了解爬蟲的分布式架構。
Middleware

中間件是Scrapy裡面的一個核心概念。使用中間件可以在爬蟲的請求發起之前或者請求傳回之後對資料進行定制化修改,進而開發出适應不同情況的爬蟲。

“中間件”這個中文名字和前面章節講到的“中間人”隻有一字之差。它們做的事情确實也非常相似。中間件和中間人都能在中途劫持資料,做一些修改再把資料傳遞出去。不同點在于,中間件是開發者主動加進去的元件,而中間人是被動的,一般是惡意地加進去的環節。

中間件主要用來輔助開發,而中間人卻多被用來進行資料的竊取、僞造甚至攻擊。

在Scrapy中有兩種中間件:下載下傳器中間件(Downloader Middleware)和爬蟲中間件(Spider Middleware)。

Downloader Middleware

更換代理IP,更換Cookies,更換User-Agent,自動重試。

加入中間件以後的爬蟲流程
Python爬蟲開發從入門到實戰
開發代理中間件

代理中間件的可用代理清單不一定非要寫在settings.py裡面,也可以将它們寫到資料庫或者Redis中。一個可行的自動更換代理的爬蟲系統,應該有如下的3個功能。

  • 有一個小爬蟲ProxySpider去各大代理網站爬取免費代理并驗證,将可以使用的代理IP儲存到資料庫中。
  • 在ProxyMiddlerware的process_request中,每次從資料庫裡面随機選擇一條代理IP位址使用。
  • 周期性驗證資料庫中的無效代理,及時将其删除。
激活中間件

中間件寫好以後,需要去settings.py中啟動。

Scrapy自帶中間件及其順序編号
Python爬蟲開發從入門到實戰
開發UA中間件

從settings.py配置好的UA清單中随機選擇一項,加入到請求頭中

開發Cookies中間件

對于需要登入的網站,可以使用Cookies來保持登入狀态。那麼如果單獨寫一個小程式,用Selenium持續不斷地用不同的賬号登入網站,就可以得到很多不同的Cookies。由于Cookies本質上就是一段文本,是以可以把這段文本放在Redis裡面。這樣一來,當Scrapy爬蟲請求網頁時,可以從Redis中讀取Cookies并給爬蟲換上。這樣爬蟲就可以一直保持登入狀态。

在中間件中內建Selenium

對于一些很麻煩的異步加載頁面,手動尋找它的背景API代價可能太大。這種情況下可以使用Selenium和ChromeDriver或者Selenium和PhantomJS來實作渲染網頁。

在中間件中內建了Selenium以後,就可以像爬取普通網頁一樣爬取異步加載的頁面

在中間件裡重試

在爬蟲的運作過程中,可能會因為網絡問題或者是網站反爬蟲機制生效等原因,導緻一些請求失敗。在某些情況下,少量的資料丢失是無關緊要的,例如在幾億次請求裡面失敗了十幾次,損失微乎其微,沒有必要重試。但還有一些情況,每一條請求都至關重要,容不得有一次失敗。此時就需要使用中間件來進行重試。

在中間件裡處理異常

在預設情況下,一次請求失敗了,Scrapy會立刻原地重試,再失敗再重試,如此3次。如果3次都失敗了,就放棄這個請求。這種重試邏輯存在一些缺陷。以代理IP為例,代理存在不穩定性,特别是免費的代理,差不多10個裡面隻有3個能用。而現在市面上有一些收費代理IP提供商,購買他們的服務以後,會直接提供一個固定的網址。

把這個網址設為Scrapy的代理,就能實作每分鐘自動以不同的IP通路網站。如果其中一個IP出現了故障,那麼需要等一分鐘以後才會更換新的IP。在這種場景下,Scrapy自帶的重試邏輯就會導緻3次重試都失敗。

這種場景下,如果能立刻更換代理就立刻更換;如果不能立刻更換代理,比較好的處理方法是延遲重試。而使用Scrapy_redis就能實作這一點。

爬蟲的請求來自于Redis,請求失敗以後的URL又放回Redis的末尾。

一旦一個請求原地重試3次還是失敗,那麼就把它放到Redis的末尾,這樣Scrapy需要把Redis清單前面的請求都消費以後才會重試之前的失敗請求。這就為更換IP帶來了足夠的時間。

下載下傳器中間件功能總結

能在中間件中實作的功能,都能通過直接把代碼寫到爬蟲中實作。使用中間件的好處在于,它可以把資料爬取和其他操作分開。在爬蟲的代碼裡面專心寫資料爬取的代碼;在中間件裡面專心寫突破反爬蟲、登入、重試和渲染AJAX等操作。

對團隊來說,這種寫法能實作多人同時開發,提高開發效率;對個人來說,寫爬蟲的時候不用考慮反爬蟲、登入、驗證碼和異步加載等操作。另外,寫中間件的時候不用考慮資料怎樣提取。一段時間隻做一件事,思路更清晰。

爬蟲中間件

爬蟲中間件的用法與下載下傳器中間件非常相似,隻是它們的作用對象不同。

下載下傳器中間件的作用對象是請求request和傳回response;爬蟲中間鍵的作用對象是爬蟲,更具體地來說,就是寫在spiders檔案夾下面的各個檔案。

Scrapy的資料流圖
Python爬蟲開發從入門到實戰

其中,4、5表示下載下傳器中間件,6、7表示爬蟲中間件。爬蟲中間件會在以下幾種情況被調用。

  • 當運作到yield scrapy.Request()或者yield item的時候,爬蟲中間件的process_spider_output()方法被調用。
  • 當爬蟲本身的代碼出現了Exception的時候,爬蟲中間件的process_spider_exception()方法被調用。
  • 當爬蟲裡面的某一個回調函數parse_xxx()被調用之前,爬蟲中間件的process_spider_input()方法被調用。
  • 當運作到start_requests()的時候,爬蟲中間件的process_start_ requests()方法被調用。
在中間件處理爬蟲本身的異常

在爬蟲中間件裡面可以處理爬蟲本身的異常。

下載下傳器中間件裡面的報錯一般是由于外部原因引起的,和代碼層面無關。而現在的這種報錯是由于代碼本身的問題導緻的,是代碼寫得不夠周全引起的。

激活爬蟲中間件

在settings.py中,在下載下傳器中間件配置項的上面就是爬蟲中間件的配置項,它預設也是被注釋了的,解除注釋,并把自定義的爬蟲中間件添加進去即可

幾個自帶的爬蟲中間件
Python爬蟲開發從入門到實戰
爬蟲中間件輸入/輸出

在爬蟲中間件裡面還有兩個不太常用的方法,分别為process_ spider_input(response, spider)和process_spider_output(response, result, spider)。其中,process_spider_input(response, spider)在下載下傳器中間件處理完成後,馬上要進入某個回調函數parse_xxx()前調用。

process_spider_output(response, result, output)是在爬蟲運作yield item或者yield scrapy.Request()的時候調用。

在這個方法處理完成以後,資料如果是item,就會被交給pipeline;如果是請求,就會被交給排程器,然後下載下傳器中間件才會開始運作。

是以在這個方法裡面可以進一步對item或者請求做一些修改。這個方法的參數result就是爬蟲爬出來的item或者scrapy.Request()。由于yield得到的是一個生成器,生成器是可以疊代的,是以result也是可以疊代的,可以使用for循環來把它展開。

爬蟲的部署

一般情況下,爬蟲會使用雲伺服器來運作,這樣可以保證爬蟲24h不間斷運作。

FTP:使用FTP來上傳代碼,不僅非常不友善,而且經常出現把方向搞反,導緻本地最新的代碼被伺服器代碼覆寫的問題。

Git:好處是可以進行版本管理,不會出現代碼丢失的問題。但操作步驟多,需要先在本地送出,然後登入伺服器,再從伺服器上面把代碼下載下傳下來。如果有很多伺服器的話,每個伺服器都登入并下載下傳一遍代碼是非常浪費時間的事情。

Docker:好處是可以做到所有伺服器都有相同的環境,部署非常友善。但需要對Linux有比較深入的了解,對新人不友好,上手難度比較大。

為了克服上面的種種問題,本書将會使用Scrapy官方開發的爬蟲部署、運作、管理工具:Scrapyd。

Scrapyd

Scrapyd是Scrapy官方開發的,用來部署、運作和管理Scrapy爬蟲的工具。使用Scrapyd,可以實作一鍵部署Scrapy爬蟲,通路一個網址就啟動/停止爬蟲。Scrapyd自帶一個簡陋網頁,可以通過浏覽器看到爬蟲目前運作狀态或者查閱爬蟲Log。Scrapyd提供了官方API,進而可以通過二次開發實作更多更加複雜的功能。

Scrapyd可以同時管理多個Scrapy工程裡面的多個爬蟲的多個版本。如果在多個雲伺服器上安裝Scrapyd,可以通過Python寫一個小程式,來實作批量部署爬蟲、批量啟動爬蟲和批量停止爬蟲。

pip install scrapyd
           
上傳Scrapy爬蟲的工具

Scrapyd需要安裝到雲伺服器上,如果讀者沒有雲伺服器,或者想在本地測試,那麼可以在本地也安裝一個。

接下來需要安裝scrapyd-client,這是用來上傳Scrapy爬蟲的工具,也是Python的一個第三方庫,使用pip安裝即可:

pip install scrapyd-client
           

這個工具隻需要安裝到本地計算機上,不需要安裝到雲伺服器上

啟動Scrapyd

接下來需要在雲伺服器上啟動Scrapyd。在預設情況下,Scrapyd不能從外網通路,為了讓它能夠被外網通路,需要建立一個配置檔案。除了bind_address這一項外,其他都可以保持預設。bind_address這一項的值設定為目前這台雲伺服器的外網IP位址。配置檔案放好以後,在終端或者CMD中輸入scrapyd并按Enter鍵,這樣Scrapyd就啟動了。

scrapyd
           

此時打開浏覽器,輸入“http://雲伺服器IP位址:6800”格式的位址就可以打開Scrapyd自帶的簡陋網頁

部署爬蟲

Scrapyd啟動以後,就可以開始部署爬蟲了。打開任意一個Scrapy的工程檔案,可以看到在工程的根目錄中,Scrapy已經自動建立了一個scrapy.cfg檔案,打開這個檔案。現在需要把第10行的注釋符号去掉,并将IP位址改為Scrapyd所在雲伺服器的IP位址。

最後,使用終端或者CMD進入這個Scrapy工程的根目錄,執行下面這一行指令部署爬蟲:

scrapyd-deploy
           
Mac OS和Linux系統,啟動/停止爬蟲

curl這個發起網絡請求的自帶工具

在上傳了爬蟲以後,就可以啟動爬蟲了。對于Mac OS和Linux系統,啟動和停止爬蟲非常簡單。要啟動爬蟲,需要在終端輸入下面這一行格式的代碼。

curl http://雲伺服器IP位址:6800/schedule.json -d project=爬蟲工程名 -d spider=爬蟲名
           

如果爬蟲的運作時間太長,希望提前結束爬蟲,那麼可以使用下面格式的指令來實作:

curl http://爬蟲伺服器IP位址:6800/cancel.json -d project=工程名 -d job=爬蟲JOBID(在網頁上可以查詢到)
           

運作以後,相當于對爬蟲按下了Ctrl+C組合鍵的效果

Windows系統,啟動/停止爬蟲

沒有像Mac OS和Linux的終端一樣擁有curl這個發起網絡請求的自帶工具。但既然是發起網絡請求,那麼隻需要借助Python和requests就可以完成。

  • 使用requests發送請求啟動爬蟲
  • 使用requests發送請求關閉爬蟲
在Python中執行指令部署爬蟲

由于部署爬蟲的時候直接執行scrapyd-deploy指令,是以如何使用Python來自動化部署爬蟲呢?其實也非常容易。在Python裡面使用os這個自帶子產品就可以執行系統指令

批量部署

使用Python與requests的好處不僅在于可以幫助Windows實作控制爬蟲,還可以用來實作批量部署、批量控制爬蟲。

假設有一百台雲伺服器,隻要每一台上面都安裝了Scrapyd,那麼使用Python來批量部署并啟動爬蟲所需要的時間不超過1min。

這裡給出一個批量部署并啟動爬蟲的例子,首先在爬蟲的根目錄下建立一個scrapy_template.cfg檔案。

權限管理

為了彌補Scrapyd沒有權限管理系統這一短闆,就需要借助其他方式來對網絡請求進行管控。帶權限管理的反向代理就是一種解決辦法。

Nginx的介紹

Nginx讀作Engine X,是一個輕量級的高性能網絡伺服器、負載均衡器和反向代理。

為了解決Scrapyd的問題,需要用Nginx做反向代理。

正向代理與反向代理

所謂“反向代理”,是相對于“正向代理”而言的。前面章節所用到的代理是正向代理。正向代理幫助請求者(爬蟲)隐藏身份,爬蟲通過代理通路伺服器,伺服器隻能看到代理IP,看不到爬蟲;

反向代理幫助伺服器隐藏身份。使用者隻知道伺服器傳回的内容,但不知道也不需要知道這個内容是在哪裡生成的。

使用Nginx來保護Scrapyd
Python爬蟲開發從入門到實戰

使用Nginx反向代理到Scrapyd以後,Scrapyd本身隻需要開通内網通路即可。

使用者通路Nginx,Nginx再通路Scrapyd,然後Scrapyd把結果傳回給Nginx,Nginx再把結果傳回給使用者。這樣隻要在Nginx上設定登入密碼,就可以間接實作Scrapyd的通路管控了

Nginx的安裝
sudo apt-get install nginx
           

安裝好以後,需要考慮以下兩個問題。

  • 伺服器是否有其他的程式占用了80端口。
  • 伺服器是否有防火牆。

/etc/nginx/sites-available/default檔案,将80全部改成81并儲存

sudo systemctl restart nginx
           
配置反向代理

首先打開/etc/scrapyd/scrapyd.conf,把bind_address這一項重新改為127.0.0.1,并把http_port這一項改為6801。把Scrapyd設定為隻允許内網通路,端口為6801

接下來配置Nginx,在/etc/nginx/sites-available檔案夾下建立一個scrapyd.conf,其内容為:

server {
  listen 6800;
  location / {
      proxy_pass http://127.0.0.1:6801/;
      auth_basic "Restricted";
      auth_basic_user_file /etc/nginx/conf.d/.htpasswd;
    }
  }
           

使用basic auth權限管理方法,對于通過授權的使用者,将它對6800端口的請求轉到伺服器本地的6801端口。需要注意配置裡面的記錄授權檔案的路徑這一項:auth_basic_user_file /etc/nginx/conf.d/.htpasswd

在後面會将授權資訊的記錄檔案放在/etc/nginx/conf.d/.htpasswd這個檔案中。寫好這個配置以後,儲存。

接下來,執行下面的指令,在/etc/nginx/sites-enabled檔案夾下建立一個軟連接配接:

sudo ln -s /etc/nginx/sites-available/scrapyd.conf /etc/nginx/sites-enabled/
           

軟連接配接建立好以後,需要生成賬号和密碼的配置檔案。首先安裝apache2-utils軟體包:

sudo apt-get install apache2-utils
           

安裝完成apache2-utils以後,cd進入/etc/nginx/conf.d檔案夾,并執行指令為使用者kingname生成密碼檔案:

sudo htpasswd -c .htpasswd kingname
           

上面的指令會在/etc/nginx/conf.d檔案夾下生成一個.htpasswd的隐藏檔案。有了這個檔案,Nginx就能進行權限驗證了。

接下來重新開機Nginx:

sudo systemctl restart nginx
           

重新開機完成以後,啟動Scrapyd,再在浏覽器上通路格式為“http://伺服器IP:6800”的網址

配置Scrapy工程

由于為Scrapyd添加了權限管控,是以在12.2.1小節中涉及的部署爬蟲、啟動/停止爬蟲的地方都要做一些小修改。

首先是部署爬蟲,為了讓scrapyd-deploy能成功地輸入密碼,需要修改爬蟲根目錄的scrapy.cfg檔案,添加username和password兩項,其中username對應賬号,password對應密碼。

配置好scrapy.cfg以後,部署爬蟲的指令不變,還是進入這個Scrapy工程的根目錄,執行以下代碼即可:

scrapyd-deploy
           

使用curl啟動/關閉爬蟲,隻需要在指令上加上賬号參數即可。賬号參數為“-u 使用者名:密碼”。

是以,啟動爬蟲的指令為:

curl http://45.76.110.210:6800/schedule.json -d project=工程名 -d spider=爬蟲名 -u kingname:genius

停止爬蟲的指令為:

```shell
curl http://45.76.110.210:6800/cancel.json -d project=工程名 -d job=爬蟲JOBID-u kingname:genius
           

如果使用Python與requests編寫腳本來控制爬蟲,那麼賬号資訊可以作為POST方法的一個參數,參數名為auth,值為一個元組,元組第0項為賬号,第1項為密碼:

result = requests.post(start_url, data=start_data, auth=('kingname', 'genius')).text
result = requests.post(end_url, data=end_data, auth=('kingname', 'genius')).text
           
分布式架構介紹

在前面章節中已經講到了把目标放到Redis裡面,然後讓多個爬蟲從Redis中讀取目标再爬取的架構,這其實就是一種主—從式的分布式架構。使用Scrapy,配合scrapy_redis,再加上Redis,也就實作了一個所謂的分布式爬蟲。

實際上,這種分布式爬蟲的核心概念就是一個中心結點,也叫Master。它上面跑着一個Redis,所有的待爬網站的網址都在裡面。其他雲伺服器上面的爬蟲(Slave)就從這個共同的Redis中讀取待爬網址。

分布式爬蟲架構圖
Python爬蟲開發從入門到實戰

隻要能通路這個Master伺服器,并讀取Redis,那麼其他伺服器使用什麼系統什麼提供商都沒有關系。

例如,使用Ubuntu作為爬蟲的Master,用來做任務的派分。

使用樹莓派、Windows 10 PC和Mac來作為分布式爬蟲的Slave,用來爬取網站,并将結果儲存到Mac上面運作的MongoDB中。

其中,作為Master的Ubuntu伺服器僅需要安裝Redis即可,它的作用僅僅是作為一個待爬網址的臨時中轉,是以甚至不需要安裝Python。

在Mac、樹莓派和Windows PC中,需要安裝好Scrapy,并通過Scrapyd管理爬蟲。由于爬蟲會一直監控Master的Redis,是以在Redis沒有資料的時候爬蟲處于待命狀态。

當目标被放進了Redis以後,爬蟲就能開始運作了。由于Redis是一個單線程的資料庫,是以不會出現多個爬蟲拿到同一個網址的情況。

如何選擇Master

嚴格來講,Master隻需要能運作Redis并且能被其他爬蟲通路即可。但是如果能擁有一個公網IP則更好。這樣可以從世界上任何一個能通路網際網路的地方通路Master。但如果實在沒有雲伺服器,也并不是說一定得花錢買一個,如果自己有很多台計算機,完全可以用一台計算機來作為Master,其他計算機來做Slave。

Master也可以同時是Slave。在第11章的例子中,Scrapy和Redis是安裝在同一台計算機中的。這台計算機既是Master又是Slave。

Master一定要能夠被其他所有的Slave通路。是以,如果所有計算機不在同一個區域網路,那麼就需要想辦法弄到一台具有公網IP的計算機或者雲伺服器。

在中國,大部分情況下,電信營運商配置設定到的IP是内網IP。在這種情況下,即使知道了IP位址,也沒有辦法從外面連進來。

在區域網路裡面,因為區域網路共用一個出口,區域網路内的所有共用同一個公網IP。對網站來說,這個IP位址通路頻率太高了,肯定不是人在通路,進而被網站封鎖的可能性增大。而使用分布式爬蟲,不僅僅是為了提高通路抓取速度,更重要的是降低每一個IP的通路頻率,使網站誤認為這是人在通路。是以,如果所有的爬蟲确實都在同一個區域網路共用一個出口的話,建議為每個爬蟲加上代理。

在實際生産環境中,最理想的情況是每一個Slave的公網IP都不一樣,這樣才能做到既能快速抓取,又能減小被反爬蟲機制封鎖的機會。

小結

本章主要介紹了Scrapy中間件的進階用法和爬蟲的批量部署。

使用中間件可以讓爬蟲專注于提取資料,而像更換IP、擷取登入Session等事情全部都交給中間件來做。這樣可以讓爬蟲本身的代碼更加簡潔,也便于協同開發。

使用Scrapyd來部署爬蟲,可以實作遠端開關爬蟲和批量部署爬蟲,進而實作分布式爬蟲。

第13章 爬蟲開發中的法律和道德問題

小結
  • 在爬蟲開發和資料采集的過程中,閱讀網站的協定可以有效發現并規避潛在的法律風險。
  • 爬蟲在爬取網站的時候控制頻率,善待網站,才能讓爬蟲運作得更長久。