Python網路爬蟲入門案例
#一、什麼是網路爬蟲? 網路爬蟲又稱網路蜘蛛、網路螞蟻、網路機器人等,本質上是一段程式或指令碼,可以自動化瀏覽網路中的資訊,瀏覽資訊時程式會按照一定的規則去瀏覽,這些規則我們稱之為網路爬蟲演算法。 作用:
- 定製搜尋引擎
- 自動去廣告
- 爬取圖片、文字
- 爬取金融資訊進行投資分析
#二、前置知識
- Http協議
- Html
- 正則表示式
- 一門程式語言(建議Python)
#三、網路爬蟲的核心步驟
- 選定爬取範圍
- 分析網站結構特徵
- 設計爬蟲規則
- 編寫爬蟲指令碼
#四、案例 湖北師範大學“學校要聞” 這個案例的的目標就是將上圖紅框所示中,學校要聞的新聞全部爬取到本地的txt檔案中。案例網站連結。
好,現在已經選定了爬取範圍,接下來就開始分析網站的特徵結構,以及爬蟲策略
按住Fn+F11調出控制檯,在Element面板中檢視網頁的html標籤結構,如下所示
可以看到,頁面主要的分為三個**<div>,其中<div class=“content”>中包含了代表新聞列表的<ul>標籤,內部每個<li>標籤都包含了一個<a>標籤,裡面href屬性中的值就是我們需要的,代表每一篇新聞頁的地址**。仔細觀察的話還可以發現,新聞標題也在標籤內部。
寫到這裡我們已經發現該頁面的結構特徵了,可以先動手寫一段指令碼來爬取這些內容。內容就暫時限定為新聞標題和對應的超連結,新聞內容的爬取待會再完善。
我們現在可以先寫出下面的程式碼:
import urllib.request
import re
url = 'http://www.news.hbnu.edu.cn/second/1' # 構造網頁的url
response = urllib.request.urlopen(url) # 呼叫庫來訪問該url,獲得響應
page = response.read().decode('utf-8') # 讀取響應內容,並解碼為utf-8,得到html文件
print('===================爬取下來網頁的html文件===================')
print(page) # 在控制檯打印出來將完整的html
# 如果不使用re.S引數,則只在每一行內進行匹配,如果一行沒有,就換下一行重新開始,不會跨行。
# 而使用re.S引數以後,正則表示式會將這個字串作為一個整體。
page = re.findall('<div class="content">.+?</div>', page, re.S)
page = re.findall('<ul>.+?</ul>', str(page), re.S)
page = re.findall('<a.+?</a>', str(page))
print('===================爬取下來網頁的html文件===================')
for item in page:
print(item)
執行上述程式碼,可以在控制檯中看到,包含新聞標題和正文超連結的**<a>標籤全部被爬下來,以陣列的形式儲存,最後通過for
迴圈將陣列每一項列印至控制檯。如下圖所示:
現在我們要做的就很清晰了,通過超連結訪問新聞正文,將新聞標題和正文爬取到本地的txt檔案中**。
繼續擴充套件程式碼,使用超連結獲取新聞正文頁的響應,再繼續分析html結構特徵,爬取標題和正文內容。
程式碼如下:
import urllib.request
import re
url = 'http://www.news.hbnu.edu.cn/second/1' # 構造網頁的url
response = urllib.request.urlopen(url) # 呼叫庫來訪問該url,獲得響應
page = response.read().decode('utf-8') # 讀取響應內容,並解碼為utf-8,得到html文件
# 如果不使用re.S引數,則只在每一行內進行匹配,如果一行沒有,就換下一行重新開始,不會跨行。
# 而使用re.S引數以後,正則表示式會將這個字串作為一個整體。
page = re.findall('<div class="content">.+?</div>', page, re.S)
page = re.findall('<ul>.+?</ul>', str(page), re.S)
page = re.findall('<a.+?</a>', str(page))
for item in page:
href = re.findall('<a href="(.+?)">', item)[0] # 匹配<a>標籤中href屬性中的內容
article_url = 'http://www.news.hbnu.edu.cn/' + href # 拼接成完整的新聞超連結URL
response = urllib.request.urlopen(article_url) # 獲得正文頁相應
html = response.read().decode('utf-8') # 得到新聞正文頁的完整html
title = re.findall('<div class="content">(.+?)</div>', html, re.S)
title = re.findall('<h1>(.+?)</h1>', str(title))[0] # 匹配標題內容
article = re.findall('<p class="MsoNormal".+?>(.+?)</p>', str(html)) # 匹配正文內容
content = '' # 定義存放正文內容的變數
for p in article:
p = re.findall('>(.+?)<', p) # 儘量只匹配標籤之間的純文字內容
if len(p) != 0: # 去除空元素,防止下標越界
for text in p:
if '<' not in text: # 跳過包含標籤的元素
content += text
# 再次過濾按照之前規則沒法過濾掉的html元素
content = content.replace('“', '').replace('”', '').replace(' ', '')
# 列印文章標題
print('==========' + title[0] + '==========')
# 列印文章內容
print(content)
執行上述程式碼,可以在控制檯看到純淨的新聞標題和正文文字,如下圖所示: 接下來我們要做的就是將標題和正文輸出到檔案中。
程式碼修改如下:
import urllib.request
import re
import os
url = 'http://www.news.hbnu.edu.cn/second/1' # 構造網頁的url
response = urllib.request.urlopen(url) # 呼叫庫來訪問該url,獲得響應
page = response.read().decode('utf-8') # 讀取響應內容,並解碼為utf-8,得到html文件
# 如果不使用re.S引數,則只在每一行內進行匹配,如果一行沒有,就換下一行重新開始,不會跨行。
# 而使用re.S引數以後,正則表示式會將這個字串作為一個整體。
page = re.findall('<div class="content">.+?</div>', page, re.S)
page = re.findall('<ul>.+?</ul>', str(page), re.S)
page = re.findall('<a.+?</a>', str(page))
for item in page:
href = re.findall('<a href="(.+?)">', item)[0] # 匹配<a>標籤中href屬性中的內容
article_url = 'http://www.news.hbnu.edu.cn/' + href # 拼接成完整的新聞超連結URL
response = urllib.request.urlopen(article_url) # 獲得正文頁相應
html = response.read().decode('utf-8') # 得到新聞正文頁的完整html
title = re.findall('<div class="content">(.+?)</div>', html, re.S)
title = re.findall('<h1>(.+?)</h1>', str(title))[0] # 匹配標題內容
article = re.findall('<p class="MsoNormal".+?>(.+?)</p>', str(html)) # 匹配正文內容
content = '' # 定義存放正文內容的變數
for p in article:
p = re.findall('>(.+?)<', p) # 儘量只匹配標籤之間的純文字內容
if len(p) != 0: # 去除空元素,防止下標越界
for text in p:
if '<' not in text: # 跳過包含標籤的元素
content += text
# 再次過濾按照之前規則沒法過濾掉的html元素
content = content.replace('“', '').replace('”', '').replace(' ', '')
# 列印進度
print('==========正在寫入新聞《' + title + '》到TxT檔案==========')
# 在原始碼同目錄建立名“新聞”為資料夾
existed = os.path.exists('./新聞')
if not existed:
os.makedirs('./新聞')
# 寫入內容至檔案
txt = open("./新聞/" + title + ".txt", "wb")
txt.write(content.encode())
txt.close()
執行程式碼後,控制檯可以看到爬蟲進度: 在程式碼同目錄可以看到生成了“新聞”資料夾,裡面存放已經寫入完畢的txt檔案: 開啟txt檔案,可以看到新聞正文內容: 這麼快就要大功告成了嗎?
好了,別忘了,這個新聞頁面是可以翻頁的!!!!
你可以看到這個欄目一共有261頁,我們不可能針對每一個頁面去寫一個爬蟲,這樣就失去了爬蟲的意義。
所以現在我們需要做另一件事,繼續分析翻頁行為的細節。
首先觀察,在翻頁過程中,發現瀏覽器的URL並不包含頁碼資訊,也沒有發生改變,這說明這個案例不可能通過URL實現翻頁效果。
那好吧,有些難度了,繼續檢視控制檯,切換到NetWork面板,點選翻頁,檢視翻頁動作觸發了哪些http請求內容: 直覺得到,最可疑的就是那名字為**“1”**的請求了,點進去檢視,果不其然,這是一個post請求,**請求data裡面還包含了請求的頁碼資訊。**如上圖紅框所示。
那麼我們就用Python構造個post請求來試一試,看看結果吧。
程式碼如下:
import requests
url = 'http://www.news.hbnu.edu.cn/second/1'
# 還有我們準備用Post傳的值,這裡值用字典的形式
values = {
'type_id': '1,2',
'page': '2'
}
html = requests.post(url, data=values).text # 獲得新聞列表頁html
# 列印頁面html至控制檯
print(html)
執行上述程式碼,還真得到了下一頁面的html文件!而且文件結構特徵和第一頁完全一致!
這樣就好辦了,回到之前沒寫完的程式碼,繼續補完利用post請求翻頁的程式碼:
import urllib.request
import urllib.parse
import urllib.error
import re
import os
import requests
url = 'http://www.news.hbnu.edu.cn/second/1' # 構造網頁的url
index = 0 # 開始頁碼
while 1:
# 列印當前頁碼
print('正在爬取第' + str(index + 1) + '頁')
# 還有我們準備用Post傳的值,這裡值用字典的形式
values = {
'type_id': '1,' + str(index),
'page': str(index)
}
index = index + 1 # 頁碼加1
page = ''
# 為了避免碰上網路堵塞URLError: <urlopen error timed out>的異常,加上這個迴圈多試幾次
while 1:
try:
page = requests.post(url, data=values).text # 獲得新聞列表頁html
break
except urllib.error.URLError as e:
continue
# 如果不使用re.S引數,則只在每一行內進行匹配,如果一行沒有,就換下一行重新開始,不會跨行。
# 而使用re.S引數以後,正則表示式會將這個字串作為一個整體。
page = re.findall('<div class="content">.+?</div>', page, re.S)
page = re.findall('<ul>.+?</ul>', str(page), re.S)
page = re.findall('<a.+?</a>', str(page))
for item in page:
href = re.findall('<a href="(.+?)">', item)[0] # 匹配<a>標籤中href屬性中的內容
article_url = 'http://www.news.hbnu.edu.cn/' + href # 拼接成完整的新聞超連結URL
response = ''
# 為了避免碰上網路堵塞URLError: <urlopen error timed out>的異常,加上這個迴圈多試幾次
while 1:
try:
response = urllib.request.urlopen(article_url) # 獲得正文頁相應
break
except urllib.error.URLError as e:
continue
html = response.read().decode('utf-8') # 得到新聞正文頁的完整html
title = re.findall('<div class="content">(.+?)</div>', html, re.S)
title = re.findall('<h1>(.+?)</h1>', str(title))[0] # 匹配標題內容
article = re.findall('<p class="MsoNormal".+?>(.+?)</p>', str(html)) # 匹配正文內容
content = '' # 定義存放正文內容的變數
for p in article:
p = re.findall('>(.+?)<', p) # 儘量只匹配標籤之間的純文字內容
if len(p) != 0: # 去除空元素,防止下標越界
for text in p:
if '<' not in text: # 跳過包含標籤的元素
content += text
# 再次過濾按照之前規則沒法過濾掉的html元素
content = content.replace('“', '').replace('”', '').replace(' ', '')
# 列印進度
print('==========正在寫入新聞《' + title + '》到TxT檔案==========')
# 在原始碼同目錄建立名“新聞”為資料夾
existed = os.path.exists('./新聞')
if not existed:
os.makedirs('./新聞')
# 寫入內容至檔案
txt = open("./新聞/" + title + ".txt", "wb")
txt.write(content.encode())
txt.close()
程式碼整合在一起,看起來稍微複雜,可以執行看看觀看最終效果,結合程式碼幫助自己理解。
最終執行結果: