(協程)壁紙爬取
(協程)壁紙爬取
一、 演算法解析
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]...
全部下載完畢...
二、總結
一、演算法思想
- 每個表層網頁有若干張圖片,是我們最終需要下載的圖片(1920*1080)的縮小版用於展示的小圖片。
- 但我們可以通過爬取該頁面的這些圖片的鏈接 即 url_suface,通過 get_deep_url( ) 進入深層頁面,深層頁面中間有個大圖
- 再通過爬取該頁面的最終下載網址得到一個個的 url_deep,最終將這些網址放入 url_deep_list 列表中
- 最後通過 url_deep_list 一個個取出下載
二、注意事項
-
先將所有地址爬取完畢再進行下載,方便統計需下載的總圖片數量,用於顯示下載進度的圖片總數
-
使用協程進行下載,要注意合理分配時間給用於 下載的協程 和用於 顯示下載進度的協程
因為協程的時間分配是很智慧的,由於下載圖片是需要請求且等待的過程,協程會自動將這些多餘的時間分配給其他協程,這樣顯示下載進度的協程總是能分配到很多時間,導致用於下載協程出現阻塞,從而導致下載速度極慢!
-
所以我們要讓 顯示下載進度的協程 每隔0.5s顯示一次(gevent.sleep(0.5)),那麼這0.5s就可以分配給 用於下載的協程了
最後再附上下載地址:
鏈接://pan.baidu.com/s/1SVIMVzmPIEJ7kEsvMpK23Q
提取碼:fkgz
算是簡單的項目了(演算法很容易),只是附加的功能看起來可能有點複雜,其實也有一大部分程式碼都是為了防止bug
有不足之處,懇請大佬指出orz!
或有看不懂的朋友也可以評論,我會儘快恢復的啦~ღ( ´・ᴗ・` )比心