上一篇我們學習了BeautifulSoup的基本用法,本節我們使用它來爬取豆瓣圖書Top250。

一、網頁分析

我們爬取的網頁的url是book.douban.com/top250?。首頁如圖

與豆瓣電影Top250差不多,將頁面拉到最底部,可以看到分頁列表

並且每一頁的url也是以25遞增,所以爬取思路與豆瓣電影Top250一致。

二、爬取目標

我們本篇要爬取的信息包括書名、作者、出版社、價格、評分、推薦語。

三、爬取首頁

  • 網頁獲取源代碼

import requests

def get_html(url):
headers = {
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
}
html = requests.get(url,headers=headers)
return html.text

if __name__ == __main__:
url = https://book.douban.com/top250?start=0
html = get_html(url)
print(html)

輸出結果

  • 解析提取所需信息

如圖,查看源代碼我們可以知道頁面中書的信息均包含在一個個<table>標籤中,所以我們可以先用CSS選擇器將一個個<table>節點選出來,然後在使用循環提取每一本書的信息。提取<table>節點的代碼如下:

def parse_html(html):
soup = BeautifulSoup(html,lxml)
books = soup.select(div.article div.indent table)
print(books)

運行結果如下:

可以看到輸出為列表,並且第一個元素包含《追風箏的人》的相關信息。這裡我們使用BeautifulSoup中的select()加CSS選擇器提取<table>節點。傳入的CSS選擇器為:div.article div.indent table。其中div.article的意思為選擇包含屬性class="article"的<div>標籤,然後跟空格代表嵌套關係,表示接著選擇該<div>下的包含class="indent"的<div>標籤,再跟空格表示接著嵌套,繼續選擇第二個<div>標籤下的<table>標籤。

接下來,對選出來的<table>標籤循環,在每一個<table>標籤中去提取圖書信息。這裡我們先提出書名信息,先看一種寫法,代碼如下:

def parse_html(html):
soup = BeautifulSoup(html,lxml)
books = soup.select(div.article div.indent table)
for book in books:
title = book.div.a.string
print(title)

輸出結果

可以看到確實獲取到了書名信息,但是有些書的書名沒有得到,返回了None,這就不是很完美了呀。我們先解釋寫這裡獲取標題的方法,這裡我們使用了節點選擇器的嵌套選擇:首先選擇了<div>標籤,然後繼續選擇其下的<a>標籤。為什麼不直接選擇<a>標籤呢,因為包含書名信息的<a>標籤是<table>節點下的第二個<a>標籤,直接選擇<a>只會選擇第一個不包含書名信息的那個<a>標籤。下面我們來研究下為什麼使用string屬性不能獲取某些書的書名信息,我們先將獲取到的<a>標籤列印出來。

def parse_html(html):
soup = BeautifulSoup(html,lxml)
books = soup.select(div.article div.indent table)
for book in books:
title = book.div.a
print(title)

並截取返回None的那本書的位置

可以看到《三體》這本書的<a>標籤的內部結構不同,所以導致調用string屬性返回None,但是我們可以注意到每條<a>標籤都包含title屬性,我們是否可以通過title屬性獲取書名?

def parse_html(html):
soup = BeautifulSoup(html,lxml)
books = soup.select(div.article div.indent table)
for book in books:
title = book.div.a[title]
print(title)

輸出結果如下:

可以看到不僅獲取到了想要的信息,而數據更加乾淨。這裡獲取title部分說了這麼多主要是想告訴大家一個獲取相同的信息有很多的方法,當一種獲取方式不理想時可以考慮換一種思路。

接下來我們一次性將所有的信息抓取下來。

def parse_html(html):
soup = BeautifulSoup(html,lxml)
tables = soup.select(div.article div.indent table)
books = []
for table in tables:
title = table.div.a[title]

由於information中包含多個信息,某些書與大多數書的信息格式不一致
在進行列表索引的時候非常容易引起IndexError異常,為了保證爬蟲的健壯性
我們對該異常進行處理

information = table.p.string
informations = information.split(/)
while(len(informations)>4):
del informations[1]
try:
author = informations[0]
press = informations[1]
date = informations[2]
price = informations[3]
except IndexError:
continue

像這樣子進行數據提取很容易遇到某一個特殊部分的網頁結構與大部分的不
一樣,這會導致首頁能抓取到的節點,在該部分會返回None,從而導致調
用string屬性產生AttributeError異常,我們需要進行異常處理

try:
score = table.find(attrs={class:rating_nums}).string
recommendation = table.find(attrs={class:inq}).string
except AttributeError:
continue
book = {
書名:title,
作者:author,
出版社:press,
出版日期:date,
價格:price,
評分:score,
推薦語:recommendation
}
books.append(book)
return books

輸出結果:

這裡可以看到輸出結果雖然為字典,但是不好看。保存為字典格式只是方便存儲與後續使用,假如我們要將其列印到屏幕上的話,並不好看,所以我們接著寫一個列印函數,專門用於輸出:

def print_(books):
for book in books:
print(**50)
for key,value in zip(book.keys(),book.values()):
print(key+:+value)
print(**50)

我們將爬取首頁的代碼匯總在一起,看看輸出效果

import requests
from bs4 import BeautifulSoup

def get_html(url):
headers = {
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
}
html = requests.get(url,headers=headers)
return html.text

def parse_html(html):
soup = BeautifulSoup(html,lxml)
tables = soup.select(div.article div.indent table)
books = []
for table in tables:
title = table.div.a[title]

由於information中包含多個信息,某些書與大多數書的信息格式不一致
在進行列表索引的時候非常容易引起IndexError異常,為了保證爬蟲的健壯性
我們對該異常進行處理

information = table.p.string
informations = information.split(/)
while(len(informations)>4):
del informations[1]
try:
author = informations[0]
press = informations[1]
date = informations[2]
price = informations[3]
except IndexError:
continue

像這樣子進行數據提取很容易遇到某一個特殊部分的網頁結構與大部分的不
一樣,這會導致首頁能抓取到的節點,在該部分會返回None,從而導致調
用string屬性產生AttributeError異常,我們需要進行異常處理

try:
score = table.find(attrs={class:rating_nums}).string
recommendation = table.find(attrs={class:inq}).string
except AttributeError:
continue
book = {
書名:title,
作者:author,
出版社:press,
出版日期:date,
價格:price,
評分:score,
推薦語:recommendation
}
books.append(book)
return books

def print_(books):
for book in books:
print(**50)
for key,value in zip(book.keys(),book.values()):
print(key+:+value)
print(**50)

if __name__ == __main__:
url = https://book.douban.com/top250?start=0
html = get_html(url)
books = parse_html(html)
print_(books)

輸出結果:

注意,這裡我們沒有寫存儲的相關函數,因為這裡只為演示BeautifulSoup的用法,假如需要存儲數據參考爬蟲系列第三篇 使用requests與正則表達式爬取豆瓣電影Top250

四、爬取整個豆瓣圖書Top250

與前面個爬蟲實例一樣,構造url列表,使用循環即可。全部代碼如下

import requests
from bs4 import BeautifulSoup

def get_html(url):
headers = {
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
}
html = requests.get(url,headers=headers)
return html.text

def parse_html(html):
soup = BeautifulSoup(html,lxml)
tables = soup.select(div.article div.indent table)
books = []
for table in tables:
title = table.div.a[title]
由於information中包含多個信息,某些書與大多數書的信息格式不一致
在進行列表索引的時候非常容易引起IndexError異常,為了保證爬蟲的健壯性
我們對該異常進行處理
information = table.p.string
informations = information.split(/)
while(len(informations)>4):
del informations[1]
try:
author = informations[0]
press = informations[1]
date = informations[2]
price = informations[3]
except IndexError:
continue

像這樣子進行數據提取很容易遇到某一個特殊部分的網頁結構與大部分的不
一樣,這會導致首頁能抓取到的節點,在該部分會返回None,從而導致調
用string屬性產生AttributeError異常,我們需要進行異常處理

try:
score = table.find(attrs={class:rating_nums}).string
recommendation = table.find(attrs={class:inq}).string
except AttributeError:
continue
book = {
書名:title,
作者:author,
出版社:press,
出版日期:date,
價格:price,
評分:score,
推薦語:recommendation
}
books.append(book)
return books

def print_(books):
for book in books:
print(**50)
for key,value in zip(book.keys(),book.values()):
print(key+:+value)
print(**50)

if __name__ == __main__:
urls = [fhttps://book.douban.com/top250?start={i*25} for i in range(0,10)]
for url in urls:
html = get_html(url)
books = parse_html(html)
print_(books)

五、總結

通過本篇的學習,讀者應該著重掌握:

  • BeautifulSoup庫三種節點選擇方式的靈活運用
  • 對可能的異常要進行處理(請求部分的異常一般不用處理)
  • 與正則表達式進行優劣比較
  • 讀者可以自行將正則表達式與BeautifulSoup結合起來靈活使用

這裡解釋一下為什麼請求部分的異常一般不需要處理,因為請求出現異常一般意味著url錯誤、網路連接有問題等,這些異常都需要我們處理好而不是用try...except語句跳過它,否則爬蟲無法繼續。

如果覺得本篇文章不錯,歡迎關注我的爬蟲系列教程公眾號【痕風雨】,一起學習交流。

推薦閱讀:

相關文章