「微硬核」下圖下到手抽筋?手把手教你用腳本解救你的手

我們時常會在網上瀏覽網頁的時候,看到很多美美的圖片。有些時候因為喜歡想保存下來,有些時候因為網頁瀏覽太慢,就想把圖片下載到電腦上方便查看。但是,下載圖片除非是網站提供了打包下載的功能。否則都是一張張點右鍵另存為,然後選擇保存路徑,才能下載下來。

好一點的情況是,一套圖片幾十張都在一個頁面,雖然操作到手抽筋,但好歹一個頁面搞定。但,有些每頁一個的,就需要打開幾十個頁面再一個個保存。更何況,面對美美的圖片,我們想的是,我!全!要!忙到手抽筋也沒法把一個網站的圖片全部保存下來啊。

「微硬核」下圖下到手抽筋?手把手教你用腳本解救你的手

這些圖片,我!全!要!

好的,你需要的是一個自動下載圖片的工具。網上其實有一些類似工具,但是其實自己寫也是非常容易的。本文就是手把手教你寫一個下載工具,自由下載,解放雙手。

寫工具用啥語言?“人生苦短,我用python”,所以當然是python了。

需要預先準備的:

  • https://www.python.org/downloads/release/python-375/ 下載python,一般都是windows版本的,現在基本都64位的,所以選擇Windows x86-64 executable installer。安裝的時候記得選擇“add to path”的選項。
  • 安裝完python,咱們還得裝幾個方便使用的庫,bs4和requests。bs4就是beautifulsoup,解析html的,requests就是方便http請求的。打開powershell,執行下面的命令
pip install bs4 -i https://mirrors.aliyun.com/pypi/simple/
pip install requests -i https://mirrors.aliyun.com/pypi/simple/

為加快安裝速度,咱們選用的是pip的國內鏡像。 -i https://mirrors.aliyun.com/pypi/simple/就是指定阿里雲的pip鏡像。

接下來選擇一個下載圖片的網站,為避免廣告導流嫌疑(雖然沒啥量),我百度上隨便搜了一個,中關村的圖片精選頁面 http://bbs.zol.com.cn/dcbbs/topic。當然實際大家自己用的肯定是自己要下載圖片的頁面地址。

好的,下面要開始碼了,碼之前我們先說說思路。總的來說,就是抓取站點頁面,然後把頁面html解析之後獲取圖片地址,再下載圖片,保存到本地。難點就是,為了抓取全部頁面,我們要解析html的時候,獲取當前頁面所有的本站地址,依次去請求處理,還得注意排除已經請求過的。

coding begin...

咱們打開自帶的edle編輯器,File->New File創建一個新文件,保存為downloader.py。

先加載庫

import bs4
import requests
import os
import logging
import urllib.parse
import sys

整個功能咱們劃分成幾個模塊,功能分別如下:

  • 請求並解析頁面,獲取當前頁面圖片地址和站內鏈接地址
  • 根據上一步返回的站內鏈接地址,判斷是否重複,如果沒處理過,則使用上一步的方式處理該鏈接
  • 請求圖片地址,讀取之後保存到本地

咱們要完成的第一個功能模塊就是請求並解析頁面的方法,因為功能簡單單一,都用函數開發,不採用類。

 headers = {'user-agent': 'image downloader/0.0.1'}

def is_same_domain(base_url,url):
 base_url_parsed = urllib.parse.urlparse(base_url)
 url_parsed = urllib.parse.urlparse(url)
 return base_url_parsed.netloc == url_parsed.netloc

def process_page(url):
 '''
獲取url下所有圖片以及站內鏈接
'''
 images = []
 site_urls=[]
 try:
 page_req = requests.get(url, headers=headers)
 if page_req.status_code != 200:
 logging.error(url+' http code: '+str(page_req.status_code))
 return images,site_urls
 page_parsed = bs4.BeautifulSoup(page_req.text, 'html.parser')
 images = [ img.attrs['src'] for img in page_parsed.find_all('img')]
 site_urls= [ a.attrs['href'] for a in page_parsed.find_all('a') if 'href' in a.attrs and is_same_domain(url,a.attrs['href'])]
 logging.info("processed "+url)
 return images,site_urls
 except Exception as e:
 logging.error(url+' '+str(e))
 return images,site_urls

