前情提要:Python爬蟲初體驗(1):利用requests和bs4提取網站漫畫
前幾天有些放松懈怠,并沒有做多少事情……這幾天要加油了!7月的計劃要抓緊時間完成!
今天瘋狂肝這個程序,算是暑假睡得最晚的一天了……(不過程序仍然有問題)
好的廢話不多說,進入正題
總結了下上次的爬蟲體驗。雖然能保證穩定下載,但是下載 50 張XKCD漫畫花費的時間達到了將近 10 分鐘,效率比較低。
所以這次學習了多線程,以求達到較快下載完全部 2000 余張漫畫的目標。
(另外配合 V 姓網絡加速工具保證連接外網的質量)
額外加入了 threading 模塊來實現多線程。
另外,改進了一下代碼風格,變量名稱
threading 庫中,Thread()?是進行多線程操作的關鍵。
在這里簡單的應用:threading.Thread(target=xxxx, args=(), kwargs=None)
(target 指向函數本身,args 為向目標函數傳遞的常規參數,kwargs 為傳遞的關鍵字參數)
然后!
一樣的方法去弄就可以了……
……
其實并不行。必須要把提取—解析—下載—存儲的全過程函數化,這樣才能實現多線程。
于是索性把所有過程都寫成了函數里……看起來雖然增加了代碼量,但是用起來就會很方便。
這里出現了一個問題:我開了?5 個線程,假如圖片一共有 2000 張還行,有 2003?張怎么辦?
emm,前 4 個線程下載 401 張圖,第 5 個下載 399 張圖就可以啦!
但是,如果前 400 張圖比較小,第 401-800 張圖比較大,這樣的話第一個線程結束時間遠早于第二個,如何解決?
其實可以挨個下載:不預先分配每個線程下載哪些圖片,直接
……
吐槽:居然花了我 8?個小時來搞這段代碼!沒想到這部分是這么難弄,
搞的來和當年學 OI 時有一樣的心情了。
(心情簡單.jpg)
不得不說,學 OI 的時候調試代碼的過程,和現在很類似。只是這里涉及更多的是實際操作而非算法。
實際操作就要考慮異常,異常處理,維護,等等。所以花時間也是避免不了的啦……
然后就可以繼續等待它慢慢扒圖……
……
……
然后就發現了一堆莫名其妙的錯誤!嗚嗚嗚……
(一共下載了 607 張圖片,最后 5 個線程全部斷掉了……為什么我設置了超時重試都還會 Timeout Error……QAQ)
(最好笑的還是 retry 4 in 3 times……這個是如何做到的)
再來。
后來發現,拋出異常時,僅僅針對?Readtime Error 的異常來解決問題。還有 Connection Error 等沒有處理。這個是最主要的問題。
某些地方寫入文件會有異常?嗯,這個暫時不了解原因。好像是圖片的鏈接讀取錯誤?
于是加入了兩種連網的錯誤處理。
為了防止連網出錯,特意把重試次數設為了 4 次,超時 Timeout 設為了 4 秒。
……
然而還是出錯了!
這次總算是查到了失敗的原因。
XKCD漫畫還真的有些不同尋常,例如:第 404 張漫畫竟然……就是一個 404 not found 的網站???
第 1350,2067 張漫畫居然有用戶交互的方式?
怪說不得線程又終止在這里了……
先把它們記下來,晚些時候再去解決吧。
發一個
不規范的
源碼存檔:
#! python3
# Upgraded version. Use Threading to speed-up the download process.
import os,requests,bs4,threading,math,time
url = "http://xkcd.com/"
errList = []
mutex = threading.Lock()
def createDir():
os.chdir("G:\\work\\gjmtest")
os.makedirs(".\\comicsplus2", exist_ok=True)
os.chdir(".\\comicsplus2")
def getResource(link,num=None,notify=False,tle=4): # default timeout: 3 seconds
count = 1
while count <= 4:
try:
if num is None:
res = requests.get(link, timeout=tle)
else:
res = requests.get(link+str(num), timeout=tle)
res.raise_for_status()
return res
except:
count += 1
if notify is True and count <= 4:
print("Timeout. Retry %d in 3 times..." % count)
if num is None:
raise TimeoutError("Can't connect to "+url+".Please check your Internet configuration.")
else:
raise TimeoutError("Can't connect to "+url+str(num)+".Please check your Internet configuration.")
def getSoup(res):
soup = bs4.BeautifulSoup(res.text, features="html.parser")
return soup
def getImageNum(soup):
b = soup.select("#middleContainer") # find image num
picNumString = b[0].text
picNumPosStart = picNumString.find("xkcd.com")
picNumPosEnd = picNumString.find("Image URL")
picNum = picNumString[(picNumPosStart + 9):(picNumPosEnd - 2)]
return picNum
def getImageUrl(soup):
k = soup.select("#comic img")
picUrl = k[0].get("src") # //img.xxxxxx
return picUrl
def writeFile(num, pic_res, pic_link, path="G:\\work\\gjmtest\\comicsplus2\\"):
f = open(path + num + '_' + os.path.basename(pic_link), "wb")
for chunk in pic_res.iter_content(100000):
f.write(chunk)
f.close()
def errorRetry():
for i in errList: # for each picture number (error occurred)
download(i)
def download(i): # download particular picture
print("Downloading picture %d..." % i)
try:
res = getResource(url, num=i, notify=True)
except:
mutex.acquire()
errList.append(i)
print("Error occurred: picture %d !!!" % i)
mutex.release()
return
print("Parsing webpage of picture %d..." % i)
soup = getSoup(res)
num = getImageNum(soup)
picurl = getImageUrl(soup) # picture resources
picres = requests.get("http:" + picurl)
print("Writing picture %d..." % i)
writeFile(num, picres, picurl)
print("Succeeded in picture %d." % i)
def downloadSeq(start, end): # [start, end)
for i in range(start, end):
download(i)
def createThread(total_num):
count = 1
th = 0
thread_list = []
remain = total_num
while remain > groupNum:
thread_list.append(threading.Thread(target=downloadSeq, args=(count, count + groupNum)))
thread_list[th].start()
remain -= groupNum
count += groupNum
th += 1
thread_list.append(threading.Thread(target=downloadSeq, args=(count, total_num)))
thread_list[th].start()
for t in thread_list:
t.join()
def getTotalImage(): # one-time use
try:
res = getResource(url)
except:
print("Your network is too bad!")
exit()
soup = getSoup(res)
num = int(getImageNum(soup))
print("Total image number is %d." % num)
print("Downloading process started.")
return num
if __name__ == "__main__":
startTime = time.time()
createDir()
totalNum = getTotalImage()
threads = 5
groupNum = math.ceil(totalNum / threads)
createThread(totalNum)
errorRetry()
endTime = time.time()
timeCost = round(endTime - startTime, 1)
print("Done. Total time: %s sec." ,str(timeCost))
我保證,以后一定要 11 點準時睡覺 = =||
不說了,累死啦~趕快滾去睡
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
