前面老猿我嘮叨了很多內容,都是為今天的實戰做鋪墊。小猿們可能已經等得有些不耐煩了,那麼我們就廢話不多說,馬上幹起來!
這個實戰例子是構建一個大規模的非同步新聞爬蟲,但要分幾步走,從簡單到複雜,循序漸進的來構建這個Python爬蟲。
本教程所有程式碼以Python 3.6實現,不兼顧Python 2,這也是強烈建議猿猿們使用Python 3的良苦用心啊,O(∩_∩)O哈哈哈~
要抓取新聞,首先得有新聞源,也就是抓取的目標網站。國內的新聞網站,從中央到地方,從綜合到垂直行業,大大小小有幾千家新聞網站。百度新聞(news.baidu.com)收錄的大約兩千多家。那麼我們先從百度新聞入手。
開啟百度新聞的網站首頁:news.baidu.com
我們可以看到這就是一個新聞聚合網頁,裡面列舉了很多新聞的標題及其原始連結。如圖所示:
我們的目標就是從這裡提取那些新聞的連結並下載。流程比較簡單:
根據這個簡單流程,我們先實現下面的簡單程式碼:
#!/usr/bin/env python3
# Author: veelion
import re
import time
import requests
import tldextract
def save_to_db(url, html):
# 儲存網頁到資料庫,我們暫時用列印相關資訊代替
print('%s : %s' % (url, len(html)))
def crawl():
# 1. download baidu news
hub_url = 'http://news.baidu.com/'
res = requests.get(hub_url)
html = res.text
# 2. extract news links
## 2.1 extract all links with 'href'
links = re.findall(r'href=[\'"]?(.*?)[\'"\s]', html)
print('find links:', len(links))
news_links = []
## 2.2 filter non-news link
for link in links:
if not link.startswith('http'):
continue
tld = tldextract.extract(link)
if tld.domain == 'baidu':
continue
news_links.append(link)
print('find news links:', len(news_links))
# 3. download news and save to database
for link in news_links:
html = requests.get(link).text
save_to_db(link, html)
print('works done!')
def main():
while 1:
crawl()
time.sleep(300)
if __name__ == '__main__':
main()
簡單解釋一下上面的程式碼:
1. 使用requests下載百度新聞首頁;
2. 先用正規表示式提取a標籤的href屬性,也就是網頁中的連結;然後找出新聞的連結,方法是:假定非百度的外鏈都是新聞連結;
3. 逐個下載找到的所有新聞連結並儲存到資料庫;儲存到資料庫的函式暫時用列印相關資訊代替。
4. 每隔300秒重複1-3步,以抓取更新的新聞。
以上程式碼能工作,但也僅僅是能工作,槽點多得也不是一點半點,那就讓我們一起邊吐槽邊完善這個爬蟲吧。
1. 增加異常處理
在寫爬蟲,尤其是網路請求相關的程式碼,一定要有異常處理。目標伺服器是否正常,當時的網路連線是否順暢(超時)等狀況都是爬蟲無法控制的,所以在處理網路請求時必須要處理異常。網路請求最好設定timeout,別在某個請求耗費太多時間。timeout 導致的識別,有可能是伺服器響應不過來,也可能是暫時的網路出問題。所以,對於timeout的異常,我們需要過段時間再嘗試。
2. 要對伺服器返回的狀態,如404,500等做出處理
伺服器返回的狀態很重要,這決定著我們爬蟲下一步該怎麼做。需要處理的常見狀態有:
- 301, 該URL被永久轉移到其它URL,以後請求的話就請求被轉移的URL
- 404,基本上是這個網站已經失效了,後面也就別試了
- 500,伺服器內部出錯了,可能是暫時的,後面要再次請求試試
3. 管理好URL的狀態
記錄下此次失敗的URL,以便後面再試一次。對於timeout的URL,需要後面再次抓取,所以需要記錄所有URL的各種狀態,包括:
- 已經下載成功
- 下載多次失敗無需再下載
- 正在下載
- 下載失敗要再次嘗試
增加了對網路請求的各種處理,這個爬蟲就健壯多了,不會動不動就異常退出,給後面運維帶來很多的工作量。
下一節我們講對上面三個槽點結合程式碼一一完善。欲知詳情,請聽下回分解。
Python爬蟲知識點
本節中我們用到了Python的幾個模組,他們在爬蟲中的作用如下:
1. requests模組
它用來做http網路請求,下載URL內容,相比Python自帶的urllib.request,requests更加易用。GET,POST信手拈來:
import requests
res = requests.get(url, timeout=5, headers=my_headers)
res2 = requests.post(url, data=post_data, timeout=5, headers=my_headers)
get()和post()函式有很多引數可選,上面用到了設定timeout,自定義headers,更多引數可參考requests 文件。
requests無論get()還是post()都會返回一個Response物件,下載到的內容就透過這個物件獲取:
- res.content 是得到的二進位制內容,其型別是bytes;
- res.text 是二進位制內容content decode後的str內容;
它先從response headers裡面找到encoding,沒找到就透過chardet自動判斷得到encoding,並賦值給res.encoding,最後把二進位制的content解密為str型別。
老猿經驗: res.text判斷中文編碼時有時候會出錯,還是自己透過cchardet(用C語言實現的chardet)獲取更準確。這裡,我們列舉一個例子:
In [1]: import requests
In [2]: r = requests.get('http://epaper.sxrb.com/')
In [3]: r.encoding
Out[3]: 'ISO-8859-1'
In [4]: import chardet
In [5]: chardet.detect(r.content)
Out[5]: {'confidence': 0.99, 'encoding': 'utf-8', 'language': ''}
上面是用ipython互動式直譯器(強烈推薦ipython,比Python自己的直譯器好太多)演示了一下。開啟的網址是山西日報數字報,手動檢視網頁原始碼其編碼是utf8,用chardet判斷得到的也是utf8。而requests自己判斷的encoding是ISO-8859-1,那麼它返回的text的中文也就會是亂碼。
requests還有個好用的就是Session,它部分類似瀏覽器,儲存了cookies,在後面需要登入和與cookies相關的爬蟲都可以用它的session來實現。
2. re模組
正規表示式主要是用來提取html中的相關內容,比如本例中的連結提取。更復雜的html內容提取,推薦使用lxml來實現。
3. tldextract模組
這是個第三方模組,需要pip install tldextract
進行安裝。它的意思就是Top Level Domain extract,即頂級域名提取。前面我們講過URL的結構,news.baidu.com 裡面的news.baidu.com叫做host,它是註冊域名baidu.com的子域名,而com就是頂級域名TLD。它的結果是這樣的:
In [6]: import tldextract
In [7]: tldextract.extract('http://news.baidu.com/')
Out[7]: ExtractResult(subdomain='news', domain='baidu', suffix='com')
返回結構包含三部分:subdomain, domain, suffix
4. time模組
時間,是我們在程式中經常用到的概念,比如,在迴圈中停頓一段時間,獲取當前的時間戳等。而time模組就是提供時間相關功能的模組。同時還有另外一個模組datetime也是時間相關的,可以根據情況適當選擇來用。
記住這幾個模組,在今後的寫爬蟲生涯中將會受益匪淺。
下一篇俺們講:
大規模非同步新聞爬蟲之二
我的公眾號:猿人學 Python 上會分享更多心得體會,敬請關注。
***版權申明:若沒有特殊說明,文章皆是猿人學 yuanrenxue.com 原創,沒有猿人學授權,請勿以任何形式轉載。***