概述 既然決定把視頻上老師講的實戰都自己動手實現一遍,那麼就先把最好大學排名這個實例自己寫一遍。看視頻的時候挺輕鬆的,但是到自己動手的時候才知道不容易,寫這個程式遇到兩個比較棘手的問題,一個是如何從網頁中提取出自己想要的信息,另一個是信息以什麼樣的形式保存並展示出來。其實幾乎所有的爬蟲都會遇到這兩個 ...
概述
既然決定把視頻上老師講的實戰都自己動手實現一遍,那麼就先把最好大學排名這個實例自己寫一遍。看視頻的時候挺輕鬆的,但是到自己動手的時候才知道不容易,寫這個程式遇到兩個比較棘手的問題,一個是如何從網頁中提取出自己想要的信息,另一個是信息以什麼樣的形式保存並展示出來。其實幾乎所有的爬蟲都會遇到這兩個問題吧,希望在以後的練習中能不斷增強對這兩個問題的處理能力。當然,這個實例還有一個重要的作用是增強自己對beautifulsoup庫的理解和運用。
準備工作
打開Chrome瀏覽器,查看要爬取的頁面是否為靜態頁面,如果不是的話以目前的技術是不能對其進行爬取的,畢竟視頻的錄製已經有一段時間了,網頁可能會有所變化。經檢查,幸好網頁還是靜態的,就可以用requests+bs4對其進行爬取。
調試
不能掄起袖子,就在sublime中猛寫代碼,邊寫邊改,其實效率是很低的,因為如果在編輯器中寫代碼調試,發現自己的代碼不能抓取到目標信息,就必須修改代碼並且重新對網頁進行爬取操作,無疑這種人工的重覆勞動是不行的,效率十分低下,畢竟只是個小白,不能一下子就把正確的信息抓取下來。
針對此問題,可以在Python shell中進行調試,這樣就好了許多。在互動式視窗中引入requests和bs4,在shell中進行調試明顯比在命令行中調試好了許多。
調試過程中儘量用瀏覽器的開發者工具對信息進行分析吧,如果用源代碼對其進行分析的話,結構不清晰,不容易分析。
bd4的使用
根據個人的習慣,發現自己最常用的方法應該是find_all()、對子節點的訪問和平行節點向下遍歷了,有時還會遇到對標簽的屬性信息和文本信息的提取。
find_all()
標簽對象的一個方法,主要是用來定位一個標簽或者是一些標簽的位置,返回的是一個列表,列表裡的每個元素是一個tag對象。此方法常和標簽的屬性組合來完成定位,要註意的是,class屬性由於和Python中的關鍵字衝突,所以用class_代替,由於此方法很常用,所以可以不寫,直接加對括弧就可以表示調用了此方法了,十分方便。
子節點遍歷
- "." 操作符 得到子節點中第一個符合的標簽
- .contents 返回一個含所有子節點的列表
- .children 返回一個可迭代對象,內容和.contents是一樣的
tbody.tr.td
<td>1</td>
值得註意的是,空字元串或者是換行符也算是節點,這是一個大坑,要註意一點。
平行節點的向下遍歷
- .next_sibling 返回一個標簽對象
- .next_siblings 返回一個可迭代對象
對標簽屬性的訪問
- 類似於字典式的訪問
- get()
tbody['class']
['hidden_zhpm']
tbody.get('class')
['hidden_zhpm']
也許用第二種意思更清晰點吧==
對標簽中文本信息的提取
- .string
- .text
- .get_text()
td.string
'1'
td.text
'1'
td.get_text()
'1'
遇到的問題
- 每所學校的標簽基本上一樣的,有些有屬性還能精准定位,而有些沒有屬性,不能精準的定位,而且還有些空格或者是換行符還混進來,增加了提取信息的難度。
- 每所學校要獲取的信息有三個:排名、學校名稱和所在地區,應該以哪種方式對它們進行保存也是個問題。
- 把獲取到的信息格式化輸出也是個問題,因為要做到簡潔、清晰和美觀。
- 對於第一個問題,既然有些標簽沒有屬性,不能精確定位,又不能將其放棄,那麼只能不用配合屬性將它們都抓取下來,其中混雜著換行符這個垃圾信息,可以增加一個判斷語句來將換行符給排除掉:
if isinstance(tr, bs4.element.Tag)
- 第二個問題,用列表的形式將這三個信息保存下來,而這三個元素是列表的一個元素,也就是嵌套列表,列表可以從主函數這邊傳遞過去。
- 第三個問題,格式化輸出可以用字元串的format函數,但是還是遇到了問題,主要是大學名稱太長會把後面的省市的一部分空間給占用掉,造成不對齊現象。測試了很多次,在設置的長度足夠長的情況下,只要中文字數超過7個,必將不對齊,很神奇的現象,跑去論壇問,也沒有誰說出個所以然來,很無奈。format函數在一般情況下不會出現問題,先這樣吧。
總結
對於這個爬蟲程式的編寫,最大的收穫就是幫助我理解和掌握beautifulsoup的用法,當然還有其他方面的收穫,比如怎樣將信息格式化輸出和學習老師編寫代碼的方法,雖然最後格式化輸出有點不理想,但整個程式在整體上還是成功的,希望自己繼續努力。
源代碼
from fake_useragent import UserAgent
import requests
from requests import Timeout, HTTPError
from bs4 import BeautifulSoup
import bs4
def get_page(url, ua):
try:
headers = {'User-Agent':ua}
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
response.encoding = 'utf-8'
return response.text
except Timeout:
print('requests timeout')
get_page(url)
except HTTPError:
print('the http error(maybe status is not 200)')
except:
print('other error')
def parse_page(html):
soup = BeautifulSoup(html, 'html.parser')
return soup
def get_message(html, ulist):
tbody = html.find('tbody', class_='hidden_zhpm')
trs = tbody('tr')
for tr in trs:
if isinstance(tr, bs4.element.Tag):
tds = tr.contents
ulist.append([tds[2].string.strip(),tds[4].string.strip(), tds[0].string.strip()])
def print_message(messages):
templet = '{0:<50}\t{1:<50}\t{2:<50}'
print(templet.format('排名', '學校名稱', '省市'))
for ult in messages:
print(templet.format(ult[2], ult[0], ult[1], ' '))
def main():
ua = UserAgent()
ua = ua.random
url = 'http://www.zuihaodaxue.com/zuihaodaxuepaiming2016.html'
html = get_page(url, ua)
soup = parse_page(html)
ulist = []
get_message(soup, ulist)
print_message(ulist)
main()