1. 程式人生 > >Python For 和 While 迴圈爬取不確定頁數的網頁!

Python For 和 While 迴圈爬取不確定頁數的網頁!

Python For 和 While 迴圈爬取不確定頁數的網頁!

 

Python For 和 While 迴圈爬取不確定頁數的網頁!

 

第二種是不直觀顯示網頁總頁數,需要在後臺才可以檢視到,比如之前爬過的虎嗅網,文章見:

Python For 和 While 迴圈爬取不確定頁數的網頁!

 

私信菜鳥 007 獲取神祕大禮包!

第三種是今天要說的,不知道具體有多少頁的網頁,比如豌豆莢:

Python For 和 While 迴圈爬取不確定頁數的網頁!

 

對於,前兩種形式的網頁,爬取方法非常簡單,使用 For 迴圈從首頁爬到尾頁就行了,第三種形式則不適用,因為不知道尾頁的頁數,所以迴圈到哪一頁結束無法判斷。

那如何解決呢?有兩種方法。

第一種方式 使用 For 迴圈配合 break 語句 ,尾頁的頁數設定一個較大的引數,足夠迴圈爬完所有頁面,爬取完成時,break 跳出迴圈,結束爬取。

第二種方法 使用 While 迴圈,可以結合 break 語句,也可以設起始迴圈判斷條件為 True ,從頭開始迴圈爬取直到爬完最後一頁,然後更改判斷條件為 False 跳出迴圈,結束爬取。

實際案例

下面,我們以 豌豆莢 網站中「視訊」類別下的 App 資訊為例,使用上面兩種方法抓取該分類下的所有 App 資訊,包括 App 名稱、評論、安裝數量和體積。

首先,簡要分析下網站,可以看到頁面是通過 Ajax 載入的,GET 請求附帶一些引數,可以使用 params 引數構造 URL 請求,但不知道一共有多少頁,為了確保下載完所有頁,設定較大的頁數,比如 100頁 甚至 1000 頁都行。

下面我們嘗試使用 For 和 While 迴圈爬取 。

Python For 和 While 迴圈爬取不確定頁數的網頁!

 

Requests

▌For 迴圈

主要程式碼如下:

class Get_page():
 def __init__(self):
 # ajax 請求url
 self.ajax_url = 'https://www.wandoujia.com/wdjweb/api/category/more'
 def get_page(self,page,cate_code,child_cate_code):
 params = {
 'catId': cate_code,
 'subCatId': child_cate_code,
 'page': page,
 }
 response = requests.get(self.ajax_url, headers=headers, params=params)
 content = response.json()['data']['content'] #提取json中的html頁面資料
 return content
 def parse_page(self, content):
 # 解析網頁內容
 contents = pq(content)('.card').items()
 data = []
 for content in contents:
 data1 = {
 'app_name': content('.name').text(),
 'install': content('.install-count').text(),
 'volume': content('.meta span:last-child').text(),
 'comment': content('.comment').text(),
 }
 data.append(data1)
 if data:
 # 寫入MongoDB
 self.write_to_mongodb(data)
 
if __name__ == '__main__':
 # 例項化資料提取類
 wandou_page = Get_page()
 cate_code = 5029 # 影音播放大類別編號
 child_cate_code = 716 # 視訊小類別編號
 for page in range(2, 100):
 print('*' * 50)
 print('正在爬取:第 %s 頁' % page)
 content = wandou_page.get_page(page,cate_code,child_cate_code)
 # 新增迴圈判斷,如果content 為空表示此頁已經下載完成了,break 跳出迴圈
 if not content == '':
 wandou_page.parse_page(content)
 sleep = np.random.randint(3,6)
 time.sleep(sleep)
 else:
 print('該類別已下載完最後一頁')
 break

