前情提要:Python爬蟲(chóng)初體驗(yàn)(1):利用requests和bs4提取網(wǎng)站漫畫
前幾天有些放松懈怠,并沒(méi)有做多少事情……這幾天要加油了!7月的計(jì)劃要抓緊時(shí)間完成!
今天瘋狂肝這個(gè)程序,算是暑假睡得最晚的一天了……(不過(guò)程序仍然有問(wèn)題)
好的廢話不多說(shuō),進(jìn)入正題
總結(jié)了下上次的爬蟲(chóng)體驗(yàn)。雖然能保證穩(wěn)定下載,但是下載 50 張XKCD漫畫花費(fèi)的時(shí)間達(dá)到了將近 10 分鐘,效率比較低。
所以這次學(xué)習(xí)了多線程,以求達(dá)到較快下載完全部 2000 余張漫畫的目標(biāo)。
(另外配合 V 姓網(wǎng)絡(luò)加速工具保證連接外網(wǎng)的質(zhì)量)
額外加入了 threading 模塊來(lái)實(shí)現(xiàn)多線程。
另外,改進(jìn)了一下代碼風(fēng)格,變量名稱
threading 庫(kù)中,Thread()?是進(jìn)行多線程操作的關(guān)鍵。
在這里簡(jiǎn)單的應(yīng)用:threading.Thread(target=xxxx, args=(), kwargs=None)
(target 指向函數(shù)本身,args 為向目標(biāo)函數(shù)傳遞的常規(guī)參數(shù),kwargs 為傳遞的關(guān)鍵字參數(shù))
然后!
一樣的方法去弄就可以了……
……
其實(shí)并不行。必須要把提取—解析—下載—存儲(chǔ)的全過(guò)程函數(shù)化,這樣才能實(shí)現(xiàn)多線程。
于是索性把所有過(guò)程都寫成了函數(shù)里……看起來(lái)雖然增加了代碼量,但是用起來(lái)就會(huì)很方便。
這里出現(xiàn)了一個(gè)問(wèn)題:我開(kāi)了?5 個(gè)線程,假如圖片一共有 2000 張還行,有 2003?張?jiān)趺崔k?
emm,前 4 個(gè)線程下載 401 張圖,第 5 個(gè)下載 399 張圖就可以啦!
但是,如果前 400 張圖比較小,第 401-800 張圖比較大,這樣的話第一個(gè)線程結(jié)束時(shí)間遠(yuǎn)早于第二個(gè),如何解決?
其實(shí)可以挨個(gè)下載:不預(yù)先分配每個(gè)線程下載哪些圖片,直接
……
吐槽:居然花了我 8?個(gè)小時(shí)來(lái)搞這段代碼!沒(méi)想到這部分是這么難弄,
搞的來(lái)和當(dāng)年學(xué) OI 時(shí)有一樣的心情了。
(心情簡(jiǎn)單.jpg)
不得不說(shuō),學(xué) OI 的時(shí)候調(diào)試代碼的過(guò)程,和現(xiàn)在很類似。只是這里涉及更多的是實(shí)際操作而非算法。
實(shí)際操作就要考慮異常,異常處理,維護(hù),等等。所以花時(shí)間也是避免不了的啦……
然后就可以繼續(xù)等待它慢慢扒圖……
……
……
然后就發(fā)現(xiàn)了一堆莫名其妙的錯(cuò)誤!嗚嗚嗚……
(一共下載了 607 張圖片,最后 5 個(gè)線程全部斷掉了……為什么我設(shè)置了超時(shí)重試都還會(huì) Timeout Error……QAQ)
(最好笑的還是 retry 4 in 3 times……這個(gè)是如何做到的)
再來(lái)。
后來(lái)發(fā)現(xiàn),拋出異常時(shí),僅僅針對(duì)?Readtime Error 的異常來(lái)解決問(wèn)題。還有 Connection Error 等沒(méi)有處理。這個(gè)是最主要的問(wèn)題。
某些地方寫入文件會(huì)有異常?嗯,這個(gè)暫時(shí)不了解原因。好像是圖片的鏈接讀取錯(cuò)誤?
于是加入了兩種連網(wǎng)的錯(cuò)誤處理。
為了防止連網(wǎng)出錯(cuò),特意把重試次數(shù)設(shè)為了 4 次,超時(shí) Timeout 設(shè)為了 4 秒。
……
然而還是出錯(cuò)了!
這次總算是查到了失敗的原因。
XKCD漫畫還真的有些不同尋常,例如:第 404 張漫畫竟然……就是一個(gè) 404 not found 的網(wǎng)站???
第 1350,2067 張漫畫居然有用戶交互的方式?
怪說(shuō)不得線程又終止在這里了……
先把它們記下來(lái),晚些時(shí)候再去解決吧。
發(fā)一個(gè)
不規(guī)范的
源碼存檔:
#! 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 點(diǎn)準(zhǔn)時(shí)睡覺(jué) = =||
不說(shuō)了,累死啦~趕快滾去睡
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

