Python爬蟲-速度(2)
Python爬蟲-速度(2)
文章目錄
018.9.17
前言
我原有個習慣,就是每寫個什麼東西,都會在開頭記下日期。今天得空複查這篇內容的時候,發現居然赫然顯示著:018.9.17。
十天就這麼過去了。
很難說我這10天裡到底做了什麼,收穫了什麼。因為我確實未發現自己在這時間裡有如何的長進。倒是再一次加深了對時間飛速的體會。
繼而說說這次的內容吧。由於程序執行緒協程涉及到的知識點很多,如果細講起來,夠寫好幾篇了。所以只好提供自己學習時用到的資料,包括書籍《Python Cook》,以及一些部落格文章。為了方便你我他,是全部附上了連結的。
併發與並行
我們先來說說什麼是併發,什麼又是並行。
併發:指程式有處理多個任務的能力
並行:指程式有同時處理多個任務的能力
二者之間的差別,就在同時二字。可以舉個例子:併發就是一群人從一個獨木橋上過,而並行則是一群人從多個獨木橋上過。
其實網上我看到許多人愛用“一個人吃十個饅頭與十個人吃十個饅頭”的例子,倒是很好的解釋了併發與並行的區別,然而乍一看,似乎併發沒啥用呀!所以慶幸自己想出一個過獨木橋的例子【不要臉.jpg】,這樣一來不但給了二者區別,還可以體現出併發的提速作用:一群人過獨木橋(所有人人可以都在橋上)肯定是比這群人挨個挨個的過獨木橋(橋上最多允許一個人存在)要快許多。
非同步與同步/阻塞和非阻塞
可以用一個在知乎看到的段子來理解:
老張愛喝茶,廢話不說,煮開水。
出場人物:老張。
道具:水壺兩把(普通水壺,簡稱水壺;會響的水壺,簡稱響水壺)。(1) 老張把水壺放到火上,立等水開。(同步阻塞)
【老張覺得自己有點傻】
(2) 老張把水壺放到火上,去客廳看電視,時不時去廚房看看水開沒有。(同步非阻塞)
【老張還是覺得自己有點傻,於是變高端了,買了把會響笛的那種水壺。水開之後,能大聲發出嘀~~~~的噪音。】(3) 老張把響水壺放到火上,立等水開。(非同步阻塞)
【老張覺得這樣傻等意義不大。】
(4) 老張把響水壺放到火上,去客廳看電視,水壺響之前不再去看它了,響了再去拿壺。(非同步非阻塞)
【老張覺得自己聰明瞭。】所謂同步非同步,只是對於水壺而言。
普通水壺,同步;響水壺,非同步。雖然都能幹活,但響水壺可以在自己完工之後,提示老張水開了。這是普通水壺所不能及的。同步只能讓呼叫者去輪詢自己(情況2中),造成老張效率的低下。所謂阻塞非阻塞,僅僅對於老張而言。
立等的老張,阻塞;看電視的老張,非阻塞。情況(1)和情況(3)中老張就是阻塞的,媳婦喊他都不知道。雖然(3)中響水壺是非同步的,可對於立等的老張沒有太大的意義。所以一般非同步是配合非阻塞使用的,這樣才能發揮非同步的效用。——來源網路,作者不明。
作者:愚抄
連結:https://www.zhihu.com/question/19732473/answer/23434554
來源:知乎
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
多程序
由於主要是為爬蟲程式提速,所以過多的原理暫不講的。但需要記得的是:多程序是並行的。
我們可以通過例項來觀察,首先來一個單程序單執行緒的爬蟲程式:
import requests
import time
def get_one_html(url):
response = requests.get(url=url, headers=HEADERS)
return response.url
if __name__ == "__main__":
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/65.0.3325.183 Safari/537.36 "
"Vivaldi/1.96.1147.64"}
urls = [
"http://www.baidu.com", "http://www.sina.com.cn",
"http://www.163.com", "http://www.sohu.com",
"http://www.csdn.net", "http://www.jobbole.com",
"http://www.qq.com", "http://weixin.qq.com",
"http://www.jb51.net", "https://m.qidian.com"
]
start = time.time()
results = map(get_one_html, urls)
for result in results:
print(result)
end = time.time() - start
print(end)
多次試驗,耗時在2.8~3.6s範圍。如果我們改寫為多程序呢?
可參見《Python Cook》中簡單的並行程式設計,連結:https://python3-cookbook.readthedocs.io/zh_CN/latest/c12/p08_perform_simple_parallel_programming.html
import requests
import time
from concurrent.futures import ProcessPoolExecutor
def get_one_html(url):
response = requests.get(url=url, headers=HEADERS)
return response.url
if __name__ == "__main__":
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/65.0.3325.183 Safari/537.36 "
"Vivaldi/1.96.1147.64"}
urls = [
"http://www.baidu.com", "http://www.sina.com.cn",
"http://www.163.com", "http://www.sohu.com",
"http://www.csdn.net", "http://www.jobbole.com",
"http://www.qq.com", "http://weixin.qq.com",
"http://www.jb51.net", "https://m.qidian.com"
]
start = time.time()
# 利用程序池
with ProcessPoolExecutor() as pool:
# 往程序池新增任務
pool.map(get_one_html, urls)
end = time.time() - start
print(end)
多次試驗,耗時在1.2~1.7s範圍。
可以看到多程序的確可以實現程式提速,但需要的注意的是,只有在需要多次使用get_one_html()
函式時,多程序的優勢才能體現;如果僅僅用兩三次,反而是單程序單執行緒執行速度快。這是因為系統在為實現多程序前需要一些準備工作,這個準備將耗費大量時間。
多執行緒
多執行緒是併發的。儘管Python中有GIL鎖,但針對I/O操作時,仍有提速效果。
可參見《Python Cook》中建立一個執行緒池,連結:https://python3-cookbook.readthedocs.io/zh_CN/latest/c12/p07_creating_thread_pool.html
import requests
import time
from concurrent.futures import ThreadPoolExecutor
def get_one_html(url):
response = requests.get(url=url, headers=HEADERS)
return response.url
if __name__ == "__main__":
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/65.0.3325.183 Safari/537.36 "
"Vivaldi/1.96.1147.64"}
urls = [
"http://www.baidu.com", "http://www.sina.com.cn",
"http://www.163.com", "http://www.sohu.com",
"http://www.csdn.net", "http://www.jobbole.com",
"http://www.qq.com", "http://weixin.qq.com",
"http://www.jb51.net", "https://m.qidian.com"
]
start = time.time()
# 執行緒池
with ThreadPoolExecutor() as pool:
pool.map(get_one_html, urls)
end = time.time() - start
print(end)
多次試驗,耗時在0.86~1.0s範圍。
可以看到提速比多程序會好一些。
協程+非同步
關於這部分的知識,講起來都可以單獨撐開一個篇幅了,所以這裡我就“拿來主義”:
崔大神的一篇關於非同步提速,連結:https://mp.weixin.qq.com/s/jCc1jIHxU_p6bUlwijkwyQ
以及CSDN上某友的文章,連結:https://blog.csdn.net/sinat_26917383/article/details/79246632
import aiohttp
import time
import asyncio
async def get(url):
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=HEADERS) as response:
assert response.status == 200
return response.url
async def get_one_html(url):
result = await get(url) # 由於asyncio不支援http請求,所以需要用aiohttp來封裝一個get
return result
if __name__ == "__main__":
HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/65.0.3325.183 Safari/537.36 "
"Vivaldi/1.96.1147.64"}
urls = [
"http://www.baidu.com", "http://www.sina.com.cn",
"http://www.163.com", "http://www.sohu.com",
"http://www.csdn.net", "http://www.jobbole.com",
"http://www.qq.com", "http://weixin.qq.com",
"http://www.jb51.net", "https://m.qidian.com"
]
# 構建tasks列表
tasks = [asyncio.ensure_future(get_one_html(url)) for url in urls]
start = time.time()
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
end = time.time() - start
print(end)
for task in tasks:
print(task.result())
多次試驗,耗時在1.4~1.8s範圍。儘管速度上不及多程序與多執行緒,但我們知道它節省了配置,如果請求的任務量繼續加大,做相應調整是可以超越前面兩種方法的。