這裡,首先建立了一個 Get_page 類,get_page 方法用於獲取 Response 返回的 json 資料,通過 json.cn 網站解析 json 解析後發現需要提取的內容是一段包裹在 data 欄位下 content 鍵中的 html 文字,可以使用 parse_page 方法中的 pyquery 函式進行解析,最後提取出 App 名稱、評論、安裝數量和體積四項資訊,完成抓取。

在主函式中,使用了 if 函式進行條件判斷,若 content 不為空,表示該頁有內容,則迴圈爬下去,若為空則表示此頁面已完成了爬取,執行 else 分支下的 break 語句結束迴圈,完成爬取。

Python For 和 While 迴圈爬取不確定頁數的網頁!

 

爬取結果如下,可以看到該分類下一共完成了全部 41 頁的資訊抓取。

Python For 和 While 迴圈爬取不確定頁數的網頁!

 

▌While 迴圈

While 迴圈和 For 迴圈思路大致相同,不過有兩種寫法,一種仍然是結合 break 語句,一種則是更改判斷條件。

總體程式碼不變,只需修改 For 迴圈部分:

page = 2 # 設定爬取起始頁數
while True:
 print('*' * 50)
 print('正在爬取:第 %s 頁' %page)
 content = wandou_page.get_page(page,cate_code,child_cate_code)
 if not content == '':
 wandou_page.parse_page(content)
 page += 1
 sleep = np.random.randint(3,6)
 time.sleep(sleep)
 else:
 print('該類別已下載完最後一頁')
 break

或者:

page = 2 # 設定爬取起始頁數
page_last = False # while 迴圈初始條件
while not page_last:
 #...
 else:
 # break
 page_last = True # 更改page_last 為 True 跳出迴圈

結果如下,可以看到和 For 迴圈的結果是一樣的。

Python For 和 While 迴圈爬取不確定頁數的網頁!

 

我們可以再測試一下其他類別下的網頁,比如選擇「K歌」類別,編碼為:718,然後只需要對應修改主函式中的child_cate_code 即可,再次執行程式,可以看到該類別下一共爬取了 32 頁。

Python For 和 While 迴圈爬取不確定頁數的網頁!

 

由於 Scrapy 中的寫法和 Requests 稍有不同,所以接下來,我們在 Scrapy 中再次實現兩種迴圈的爬取方式 。

Scrapy

▌For 迴圈

Scrapy 中使用 For 迴圈遞迴爬取的思路非常簡單,即先批量生成所有請求的 URL,包括最後無效的 URL,後續在 parse 方法中新增 if 判斷過濾無效請求,然後爬取所有頁面。 由於 Scrapy 依賴於Twisted框架,採用的是非同步請求處理方式,也就是說 Scrapy 邊傳送請求邊解析內容,所以這會發送很多無用請求。

def start_requests(self):
 pages=[]
 for i in range(1,10):
 url='http://www.example.com/?page=%s'%i
 page = scrapy.Request(url,callback==self.pare)
 pages.append(page)
 return pages

下面,我們選取豌豆莢「新聞閱讀」分類下的「電子書」類 App 頁面資訊,使用 For 迴圈嘗試爬取,主要程式碼如下:

def start_requests(self):
 cate_code = 5019 # 新聞閱讀
 child_cate_code = 940 # 電子書
 print('*' * 50)
 pages = []
 for page in range(2,50):
 print('正在爬取:第 %s 頁 ' %page)
 params = {
 'catId': cate_code,
 'subCatId': child_cate_code,
 'page': page,
 }
 category_url = self.ajax_url + urlencode(params)
 pa = yield scrapy.Request(category_url,callback=self.parse)
 pages.append(pa)
 return pages
def parse(self, response):
 if len(response.body) >= 100: # 判斷該頁是否爬完,數值定為100是因為response無內容時的長度是87
 jsonresponse = json.loads(response.body_as_unicode())
 contents = jsonresponse['data']['content']
 # response 是json,json內容是html,html 為文字不能直接使用.css 提取,要先轉換
 contents = scrapy.Selector(text=contents, type="html")
 contents = contents.css('.card')
 for content in contents:
 item = WandoujiaItem()
 item['app_name'] = content.css('.name::text').extract_first()
 item['install'] = content.css('.install-count::text').extract_first()
 item['volume'] = content.css('.meta span:last-child::text').extract_first()
 item['comment'] = content.css('.comment::text').extract_first().strip()
 yield item

