這個實戰例子是構建一個大規模的異步新聞爬蟲,但要分幾步走,從簡單到複雜,按部就班的來構建這個Python爬蟲html
本教程全部代碼以Python 3.6實現,不兼顧Python 2,強烈建議你們使用Python 3python
要抓取新聞,首先得有新聞源,也就是抓取的目標網站。國內的新聞網站,從中央到地方,從綜合到垂直行業,大大小小有幾千家新聞網站。百度新聞(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()
簡單解釋一下上面的代碼:cookie
1. 使用requests下載百度新聞首頁;
2. 先用正則表達式提取a標籤的href屬性,也就是網頁中的連接;而後找出新聞的連接,方法是:假定非百度的外鏈都是新聞連接;
3. 逐個下載找到的全部新聞連接並保存到數據庫;保存到數據庫的函數暫時用打印相關信息代替。
4. 每隔300秒重複1-3步,以抓取更新的新聞。網絡
以上代碼能工做,但也僅僅是能工做,槽點多得也不是一點半點,那就讓咱們一塊兒邊吐槽邊完善這個爬蟲吧。session
在寫爬蟲,尤爲是網絡請求相關的代碼,必定要有異常處理。目標服務器是否正常,當時的網絡鏈接是否順暢(超時)等情況都是爬蟲沒法控制的,因此在處理網絡請求時必需要處理異常。網絡請求最好設置timeout,別在某個請求耗費太多時間。timeout 致使的識別,有多是服務器響應不過來,也多是暫時的網絡出問題。因此,對於timeout的異常,咱們須要過段時間再嘗試。app
服務器返回的狀態很重要,這決定着咱們爬蟲下一步該怎麼作。須要處理的常見狀態有:
記錄下這次失敗的URL,以便後面再試一次。對於timeout的URL,須要後面再次抓取,因此須要記錄全部URL的各類狀態,包括:
增長了對網絡請求的各類處理,這個爬蟲就健壯多了,不會動不動就異常退出,給後面運維帶來不少的工做量。
下一節咱們講對上面三個槽點結合代碼一一完善。欲知詳情,請聽下回分解。
本節中咱們用到了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.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也是時間相關的,能夠根據狀況適當選擇來用。
記住這幾個模塊,在從此的寫爬蟲生涯中將會受益不淺。