1. 程式人生 > 其它 >Python爬蟲入門(二)

Python爬蟲入門(二)

上一篇文章大概的講解了 Python 爬蟲的基礎架構,我們對 Python 爬蟲內部執行流程有了一定的理解了,我們這節將用一些簡單的 Python 程式碼實現Python 爬蟲架構的 URL 管理器、網頁下載器和網頁解析器。

URL 管理器

上篇文章我們已經說了,URL 管理器是用來管理待抓取的 URL 和已抓取的 URL,作為一隻聰明的爬蟲,我們當然應該會選擇跳過那些我們已經爬取過的 URL ,這不僅是為了防止重複抓取,也為了防止一些迴圈抓取的問題,URL 間的互相呼叫會導致爬蟲的無限死迴圈抓取。

URL 管理器就是為了解決這些問題而存在的,有了它,我們的爬蟲才會更加聰明,從而避免重複抓取和迴圈抓取。上面列出的就是 URL 管理器所要做的工作,根據這些職能,我們就可以總結出實現 URL 管理器的一個大體思路。

我們需要兩個容器 A 和 B,A 用來儲存待爬取的 URL,B 用來儲存已爬取的 URL,管理器從 A 中獲取 URL 來交付給網頁下載器去處理,如果 A 中沒有 URL 就等待,每當爬蟲爬取到新的 URL 的時候,就將這個 URL 新增到 A 中排隊等待爬取。爬取完一個 URL 後,就把這個 URL 存放到 B 中。爬蟲的時候,如果獲取到的 URL 在 A 中或者 B 中存在了,就跳過該 URL。流程圖如下:

以上功能是 URL 管理器需要實現功能的最小功能集合,複雜的情況當然具備的功能也更多。我們來看一下簡單的 URL 管理器記憶體實現程式碼。

class UrlManager(object):

   def __init__(self):
       self.new_urls = set()
       self.old_urls = set()

   def add_new_url(self,url):
       if url is None:
           return
       if url not in self.new_urls and url not in self.old_urls:
           #新增待爬取URL
           self.new_urls.add(url)
   
   def has_new_url(self):
       return len(self.new_urls) != 0

   def get_new_url(self):
       #獲取並移除一個待爬取URL
       new_url = self.new_urls.pop()
       #將URL新增進已爬取URL
       self.old_urls.add(new_url)

上面的程式碼很簡單,我們使用 Python 中的 Set 來作為容器管理 URL,因為它可以自動的進行去重處理而且內部的查詢速度也是非常快速,所以很方便。獲取待爬取 URL 的時候,我們使用 pop 方法,在獲取一個元素的同時將它從 set 中移除出去,從而實現類似佇列的排隊形式。

這就是我們 URL 管理器的一個簡單的實現方式了,雖然很簡單,但基本功能思想已經包含在裡面了。

網頁下載器

網頁下載器是將網際網路上的 URL 對應的網頁下載到本地的工具,當我們從 URL 管理器中獲取到一個爬取 URL 的時候,我們只有將 URL 對應的網頁下載到本地,才能繼續後面的資料處理,所以網頁下載器在爬蟲架構中十分重要,是核心元件。

網頁下載器的執行模式很簡單,它可以將 URL 對應的網頁以 HTML 的形式下載到本地,儲存成一個本地檔案或者以記憶體字串的形式儲存下來。總而言之就是下載一個靜態網頁檔案,檔案內容就是

這樣的標籤組成的 HTML 檔案。

Python 中實現網頁下載器有很多現成並且功能強大的庫可供選擇。urllib 是 Python 官方提供的基礎模組,requests 是一個功能強大的第三方模組,我將使用 Python3 中的 urllib 作為演示。需要注意的是 urllib2 和 Python3 的 urllib 語法區別還是比較大的,大家權益好選擇一個版本來進行學習。

#encoding:UTF-8
import urllib.request

url = "http://www.baidu.com"
data = urllib.request.urlopen(url).read()
data = data.decode('UTF-8')
print(data)

這是 urllib 最簡單的使用方法,我們通過 urlopen 方法讀取一個 URL,並呼叫 read 方法獲取我們剛剛說到的 HTML 記憶體字串,打印出來就是一堆標籤格式的網頁字串了。

urlopen函式返回了一個HTTPResponse物件,這個物件挺有用的,是爬取請求的返回物件,我們可以通過它檢視爬取 URL 請求的狀態,還有一些物件資訊等,比如 getcode 為 200 代表了網路請求成功。

>>> a = urllib.request.urlopen(full_url)

>>> a.geturl()
'http://www.baidu.com/s?word=Jecvay'

>>> type(a)
<class 'http.client.HTTPResponse'>

>>> a.info()
<http.client.HTTPMessage object at 0x03272250>

>>> a.getcode()
200

當然了,我們的網路請求的實際情況是比較複雜的,有時候你需要在請求中新增資料,或者更改一下 header,urllib 的 Request 物件可以滿足這些需求。

import urllib.request

request = urllib.request.Request("http://www.baidu.com")
# 新增請求頭
request.add_header('User-Agent', 'Mozilla/5.0')
# 新增資料
request.data = b"I am a data"

response = urllib.request.urlopen(request)

還有一些更加特殊的場景,比如有的網頁需要 Cookie 處理,有的網頁需要新增網頁代理才能訪問,有的網頁需要輸入賬號密碼驗證,有的網頁需要 HTTPS 協議才能訪問。面對這些比較複雜的場景,urllib 提供了 Handler 這個強大的工具來處理這些需求。

不同的場景有不同的 Handler,比如處理 Cookie 使用 HTTPCookieProcessor ,處理網路代理可以使用 ProxyHandler,使用的時候,我們用 Handler 來構建一個 opener,然後用把 opener 安裝到 request 上,這樣再進行請求的時候,所安裝的 Handler 就會起到處理特殊場景的作用。我們拿輸入使用者密碼這種場景來舉例:

import urllib.request
# 構建基礎HTTP賬號驗證Handler
auth_handler = urllib.request.HTTPBasicAuthHandler()
auth_handler.add_password(realm='PDQ Application',
                         uri='https://mahler:8092/site-updates.py',
                         user='klem',
                         passwd='kadidd!ehopper')
# Handler 構建 opener
opener = urllib.request.build_opener(auth_handler)
# 安裝 opener
urllib.request.install_opener(opener)
urllib.request.urlopen('http://www.example.com/login.html')

我們利用多型構建了一個 HTTP 基本驗證資訊的 Handler,新增好相關的賬號密碼資訊後,構建了一個 opener,並把 opener 安裝到 request 上,在請求一個帶有驗證地址的時候,將會填充我們在 Handler 中填寫的資料。

有關 urllib 的 API 大家可以參考 Python3 官方文件,文件寫的清晰明瞭而且有官方的程式碼示例,我也閱讀過文件,感覺 Python 官方的文件確實非常用心,很舒服。requests 這個第三方庫大家可以自行了解下,剛開始學習的時候,實際上 urllib 基本能滿足你的需求了。

網頁解析器

網頁下載器將網頁下載到本地後,我們需要使用網頁解析器從下載好的本地檔案或者記憶體字串中提取出我們需要的有價值資訊。對於定向爬蟲來說,我們需要從網頁中提取兩個資料,一個是我們需要的價值資料,另外就是該網頁 URL 所能跳轉的 URL 列表,這個列表我們將輸入到 URL 管理器中進行處理。Python 中有以下幾種方式可以實現網頁解析器。

一個就是使用正則表示式,這個方式最為直觀,我們將網頁字串通過正則的模糊匹配的方式,提取出我們需要的價值資料,這種方法雖然比較直觀,但如果網頁複雜,會比較麻煩。但我的建議,大家還是學一下正則表示式,這個畢竟是一門扛把子技術了,至少你得看得懂。

同時推薦大家另一款分析語言 XPATH,它是一門高效的分析語言,語法表達相比正則來說清晰簡單,如果你掌握的好,基本可以替代正則,大家有興趣可以搜尋學習一下哦~

Python 還可以使用 html.parser,lxml,以及第三方庫 BeautifulSoup 來進行網頁解析。BeautifulSoup 本身包含了 html.parser 和 lxml,功能較為強大,它使用結構化解析來解析網頁,結構化解析就是使用 DOM 樹的方式進行上下級元素的遍歷訪問,從而達到解析和訪問 HTML 檔案的目的。一圖理解

我們這裡介紹下 BeautifulSoup,安裝很簡單,有好幾種方法,最簡單的方法就是使用命令 pip install beautifulsoup4 來安裝,一些安裝的流程和一些問題就不在這裡多說了。

介紹下 BeautifulSoup 的使用方法,更加詳細的 API 還是要看官方文件,而且 BS 的文件有友好的國人開發者在進行翻譯,還是非常不錯的~

使用 BS 的流程是,首先建立 BS 物件,傳入對應的網頁字串,並指定相應的解析器(html.parser 或者 lxml),然後使用 find_all 或者 find 函式來進行搜尋節點,最後通過獲取到的節點訪問對應的名稱、屬性或者文字,從而得到你想要的資訊。

舉個例子,現在有這樣一個網頁字串資訊:

<a href='123.html' class='article_link'> python </a>

在這段字串裡,節點的名稱是 a,節點屬性有 href='123.html' 和 class='article_link' 這兩個,節點內容是 python。

有了這三個節點資訊,我們就可以開始進行程式碼的編寫了

from bs4 import BeautifulSoup

# 根據 HTML 網頁字串建立 BS 物件
soup = BeautifulSoup(
                    html_doc,            # HTML 字串
                    'html.parser',       # HTML 解析器
                    from_encoding='utf8')# HTML 編碼

# 查詢所有標籤為a的節點
soup.find_all('a')
# 查詢所有便籤為a,連結符合/view/123.htm形式的節點
soup.find_all('a',href='/view/123.htm')
# 查詢所有標籤為div,class為abc,文字為Python的節點
soup.find_all('div',class_='abc',string='Python')
# 使用正則表示式匹配
soup.find_all('a',href=re.compile(r'/view/d+.htm))

find_all 和 find 使用方法一致,只是 find_all 返回的是一個節點列表。注意到,find 方法是可以使用正則表示式進行模糊匹配的,這是它強大的地方,獲取到節點 node,我們就可以很容易的獲取到節點資訊了。

# 得到節點:<a href='1.html'>Python</a>
# 獲取節點標籤名稱
node.name
# 獲取節點的href屬性
node['href']
# 獲取節點文字
node.get_text()