工具函數is_same_domain判斷解析的網頁地址是否和當前處理的地址是一個域名。process_page返回的是兩個list,一個是當前頁面所有的圖片地址,第二個是當前頁面所有的站內鏈接。圖片不需要判斷是否本域名,因為圖片一般會用專門的服務器,或者cdn,域名不同是很正常的事情。

接下來是根據頁面獲取的圖片地址,抓取圖片保存到本地的模塊。

def store_image(save_path, img_url):
 '''
將地址為img_url的圖片保存到save_path目錄下。
'''
 img_url_parsed = urllib.parse.urlparse(img_url)
 try:
 img_path = save_path+'/'+os.path.dirname(img_url_parsed.path).strip('/').replace('/','_')
 img_file_path = img_path + '/' + os.path.basename(img_url_parsed.path)
 img_req = requests.get(img_url, headers=headers)
 if img_req.status_code != 200:
 logging.error(img_url+' http code: '+str(img_req.status_code))
 return False
 if len(img_req.content)< 100*1024:
 logging.warning(img_url+" too small")
 return True
 if not os.path.exists(img_path):
 os.makedirs(img_path)
 with open(img_file_path,"wb") as fp:
 fp.write(img_req.content)
 logging.info("saved "+img_url)
 except Exception as e:
 logging.error(img_url+' '+str(e))

函數store_image將圖片保存到指定的路徑save_path下,如果目錄不存在,會自動創建,這裡過濾掉了大小在100k以下的圖片。這裡為了簡化,減少了圖片的目錄深度,原地址裡面的路徑都會轉化為下劃線分隔的一個目錄,使得多級目錄簡化為一級。

再加個控制函數就大功告成了。

def site_download(url,save_path):
 visited_url = {}
 stored_img = {}
 to_visit = [url]
 while len(to_visit)>0:
 url = to_visit.pop()
 imgs,urls = process_page(url)
 visited_url[url]=1
 for img in imgs:
 if img not in stored_img:
 stored_img[img]=1
 store_image(save_path,img)
 for u in urls:
 if u not in visited_url:
 to_visit.append(u)

site_download接收傳入的起始地址和圖片保存路徑,然後遞歸處理所有頁面以及子頁面,並判斷是否處理過。這裡簡化了處理,都用map去表示是否訪問過,因為一般幾十萬上百萬的頁面,應該不會佔用太多內存。

加個腳本執行的入口就可以了

if __name__=="__main__":
 if len(sys.argv) < 3:
 print("usage: python downloader.py site_url img_path")
 sys.exit(1)
 site_download(sys.argv[1],sys.argv[2])

使用方法就是 python downloader.py 網站路徑 圖片保存路徑

好的,早已按捺不住了,趕緊來跑一跑吧。

「微硬核」下圖下到手抽筋?手把手教你用腳本解救你的手

日誌好多

日誌好多,大部分警告和錯誤都是無用的日誌,比如https證書有問題的,網頁太久失效的,以及大部分都是圖片太小的。

我執行參數裡面,圖片保存路徑是當前目錄下的images目錄,打開看看。

「微硬核」下圖下到手抽筋?手把手教你用腳本解救你的手

全部到碗裡來了

嗯哼,全部到碗裡來了。

注意一點,這種訪問方式對服務器負載比較高,有些網站會封掉高頻訪問的ip的,所以注意在訪問時可以適量sleep降低頻率。

另外腳本會遍歷網站有鏈接的所有頁面,所以比較慢,慢慢等著吧。

最後,本文給出的只是一個抓取網站內容的代碼框架,能完成基本工作,但是很簡單,沒有處理各種可能的異常,沒有細緻地處理頁面。有興趣的同學可以自己根據需求增加細節完善,比如sleep降頻,比如根據網頁內容把同一篇文章的圖片聚合,比如根據網頁結構只抓取其中感興趣的區域的圖片等等。


分享到:


相關文章: