前言html
在剛開始學習python的時候,有看到過迭代器和生成器的相關內容,不過當時並未深刻了解,更談不上使用了,實際上是能夠用生成器來改造一下的,因此本次就使用生成器來優化一下爬蟲代碼python
何時能夠使用生成器呢?app
通常爬蟲常常會經過for循環來迭代處理數據,例如我以前爬取20頁數據時,會先把得到的數據存儲到一個列表或字典中,而後再把整個列表或字典 return 出去,而後保存數據至本地又會再調用這個列表獲取數據(其實作了2步:先把頁面的數據提取出來存到列表,後面用的時候再迭代列表);函數
相似這種直接使用列表或字典來存儲數據,實際上是先存儲到了內存中,若是數據量過大的話,則會佔用大量內存,這樣顯然是不合適的;學習
此時就能夠使用生成器,咱們每提取一條數據,就把該條數據經過 yield 返回出去,好處是不須要提早把全部數據加載到一個列表中,而是有須要的時候纔給它生成值返回,沒調用這個生成器的時候,它就處於休眠狀態等待下一次調用優化
優化爬蟲代碼url
首先看一下未使用生成器的代碼spa
# -*- coding:utf-8 -*- import requests from requests.exceptions import RequestException import os, time from lxml import etree def get_html(url): """獲取頁面內容""" response = requests.get(url, timeout=15) # print(response.status_code) try: if response.status_code == 200: # print(response.text) return response.text else: return None except RequestException: print("請求失敗") # return None def parse_html(html_text): """解析一個結果頁的內容,提取圖片url""" html = etree.HTML(html_text) if len(html) > 0: img_src = html.xpath("//img[@class='photothumb lazy']/@data-original") # 提取圖片url,經過xpath提取會生成一個列表 # print(img_src) return img_src # 將提取出來的圖片url列表返回出去 else: print("解析頁面元素失敗") def get_all_image_url(depth): """ 提取全部頁面的全部圖片url :param depth: 爬取頁碼 :return: """ base_url = 'https://imgbin.com/free-png/naruto/' # 定義初始url image_urls = [] for i in range(1, depth): url = base_url + str(i) # 根據頁碼遍歷請求url html = get_html(url) # 解析每一個頁面的內容 # print(html) if html: list_data = parse_html(html) # 提取頁面中的圖片url for img in list_data: image_urls.append(img) return image_urls def get_image_content(url): """請求圖片url,返回二進制內容""" try: r = requests.get(url, timeout=15) if r.status_code == 200: return r.content return None except RequestException: return None def main(depth=None): """ 主函數,下載圖片 :param depth: 爬取頁碼 :return: """ j = 1 img_urls = get_all_image_url(depth) # 提取頁面中的圖片url root_dir = os.path.dirname(os.path.abspath('.')) save_path = root_dir + '/pics/' # 定義保存路徑 # print(img_urls) # print(next(img_urls)) # print(next(img_urls)) for img_url in img_urls: # 遍歷每一個圖片url try: file_path = '{0}{1}.{2}'.format(save_path, str(j), 'jpg') if not os.path.exists(file_path): # 判斷是否存在文件,不存在則爬取 with open(file_path, 'wb') as f: f.write(get_image_content(img_url)) f.close() print('第{}個文件保存成功'.format(j)) else: print("第{}個文件已存在".format(j)) j = j + 1 except FileNotFoundError as e: print("遇到錯誤:", e) continue except TypeError as f: print("遇到錯誤:", f) continue if __name__ == '__main__': start = time.time() main(2) end = time.time() print(end-start)
parse_html()函數:它的做用解析一個結果頁的內容,提取一頁的全部圖片url(經過xpath提取,因此數據時存儲在一個列表中),能夠把它改造爲生成器;code
get_all_image_url()函數:調用parse_html()函數,經過控制爬取頁碼,提取全部頁面的全部圖片url,而後存到一個列表中返回出去,能夠改造爲生成器;orm
main()函數:調用get_all_image_url()函數獲得全部圖片url的列表,而後迭代這個列表,來獲得每個圖片url來下載圖片
接下來要作的就是改造 parse_html()函數 和 get_all_image_url()函數
這個其實也比較簡單,只須要把本來要追加到列表中的東西經過 yield 關鍵字返回出去就好了
parse_html()函數:
''' 遇到問題沒人解答?小編建立了一個Python學習交流羣:778463939 尋找有志同道合的小夥伴,互幫互助,羣裏還有不錯的視頻學習教程和PDF電子書! ''' def parse_html(html_text): """解析一個結果頁的內容,提取圖片url""" html = etree.HTML(html_text) if len(html) > 0: img_src = html.xpath("//img[@class='photothumb lazy']/@data-original") # print(img_src) for item in img_src: yield item
get_all_image_url()函數
def get_all_image_url(depth): """ 提取全部頁面的全部圖片url :param depth: 爬取頁碼 :return: """ base_url = 'https://imgbin.com/free-png/naruto/' # 定義初始url for i in range(1, depth): url = base_url + str(i) # 根據頁碼遍歷請求url html = get_html(url) # 解析每一個頁面的內容 # print(html) if html: list_data = parse_html(html) # 提取頁面中的圖片url for img in list_data: yield img # 經過yield關鍵字返回每一個圖片的url地址
而後上面代碼中有個地方須要注意
for i in range(1, depth): 這個for循環,是迭代爬取頁碼
list_data = parse_html(html):調用parse_html()函數,獲取每一頁內容的生成器對象
for img in list_data: 迭代 list_data,而後經過yield img 把值返回出去
get_all_image_url()函數 還能夠用如下方式返回結果
def get_all_image_url(depth): """ 提取全部頁面的全部圖片url :param depth: 爬取頁碼 :return: """ base_url = 'https://imgbin.com/free-png/naruto/' # 定義初始url for i in range(1, depth): url = base_url + str(i) # 根據頁碼遍歷請求url html = get_html(url) # 解析每一個頁面的內容 # print(html) if html: list_data = parse_html(html) # 提取頁面中的圖片url yield from list_data
使用關鍵字 yield from 替代了以前的內層for循環,能夠達到相同的效果(具體含義能夠查看 Python yield from 用法詳解、yield from)
main()函數 不須要做改動,由於咱們在調用生成器對象時,也是經過for循環來提取裏面的值的,因此這部分代碼和以前同樣
OK,本次代碼優化到此結束,python有太多東西要學啦,感受本身懂得仍是太少,要保持學習的心態,加油~