簡書支持打包下載所有文章功能,可以方便作者轉(zhuǎn)移或保存槐秧。但是圖片不支持自動下載儒拂,最近在學(xué)Python,便寫了一個md圖片下載器色鸳。
目標(biāo)
本人 Python 新手社痛,歡迎大佬指點(diǎn)。本文主要是對源碼進(jìn)行解讀命雀,期望實(shí)現(xiàn)以下目標(biāo):
- 一鍵下載所有Markdown文件中的圖片蒜哀,并保存到本地。
- 圖片根據(jù)文章分類
- 簡單易用吏砂。
先上最終效果:
實(shí)現(xiàn)步驟
- 搜索指定文件夾撵儿,找出文件夾及子文件包含的md文件。
- 匹配出md文件中所有的圖片狐血。
- 所有圖片異步下載淀歇。
- 下載報告與GUI。
- Python 打包工具匈织。
1. 搜索文件夾中md文件
首先我們要根據(jù)用戶指定的文件夾浪默,搜索出該文件夾及其子文件夾中包含的md文件,并將這些文件的絕對路徑加到數(shù)組當(dāng)中缀匕。
class Directory(object):
@classmethod
def find_sub_path(cls, path):
# 初始化一個空的文章列表
article_list = []
# 獲取該文件夾下的所以子文件
temp_files = os.listdir(path)
# 遍歷子文件
for temp_file in temp_files:
# 拼接該文件絕對路徑
full_path = os.path.join(path, temp_file)
# 匹配.md文件
if os.path.isfile(full_path) and os.path.splitext(full_path)[1] == ".md":
# 如果是.md文件 加入文章列表
article = Article(full_path)
article_list.append(article)
# 如果是文件夾 進(jìn)行遞歸繼續(xù)搜索
elif os.path.isdir(full_path):
# 將子文件夾中的文章列表拼接到上級目錄的文章列表中
article_list.extend(cls.find_sub_path(full_path))
return article_list
2. 匹配出md文件中的圖片
為每一篇文章新建一個存放該文章中圖片的文件夾纳决,然后利用正則匹配出該文章中的所以圖片,并保存到圖片數(shù)組中乡小。
# 文章類
class Article(object):
def __init__(self, path):
# 文章的絕對路徑
self.article_path = path
# 拼接文章圖片下載后保存的路徑
self.article_pic_dir = path.replace(".md", "_Image")
# 新建保存文章圖片的文件夾
self.mkdir_image_dir()
# 開始搜索圖片
self.pic_list = self.find_pics(self.article_path)
# 查找圖片
def find_pics(self, article_path):
# 打開md文件
f = open(article_path, 'r')
content = f.read()
pics = []
# 匹配正則 match ![]()
results = re.findall(r"!\[(.+?)\)", content)
index = 0
for result in results:
temp_pic = result.split("](")
# 將圖片加入到圖片數(shù)組當(dāng)中
if len(temp_pic) == 2:
pic = Picture(temp_pic[0], temp_pic[1], self.article_pic_dir, index)
pics.append(pic)
index += 1
f.close()
return pics
# 新建圖片的保存文件夾
def mkdir_image_dir(self):
# 如果該文件夾不存在 則新建一個
if not os.path.exists(self.article_pic_dir):
os.mkdir(self.article_pic_dir)
3. 下載圖片
簡單地判斷了圖片是否有類型阔加,拼接圖片的保存路徑與命名。檢查圖片是否重復(fù)下載满钟,檢查圖片鏈接是否合法胜榔,下載并且保存圖片,同時做了下載失敗的處理和保存失敗的處理湃番,保存了下載失敗的原因夭织,以便后面生成報告。
# 圖片類
class Picture(object):
def __init__(self, name, url, dir_path, index):
# 該圖片順序下標(biāo) 用于設(shè)置默認(rèn)圖片名字
self.index = index
# 圖片名
self.name = name
# 圖片鏈接
self.url = url
# 圖片保存路徑
self.dir_path = dir_path
# 圖片下載失敗原因
self.error_reason = None
# 開始下載
def start_download_pic(self, download_pic_callback):
pic_path = self.build_pic_name()
# 已存在則不重復(fù)下載
if os.path.exists(pic_path):
print ('pic has existed:' + self.url)
self.error_reason = "pic has existed:"
download_pic_callback(self)
return
# 圖片鏈接前綴不包含http
if not self.url.startswith("http"):
print ('pic has invalid url:' + self.url)
self.error_reason = "pic has invalid url"
download_pic_callback(self)
return
header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36',
'Cookie': 'AspxAutoDetectCookieSupport=1',
}
# 下載圖片
request = urllib2.Request(self.url, None, header)
try:
response = urllib2.urlopen(request, timeout=10)
# 下載失敗
except Exception, error:
print ('pic cannot download:' + self.url)
self.error_reason = str(error)
download_pic_callback(self)
return
# 保存圖片
try:
fp = open(pic_path, 'wb')
fp.write(response.read())
fp.close()
# 保存失敗
except IOError, error:
print(error)
self.error_reason = str(error)
download_pic_callback(self)
return
# 下載完成回調(diào)
download_pic_callback(self)
# 組裝圖片保存命名
def build_pic_name(self):
# 剪去圖片鏈接后的參數(shù)
pic_url = self.url.split("?")[0]
# 獲取圖片格式后綴 如果沒有 默認(rèn)jpg
urls = pic_url.split(".")
if len(urls) > 1:
pic_type = urls[len(urls)-1]
else:
pic_type = "jpg"
# 組裝圖片命名
if self.name is not None and len(self.name) > 0:
pic_name = self.name + "." + pic_type
else:
pic_name = "no_name_" + str(self.index) + "." + pic_type
pic_path = os.path.join(self.dir_path, pic_name)
return pic_path
4. 下載報告與GUI
a. 本來 XTImageDownloader
和 GUI
應(yīng)該分開牵辣,但是對 Tkinter
理解還有限摔癣,所以就直接寫,方便調(diào)用纬向。
這部分代碼主要是 選擇文件夾 和 開始搜索并下載 的 UI 及邏輯處理择浊。
class XTImageDownloader(object):
def __init__(self):
# 數(shù)據(jù)
self.download_error_list = []
self.all_pic_count = 0
self.current_pic_index = 0
self.thread_lock = threading.Lock()
self.search_button = None
# 圖形界面相關(guān)
self.root = Tk()
self.root.title("XTImageDownloader")
self.path = StringVar()
self.title = StringVar()
self.title.set("請選擇Markdown文件所在文件夾")
self.list_box = None
Label(self.root, textvariable=self.title).grid(row=0, column=1)
Label(self.root, text="文件夾路徑:").grid(row=1, column=0)
Entry(self.root, textvariable=self.path).grid(row=1, column=1)
Button(self.root, text="選擇路徑", command=self.select_path).grid(row=1, column=2)
self.root.mainloop()
# 選擇文件夾
def select_path(self):
self.path.set(tkFileDialog.askdirectory())
# 用戶選中文件夾之后 顯示下載按鈕
if self.path.get() != "":
self.search_button = Button(self.root, text="開始搜索并下載", command=self.start_search_dir)
self.search_button.grid(row=2, column=1)
return self.path
# 開始搜索文件夾 并且下載
def start_search_dir(self):
self.search_button['state'] = DISABLED
self.search_button['text'] = "正在下載..."
self.all_pic_count = 0
self.current_pic_index = 0
self.download_error_list = []
# 獲取Markdown文件列表
article_list = Directory.find_sub_path(self.path.get())
# 更新搜索進(jìn)度 刷新UI
for article in article_list:
self.all_pic_count += len(article.pic_list)
self.change_title(self.all_pic_count, self.current_pic_index)
# 開始下載圖片
for article in article_list:
for pic in article.pic_list:
# 開啟異步線程下載圖片 并且傳入下載完成的回調(diào)
thread = threading.Thread(target=pic.start_download_pic, args=(self.download_pic_callback,))
thread.start()
b. 開啟異步線程下載圖片,回調(diào)函數(shù)作為入?yún)魅搿?/p>
// 異步下載
thread = threading.Thread(target=pic.start_download_pic, args=(self.download_pic_callback,))
thread.start()
回調(diào)函數(shù)更新 UI 告知下載進(jìn)度逾条,由于開啟了多線程琢岩,這里需要加鎖,防止資源競爭师脂。全部下載成功后生成報告担孔。
# 下載圖片完成后的回調(diào)函數(shù)
def download_pic_callback(self, pic):
# 獲取線程鎖
self.thread_lock.acquire()
# 如果下載失敗 則保存到失敗列表
if pic.error_reason is not None and len(pic.error_reason) > 0:
self.download_error_list.append(pic)
self.current_pic_index += 1
# 更新下載進(jìn)度 刷新UI
print('finish:' + str(self.current_pic_index) + '/' + str(self.all_pic_count))
self.change_title(self.all_pic_count, self.current_pic_index)
# 全部下載成功 刷新UI 生成失敗報告
if self.all_pic_count == self.current_pic_index:
self.search_button['text'] = "下載完成"
self.print_error(self.download_error_list)
# 釋放鎖
self.thread_lock.release()
# 更新下載進(jìn)度 刷新UI
def change_title(self, total_num, current_num):
self.title.set("已完成" + str(current_num) + "/" + str(total_num))
c. 生成失敗報告,利用 list_box 來展示錯誤列表吃警,并且添加了滑塊糕篇,可以滑動瀏覽。
# 生成失敗列表
def print_error(self, download_error_list):
# python log
print("-----------------------------------")
print("some pic download failure:")
for pic in download_error_list:
print("")
print("name:" + pic.name)
print("url:" + pic.url)
print("error_reason:" + pic.error_reason)
Label(self.root, text="部分圖片下載失敗:").grid(row=4, column=1)
# GUI
# 新建listbox
self.list_box = Listbox(self.root)
for pic in download_error_list:
self.list_box.insert(END, pic.url + " -> " + pic.error_reason)
self.list_box.grid(row=5, column=0, columnspan=3, sticky=W+E+N+S)
# 垂直 scrollbar
scr1 = Scrollbar(self.root)
self.list_box.configure(yscrollcommand=scr1.set)
scr1['command'] = self.list_box.yview
scr1.grid(row=5, column=4, sticky=W+E+N+S)
# 水平 scrollbar
scr2 = Scrollbar(self.root, orient='horizontal')
self.list_box.configure(xscrollcommand=scr2.set)
scr2['command'] = self.list_box.xview
scr2.grid(row=6, column=0, columnspan=3, sticky=W+E+N+S)
d. 最后貼上引用到的標(biāo)準(zhǔn)庫(Python 2.7):
-
#!/usr/bin/env python
和# -*- coding:utf-8 -*-
這兩行是為了支持中文酌心。 -
os
和os.path
用于系統(tǒng)及文件查找拌消。 -
re
正則匹配。 -
urllib2
網(wǎng)絡(luò)庫安券,用于下載圖片墩崩。 -
Tkinter
和tkFileDialog
是 GUI 庫。 -
threading
多線程庫侯勉。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import os.path
import re
import urllib2
from Tkinter import *
import tkFileDialog
import threading
5. Python 打包工具
這部分主要是講如何把 Python 腳本打包打包成可以在 Mac 上運(yùn)行的應(yīng)用鹦筹。
PyInstaller
[圖片上傳失敗...(image-fd72fe-1516382674300)]
PyInstaller 是一個打包工具,可以幫助你打包 Python 腳本址貌,生成應(yīng)用铐拐。
安裝 PyInstaller
$ pip install pyinstaller
打包后在dist
文件夾中可找到可執(zhí)行文件
$ pyinstaller yourprogram.py
生成 app
$ pyinstaller --onedir -y main.spec
py2app
py2app 也是打包工具,這里只是簡單介紹一下练对,想要深入了解詳細(xì)內(nèi)容可以自行搜索余舶。
切換到你的工程目錄下
$ cd ~/Desktop/yourprogram
生成 setup.py 文件
$ py2applet --make-setup yourprogram.py
生成你的應(yīng)用
$ python setup.py py2app
DMG Canvas
DMG Canvas 可以將 Mac 應(yīng)用打包成 DMG 鏡像文件,并且傻瓜式操作锹淌。
總結(jié)
剛開始只是寫了很簡單的一部分功能匿值,后來慢慢完善,逐漸才有了現(xiàn)在的樣子赂摆,在這個過程中學(xué)到了很多東西挟憔,包括 Python 中的 GUI 和多線程操作,如何生成應(yīng)用程序烟号。希望能對一部分人有所幫助绊谭。
最后貼上Demo,本人 Python 2.7 環(huán)境下運(yùn)行的汪拥, Python 3以上是無法運(yùn)行的达传。