1. 程式人生 > 實用技巧 >教你使用python生成器重構提取資料方法,來優化你的爬蟲程式碼

教你使用python生成器重構提取資料方法,來優化你的爬蟲程式碼

前言

在剛開始學習python的時候,有看到過迭代器和生成器的相關內容,不過當時並未深入瞭解,更談不上使用了,其實是可以用生成器來改造一下的,所以本次就使用生成器來優化一下爬蟲程式碼

  1. 生成器函式與普通函式的區別是,生成器用關鍵字 yield 來返回值,而普通函式用 return 一次性返回值;
  2. 當你呼叫生成器函式的時候,函式內部的程式碼並不立馬執行 ,這個函式只是返回一個生成器物件;
  3. 一般使用for迴圈迭代生成器物件來獲取具體的返回值

什麼時候可以使用生成器呢?

一般爬蟲經常會通過for迴圈來迭代處理資料,例如我之前爬取20頁資料時,會先把獲得的資料儲存到一個列表或字典中,然後再把整個列表或字典 return 出去,然後儲存資料至本地又會再呼叫這個列表獲取資料(其實做了2步:先把頁面的資料提取出來存到列表,後面用的時候再迭代列表);

類似這種直接使用列表或字典來儲存資料,其實是先儲存到了記憶體中,如果資料量過大的話,則會佔用大量記憶體,這樣顯然是不合適的;

此時就可以使用生成器,我們每提取一條資料,就把該條資料通過 yield 返回出去,好處是不需要提前把所有資料載入到一個列表中,而是有需要的時候才給它生成值返回,沒呼叫這個生成器的時候,它就處於休眠狀態等待下一次呼叫

優化爬蟲程式碼

首先看一下未使用生成器的程式碼

# -*- 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提取,所以資料時儲存在一個列表中),可以把它改造為生成器;

  • get_all_image_url()函式:呼叫parse_html()函式,通過控制爬取頁碼,提取所有頁面的所有圖片url,然後存到一個列表中返回出去,可以改造為生成器;

  • 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地址

然後上面程式碼中有個地方需要注意

  1. for i in range(1, depth): 這個for迴圈,是迭代爬取頁碼

  2. list_data = parse_html(html):呼叫parse_html()函式,獲取每一頁內容的生成器物件

  3. 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有太多東西要學啦,感覺自己懂得還是太少,要保持學習的心態,加油~