­

(協程)壁紙爬取

(協程)壁紙爬取

一、 演算法解析

1.1 進入爬取壁紙的網站(表層網頁)

彼岸桌面壁紙-二次元 (少爬澀圖,健康生活!)

1.2 獲取顯示單張壁紙的頁面(深層網頁)地址

選擇網頁元素:Ctrl+Shift+C

你學廢了嗎?學廢扣眼珠子,沒學廢點贊收藏

問:為什麼不是下面那個 “img src=…” 的地址呢?

答:下面的地址的確是一個圖片的地址,但是該圖片是800*450(我們要1920*1080),是用來顯示該頁面這個小圖的(如下圖)

所以你喜歡 還是喜歡 ?你品你細品

1.3 進入單張壁紙的頁面

① 你可以點擊 “/desk/22629.htm”

② 在瀏覽器網址輸入欄輸入 “//www.netbian.com/desk/22629.htm

(② 這個是重點,因為爬取時我們需要這個完整地址才能訪問下面這個頁面)

此時的 “img src=…” 就是我們 壁紙的最終下載地址

1.4 思路總結

  • ① 訪問主頁(表層網頁)

  • ② 將主頁的源程式碼全部讀取出來

    查看源程式碼:

    ① 右鍵→查看網頁源程式碼

    ② Ctrl+U

  • ② 通過正則匹配將讀取的網頁源程式碼中 所有 單張壁紙頁面的(深層網頁)地址 “/desk/*.htm” 篩選出來,作為字元串對象

  • //www.netbian.com + “/desk/*.htm” 將兩個字元串合併為完整的 url

  • ④ 通過上面完整的網址訪問每張壁紙的深層網頁,並將深層網頁的源程式碼讀取出來

  • ⑤ 通過正則匹配1920*1080壁紙 的下載地址,篩選出來

  • ⑥ 訪問該壁紙的下載地址,以二進位的形式將數據讀取,並以 “*.jpg(.png)” 格式進行寫入為本地文件

1.5 思路程式碼重現

① 以下部分都有標明 重點 與 非重點

重點:核心演算法的展現(如何訪問網頁並通過正則匹配提取所需的url)

​ 非重點:下載功能,顯示下載進度,自動為文件名命名

​ ※ 讀者看懂重點的核心演算法就好了,其餘的功能有興趣自己琢磨

② 建議從主函數開始食用,不然絕對懵逼

③ 你也可以下載這個小項目自己去VanVan

該項目下載地址:

鏈接://pan.baidu.com/s/1SVIMVzmPIEJ7kEsvMpK23Q
提取碼:fkgz

1.5.1 工程目錄結構

1.5.2 導入模組以及定義全局變數

# 最好把 gevent 相關的模組放最前面,以免出現BUG
import gevent
from gevent import monkey
monkey.patch_all()
import requests
import re
from read_write.read_num import read
from read_write.write_num import write

# 爬取到的圖片下載地址存放列表
url_deep_list = []
# 以下四個變數並非重點,我們關注核心演算法
# 圖片文件的名字
name_jpg = 0
# 允許開始下載的標誌
flag_download = 0
gl_url_ok_num = 0
url_num = 0

1.5.3 訪問主頁(表層網頁)提取 深層網頁 地址——重點

def get_surface_url(surface_page):
    """表層網頁提取出深層網頁地址

    :param surface_page: 表層網頁
    :return: 返回一個 深層網頁 地址列表
    """
    # 通過目標網站,相當於獲取了一個 response 這個可操作對象
    response = requests.get(surface_page)

    # TODO .+? 非貪婪匹配 當遇到 .時前面的非貪婪匹配任務結束,接著匹配htm
    url_add = r'/desk/.+?\.htm'

    # response.text 即代表該對象的文本數據,源程式碼數據
    # 從源程式碼數據中匹配 url_add 相關的內容,從而生成一個列表
    url_list_old = re.findall(url_add, response.text)

    # print(url_list_old)

    url_list_new = []

    for str_old in url_list_old:
        str_new = '//www.netbian.com' + str_old
        url_list_new.append(str_new)

    # print(url_list_new)

    return url_list_new

1.5.4 訪問深層網頁地址,提取下載地址——重點

def get_deep_url(deep_page):
    """通過深層網頁獲取 1920*1080 的壁紙地址

    :param deep_page: 深層網頁地址
    :return: 返回一個 壁紙 下載地址
    """
    response = requests.get(deep_page)
    # 此頁面已經能找到 1920*1080 jpg圖片的下載地址了,我們通過正則匹配,分組「()」進行提取
    # ① 首先鎖定目標地址的頭 /desk/.+?-1920x1080.htm
    # ② 接著再往下匹配的地址,即為最終下載地址
    # 總結:之所以可以分兩部分進行鎖定匹配,是因為
    #      ① search 是可以通過跨行匹配(不過一旦匹配成功一個,即返回對象)
    #      ② .*? 到 <img src... 之間正好沒有 \n 換行符,所以能往後一直匹配(因為 . 是匹配除了\n外的所有任意一個字元)
    url_add = r'/desk/.+?-1920x1080.htm.*?<img src="(//img.netbian.com/file/.+?.jpg)"'

    deep_url = re.search(url_add, response.text)

    # 返回的對象需要,通過 group() 進行提取
    # print(deep_url.group(1))

    return deep_url.group(1)

1.5.5 下載圖片——非重點

def download_jpg(local_path):
    """通過獲取的地址列表下載壁紙

    :param local_path: 下載到的本地位置
    """

    global name_jpg
    global gl_url_ok_num

    # for url in url_list:
    while True:

        # print("開始下載圖片...")
        # url = q_url.get()
        if flag_download == 1:
            if url_deep_list:
                url = url_deep_list[0]
                url_deep_list.pop(0)

                # name_jpg += 1

                response = requests.get(url)
                # 請求成功以後,正式開始下載時,才給它 +1 起名字,因為 +1 操作不需要等待,
                # 所以不會切換協程,直接通過 open 已經為文件起好名字了,即使下載等待,
                # 也不會影響已經有名字的文件了
                # 如果 name_jpg += 1 放上面會出現當 協程遇到 requests 時網路請求,在等待時間,會讓第二個協程來執行,
                # 這樣 name_jpg += 1,這樣等待第一個協程開始下載時,名字就會變為 3,
                # 而且極有可能出現各種BUG,經過試驗:名字會從3開始,而且最終少了兩個文件
                name_jpg += 1

                with open('%s/%d.jpg' % (local_path, name_jpg), 'wb') as ft:
                    ft.write(response.content)

                # print("成功下載一張壁紙...")

                gl_url_ok_num += 1

                if not url_deep_list:
                    # print("全部下載完畢...")
                    break
            else:
                break
                # print(url)
        else:
            print("還沒有爬取到地址...無法開始下載")
            gevent.sleep(2.5)
            continue

    # write("%s/num.txt" % local_path, str(name_jpg))

1.5.6 控制爬取的頁面(從第幾頁開始,爬取若干頁)——非重點

① 第一頁

② 第二頁

③ 第三頁(這回懂了吧,改變 url 的部分參數便可以訪問不同頁面)

def crawl_url(page_num, page_start):
    """爬取出一個個下載地址,並加入列表 url_deep_list 當中

    :param page_num: 需要爬取的網頁數
    :param page_start: 從第幾個頁面開始爬取
    """
    global flag_download
    global url_num

    print("開始爬取地址...")

    j = 0
    flag = 0
    while j < page_num:

        if flag == 0:
            if page_start == 1:
                url_list_surface = get_surface_url('//www.netbian.com/erciyuan/index.htm')
            else:
                url_list_surface = get_surface_url('//www.netbian.com/erciyuan/index_%d.htm' % (page_start + j))
            flag = 1
        else:
            url_list_surface = get_surface_url('//www.netbian.com/erciyuan/index_%d.htm' % (page_start + j))

        for url_surface in url_list_surface:

            url_deep = get_deep_url(url_surface)
            # TODO url_deep 是單個圖片的最終的下載地址,把它存入隊列
            # print(url_deep)
            url_deep_list.append(url_deep)

        print()
        j += 1
    # 等所有網頁爬取完畢,才開始下載,是為了能讓下載進度能讀取到總下載圖片數量
    url_num = len(url_deep_list)
    flag_download = 1

    print("爬取地址完畢...")
    # return url_list_deep

1.5.7 顯示下載進度(附加功能)——非重點

def download_rate():

    global url_num
    global gl_url_ok_num

    while True:
        if flag_download == 1:

            url_ok_num = gl_url_ok_num

            print("\r下載進度: %.2f%%---[%d/%d]..." %
                  (url_ok_num * 100 / url_num, url_ok_num, url_num), end="")

            gevent.sleep(0.5)

            if url_ok_num == url_num:
                print("\n全部下載完畢...")
                break
        else:
            print("還未爬取完地址...無法顯示下載進度")
            gevent.sleep(3)
            continue

1.5.8 主函數(看不懂就從主函數看起)

def craw_jpg():
    """
    主函數
    """

    global name_jpg

    page_start = int(input("你希望從第幾頁開始爬取:"))
    page_num = int(input("請輸入你要爬取的頁數:"))
    local_path = str(input("請輸入你要保存的地址:"))

    num_txt_path = local_path + "/num.txt"

    # TODO 不能通過 windows 來創建 num.txt 因為這樣創建的 utf-8 文本不純凈,是BOM編碼格式的,
    #  前面會有幾個字元,導致 int() 轉換出現問題。所以應該讓 python 來自行創建

    try:  # 先嘗試打開 num.txt ,無需賦值,只是為了了解是否存在 num.txt 這個文件,下面還是會進行讀取的賦值的
        read(num_txt_path)

    # 如果 發現異常——沒有num.txt這個文件,則會 write 自動創建文件並寫入數據
    except FileNotFoundError:
        print("沒有發現命名文件[num.txt] ...")
        name_jpg = input("請輸入你要為文件命名的開頭:")
        write(num_txt_path, str(name_jpg))
    else:
        print(" num.txt 文件已存在...")
        print()
    finally:
        name_jpg = int(read(num_txt_path)) - 1

    # C:/Users/Administrator/Desktop/爬取圖片
    # name_jpg = int(read(num_txt_path))
    print(name_jpg)

    # TODO 生成5個協程 1個爬取網址和3個下載,1個顯示下載進度
    gevent.joinall([
        gevent.spawn(crawl_url, page_num, page_start),
        gevent.spawn(download_jpg, local_path),
        gevent.spawn(download_jpg, local_path),
        gevent.spawn(download_jpg, local_path),
        gevent.spawn(download_rate,),
    ])

    # 是為了使下一次下載的名字是正常順序(非重點)
    write("%s/num.txt" % local_path, str(name_jpg + 1))


if __name__ == "__main__":

    # 爬取網址: //www.netbian.com/erciyuan/index.htm
    # 下載到本地地址: C:/Users/Administrator/Desktop/爬取圖片
    craw_jpg()

1.5.9 這是txt文件寫入與讀取部分(用於為圖片名稱自動命名)——非重點

1.5.9.1 txt 讀取
def read(file):

    # 打開文件
    num_file = open(file, "r", encoding="Utf-8")

    # 現將指針放回開頭
    num_file.seek(0, 0)

    num_read = num_file.readline()

    # 關閉文件
    num_file.close()

    return num_read


if __name__ == "__main__":
    num = read("num.txt")
    print(num)
    print()
    print("正在測試 read_num 模組...")
1.5.9.2 txt 寫入
def write(file, i):

    # 打開文件
    num_file = open(file, "w", encoding="Utf-8")

    # 現將指針放回開頭
    num_file.seek(0, 0)

    num_file.write(i)

    # 關閉文件
    num_file.close()


if __name__ == "__main__":
    write("num.txt", str(2))

    print("正在測試 read_num 模組...")

程式運行結果:

你希望從第幾頁開始爬取:1
請輸入你要爬取的頁數:2
請輸入你要保存的地址:C:/Users/Administrator/Desktop/爬取圖片
沒有發現命名文件[num.txt] ...
請輸入你要為文件命名的開頭:1
0
開始爬取地址...
還沒有爬取到地址...無法開始下載
還沒有爬取到地址...無法開始下載
還沒有爬取到地址...無法開始下載
還未爬取完地址...無法顯示下載進度


爬取地址完畢...
下載進度: 100.00%---[34/34]...
全部下載完畢...

二、總結

一、演算法思想

  1. 每個表層網頁有若干張圖片,是我們最終需要下載的圖片(1920*1080)的縮小版用於展示的小圖片。
  2. 但我們可以通過爬取該頁面的這些圖片的鏈接url_suface,通過 get_deep_url( ) 進入深層頁面,深層頁面中間有個大圖
  3. 再通過爬取該頁面的最終下載網址得到一個個的 url_deep,最終將這些網址放入 url_deep_list 列表中
  4. 最後通過 url_deep_list 一個個取出下載

二、注意事項

  1. 先將所有地址爬取完畢再進行下載,方便統計需下載的總圖片數量,用於顯示下載進度的圖片總數

  2. 使用協程進行下載,要注意合理分配時間給用於 下載的協程 和用於 顯示下載進度的協程

    因為協程的時間分配是很智慧的,由於下載圖片是需要請求且等待的過程,協程會自動將這些多餘的時間分配給其他協程,這樣顯示下載進度的協程總是能分配到很多時間,導致用於下載協程出現阻塞,從而導致下載速度極慢!

  3. 所以我們要讓 顯示下載進度的協程 每隔0.5s顯示一次(gevent.sleep(0.5)),那麼這0.5s就可以分配給 用於下載的協程

最後再附上下載地址:

鏈接://pan.baidu.com/s/1SVIMVzmPIEJ7kEsvMpK23Q
提取碼:fkgz
算是簡單的項目了(演算法很容易),只是附加的功能看起來可能有點複雜,其實也有一大部分程式碼都是為了防止bug
有不足之處,懇請大佬指出orz!
或有看不懂的朋友也可以評論,我會儘快恢復的啦~ღ( ´・ᴗ・` )比心

Tags: