上一節我們實現了一個簡單的再也不能簡單的新聞爬蟲,這個爬蟲有很多槽點,估計小猿們也會鄙視這個爬蟲。上一節最後我們討論了這些槽點,現在我們就來去除這些槽點來完善我們的新聞爬蟲。
問題我們前面已經描述清楚,解決的方法也有了,那就廢話不多講,程式碼立刻上(Talk is cheap, show me the code!)。
downloader 的實現
import requests
import cchardet
import traceback
def downloader(url, timeout=10, headers=None, debug=False, binary=False):
_headers = {
'User-Agent': ('Mozilla/5.0 (compatible; MSIE 9.0; '
'Windows NT 6.1; Win64; x64; Trident/5.0)'),
}
redirected_url = url
if headers:
_headers = headers
try:
r = requests.get(url, headers=_headers, timeout=timeout)
if binary:
html = r.content
else:
encoding = cchardet.detect(r.content)['encoding']
html = r.content.decode(encoding)
status = r.status_code
redirected_url = r.url
except:
if debug:
traceback.print_exc()
msg = 'failed download: {}'.format(url)
print(msg)
if binary:
html = b''
else:
html = ''
status = 0
return status, html, redirected_url
if __name__ == '__main__':
url = 'http://news.baidu.com/'
s, html,lost_url_found_by_大大派 = downloader(url)
print(s, len(html),lost_url_found_by_大大派)
這個downloader()函式,內建了預設的User-Agent模擬成一個IE9瀏覽器,同時接受呼叫者自定義的headers和timeout。使用cchardet
來處理編碼問題,返回資料包括:
- 狀態碼:如果出現異常,設定為0
- 內容: 預設返回str內容。但是URL連結的是圖片等二進位制內容時,注意呼叫時要設binary=True
- 重定向URL: 有些URL會被重定向,最終頁面的url包含在響應物件裡面
新聞URL的清洗
我們先看看這兩個新聞網址:
http://xinwen.eastday.com/a/n181106070849091.html?qid=news.baidu.com
http://news.ifeng.com/a/20181106/60146589_0.shtml?_zbs_baidu_news
上面兩個帶?的網站來自百度新聞的首頁,這個問號?的作用就是告訴目標伺服器,這個網址是從百度新聞連結過來的,是百度帶過來的流量。但是它們的表示方式不完全一樣,一個是qid=news.baidu.com, 一個是_zbs_baidu_news。這有可能是目標伺服器要求的格式不同導致的,這個在目標伺服器的後臺的瀏覽統計程式中可能用得到。
然後去掉問號?及其後面的字元,發現它們和不去掉指向的是相同的新聞網頁。
從字串對比上看,有問號和沒問號是兩個不同的網址,但是它們又指向完全相同的新聞網頁,說明問號後面的引數對響應內容沒有任何影響。
正在抓取新聞的大量實踐後,我們發現了這樣的規律:
新聞類網址都做了大量SEO,它們把新聞網址都靜態化了,基本上都是以.html, .htm, .shtml等結尾,後面再加任何請求引數都無濟於事。
但是,還是會有些新聞網站以引數id的形式動態獲取新聞網頁。
那麼我們抓取新聞時,就要利用這個規律,防止重複抓取。由此,我們實現一個清洗網址的函式。
g_bin_postfix = set([
'exe', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
'pdf',
'jpg', 'png', 'bmp', 'jpeg', 'gif',
'zip', 'rar', 'tar', 'bz2', '7z', 'gz',
'flv', 'mp4', 'avi', 'wmv', 'mkv',
'apk',
])
g_news_postfix = [
'.html?', '.htm?', '.shtml?',
'.shtm?',
]
def clean_url(url):
# 1. 是否為合法的http url
if not url.startswith('http'):
return ''
# 2. 去掉靜態化url後面的引數
for np in g_news_postfix:
p = url.find(np)
if p > -1:
p = url.find('?')
url = url[:p]
return url
# 3. 不下載二進位制類內容的連結
up = urlparse.urlparse(url)
path = up.path
if not path:
path = '/'
postfix = path.split('.')[-1].lower()
if postfix in g_bin_postfix:
return ''
# 4. 去掉標識流量來源的引數
# badquery = ['spm', 'utm_source', 'utm_source', 'utm_medium', 'utm_campaign']
good_queries = []
for query in up.query.split('&'):
qv = query.split('=')
if qv[0].startswith('spm') or qv[0].startswith('utm_'):
continue
if len(qv) == 1:
continue
good_queries.append(query)
query = '&'.join(good_queries)
url = urlparse.urlunparse((
up.scheme,
up.netloc,
path,
up.params,
query,
'' # crawler do not care fragment
))
return url
清洗url的方法都在程式碼的註釋裡面了,這裡麵包含了兩類操作:
- 判斷是否合法url,非法的直接返回空字串
- 去掉不必要的引數,去掉靜態化url的引數
網路爬蟲知識點
1. URL清洗
網路請求開始之前,先把url清洗一遍,可以避免重複下載、無效下載(二進位制內容),節省伺服器和網路開銷。
2. cchardet 模組
該模組是chardet的升級版,功能和chardet完全一樣,用來檢測一個字串的編碼。由於是用C和C++實現的,所以它的速度非常快,非常適合在爬蟲中用來判斷網頁的編碼。
切記,不要相信requests返回的encoding,自己判斷一下更放心。上一節,我們已經列舉了一個例子來證明requests對編碼識別的錯誤,如果忘了的話,可以再去回顧一下。
3. traceback 模組
我們寫的爬蟲在執行過程中,會出現各種異常,而且有些異常是不可預期的,也不知道它會出現在什麼地方,我們就需要用try來捕獲異常讓程式不中斷,但是我們又需要看看捕獲的異常是什麼內容,由此來改善我們的爬蟲。這個時候,就需要traceback模組。
比如在downloader()函式里面我們用try捕獲了get()的異常,但是,異常也有可能是cchardet.detect()引起的,用traceback.print_exc()來輸出異常,有助於我們發現更多問題。
本篇我們講了編寫一個更好的網路請求函式,下一篇我們講:
編寫一個好用的URL Pool
我的公眾號:猿人學 Python 上會分享更多心得體會,敬請關注。
***版權申明:若沒有特殊說明,文章皆是猿人學 yuanrenxue.com 原創,沒有猿人學授權,請勿以任何形式轉載。***