上面程式碼很好理解,簡要說明幾點:

第一、判斷當前頁是否爬取完成的判斷條件改為了 response.body 的長度大於 100。

因為請求已爬取完成的頁面,返回的 Response 結果是不為空的,而是有長度的 json 內容(長度為 87),其中 content 鍵值內容才為空,所以這裡判斷條件選擇比 87 大的數值即可,比如 100,即大於 100 的表示此頁有內容,小於 100 表示此頁已爬取完成。

{"state":{"code":2000000,"msg":"Ok","tips":""},"data":{"currPage":-1,"content":""}}

第二、當需要從文字中解析內容時,不能直接解析,需要先轉換。

通常情況下,我們在解析內容時是直接對返回的 response 進行解析,比如使用 response.css() 方法,但此處,我們的解析物件不是 response,而是 response 返回的 json 內容中的 html 文字,文字是不能直接使用 .css() 方法解析的,所以在對 html 進行解析之前,需要新增下面一行程式碼轉換後才能解析。

contents = scrapy.Selector(text=contents, type="html")

結果如下,可以看到傳送了全部 48 個請求,實際上該分類只有 22 頁內容,即多傳送了無用的 26 個請求。

Python For 和 While 迴圈爬取不確定頁數的網頁!

 

▌While 迴圈

接下來,我們使用 While 迴圈再次嘗試抓取,程式碼省略了和 For 迴圈中相同的部分:

def start_requests(self):
 page = 2 # 設定爬取起始頁數
 dict = {'page':page,'cate_code':cate_code,'child_cate_code':child_cate_code} # meta傳遞引數
 yield scrapy.Request(category_url,callback=self.parse,meta=dict)
def parse(self, response):
 if len(response.body) >= 100: # 判斷該頁是否爬完,數值定為100是因為無內容時長度是87
 page = response.meta['page']
 cate_code = response.meta['cate_code']
 child_cate_code = response.meta['child_cate_code']
	 #...
 for content in contents:
 	yield item
 
 # while迴圈構造url遞迴爬下一頁
 page += 1
 params = {
 'catId': cate_code,
 'subCatId': child_cate_code,
 'page': page,
 }
 ajax_url = self.ajax_url + urlencode(params)
 dict = {'page':page,'cate_code':cate_code,'child_cate_code':child_cate_code}
 yield scrapy.Request(ajax_url,callback=self.parse,meta=dict)

這裡,簡要說明幾點:

第一、While 迴圈的思路是先從頭開始爬取,使用 parse() 方法進行解析,然後遞增頁數構造下一頁的 URL 請求,再迴圈解析,直到爬取完最後一頁即可,這樣 不會像 For 迴圈那樣傳送無用的請求

第二、parse() 方法構造下一頁請求時需要利用 start_requests() 方法中的引數,可以 使用 meta 方法來傳遞引數

執行結果如下,可以看到請求數量剛好是 22 個,也就完成了所有頁面的 App 資訊爬取。

Python For 和 While 迴圈爬取不確定頁數的網頁!

進群:960410445  即可獲取各種教程原始碼!

以上,就是本文的所有內容,小結一下:

  • 在爬取不確定頁數的網頁時,可以採取 For 迴圈和 While 迴圈兩種思路,方法大致相同。
  • 在 Requests 和 Scrapy 中使用 For 迴圈和 While 迴圈的方法稍有不同,因此本文以豌豆莢網站為例,詳細介紹了迴圈構造方法。
  • 之所以寫本文內容和之前的幾篇文章(設定隨機 UA、代理 IP), 是為了下一篇文章「分析豌豆莢全網 70000+ App 資訊」做鋪墊,敬請期待。