網頁相信是許多人取得訊息的主要來源,為了追蹤、分析或取得想要的資訊,Python網頁爬蟲技術被廣泛的使用,而如果所要爬取的資料量非常大時,效能就會變得非常的重要,而非同步(Asynchronous)處理則是用來提升效率的常用方法。
因此,本文想要以104人力銀行網站,爬取Python職缺為例,來和讀者分享如何使用GRequests套件,來開發Python非同步網頁爬蟲,並且比較同步與非同步網頁爬蟲所花費的執行時間,其中的重點包含:
- 同步vs非同步
- GRequests套件
- Python同步網頁爬蟲
- Python非同步網頁爬蟲
一、同步vs非同步
簡單來說,同步(Synchronous)就是接到一個任務後,需要等到它完成,才能繼續執行下一個任務。就像到了中午買午餐時,想要買一個炒飯和7-11的飲料,同步(Synchronous)處理就是需要等到炒飯做完了,才能去7-11買飲料,導致花費較多的執行時間。
而非同步(Asynchronous)則是能夠平行處理,無需等待第一個任務完成,即可執行其它的任務,只要第一個任務完成了,再回來處理。以上面的例子來說,非同步(Asynchronous)處理就是在叫完炒飯的同時,就可以去7-11買飲料,達到同時處理多個任務,提升執行效率。
二、GRequests套件
一般的情況下,使用Python開發網頁爬蟲時,所使用的requests套件,就是同步(Synchronous)處理,在發送一個請求(request)後,在還沒有收到回應前,不會執行其它的任務。
而grequests套件則是封裝了requests及gevent(協程)的套件,只要建置網址請求清單,就能夠平行發送多個請求(request),達到非同步(Asynchronous)處理,提升Python網頁爬蟲的執行效率。
三、Python同步網頁爬蟲
本文以Visual Studio Code為例,開啟Python網頁爬蟲專案,在Terminal視窗中,利用以下的指令來安裝所需的套件:
$ pip install beautifulsoup4
$ pip install requests
$ pip install lxml #HTML解析器
接著,新增synch.py檔案,並且引用以下的模組(Module):
from bs4 import BeautifulSoup import requests import time
前往104人力銀行網站,在搜尋的地方輸入python進行查詢後,來分析一下所要爬取的職缺網頁結構,如下圖:
開啟synch.py檔案,就可以利用以下的範例,爬取104人力銀行網站1到10頁的Python職缺:
from bs4 import BeautifulSoup import requests import time start_time = time.time() #開始時間 for page in range(1, 11): #爬取1-10頁 response = requests.get( "https://www.104.com.tw/jobs/search/?keyword=python&order=1&page=" + str(page) + "&jobsource=2018indexpoc&ro=0") soup = BeautifulSoup(response.content, "lxml") #解析HTML原始碼 blocks = soup.find_all("div", {"class": "b-block__left"}) #職缺區塊 for block in blocks: job = block.find("a", {"class": "js-job-link"}) #職缺名稱 if job is None: continue company = block.find_all("li")[1] #公司名稱 salary = block.find("span", {"class": "b-tag--default"}) #待遇 print((job.getText(),) + (company.getText().strip(),) + (salary.getText(),)) print("花費:" + str(time.time() - start_time) + "秒")
執行結果
範例中第6行,利用time模組(Module)取得開始執行的時間,接著,透過迴圈爬取1到10頁的Python職缺後,第26行計算總共花費多少時間。而從執行結果可以看到Python同步網頁爬蟲約花費5秒的時間。四、Python非同步網頁爬蟲
接下來,另外新增asynch.py檔案,開發Python非同步網頁爬蟲。首先,利用以下的指令來安裝GRequests套件:
$ pip install grequests
開啟asynch.py檔案,引用以下的模組(Module):
from bs4 import BeautifulSoup import grequests import time
要使用GRequests模組(Module)來平行發送請求(request),需先建立請求網址清單,如下範例:
from bs4 import BeautifulSoup import grequests import time start_time = time.time() #開始時間 links = list() #請求網址清單(1-10頁的網址) for page in range(1, 11): links.append("https://www.104.com.tw/jobs/search/?keyword=python&order=1&page=" + str(page) + "&jobsource=2018indexpoc&ro=0")
以上範例的第8~11行所做的事情等同於以下的範例:
links = [ https://www.104.com.tw/jobs/search/?keyword=python&order=1&page=1&jobsource=2018indexpoc&ro=0 https://www.104.com.tw/jobs/search/?keyword=python&order=1&page=2&jobsource=2018indexpoc&ro=0 https://www.104.com.tw/jobs/search/?keyword=python&order=1&page=3&jobsource=2018indexpoc&ro=0 https://www.104.com.tw/jobs/search/?keyword=python&order=1&page=4&jobsource=2018indexpoc&ro=0 https://www.104.com.tw/jobs/search/?keyword=python&order=1&page=5&jobsource=2018indexpoc&ro=0 https://www.104.com.tw/jobs/search/?keyword=python&order=1&page=6&jobsource=2018indexpoc&ro=0 https://www.104.com.tw/jobs/search/?keyword=python&order=1&page=7&jobsource=2018indexpoc&ro=0 https://www.104.com.tw/jobs/search/?keyword=python&order=1&page=8&jobsource=2018indexpoc&ro=0 https://www.104.com.tw/jobs/search/?keyword=python&order=1&page=9&jobsource=2018indexpoc&ro=0 https://www.104.com.tw/jobs/search/?keyword=python&order=1&page=10&jobsource=2018indexpoc&ro=0 ]
有了請求網址清單後,就可以利用GRequests模組(Module)建立請求清單集合,並且透過imap()方法(Method)平行發送請求,如下範例:
from bs4 import BeautifulSoup import grequests import time start_time = time.time() #開始時間 links = list() # 請求網址清單(1-10頁的網址) for page in range(1, 11): links.append("https://www.104.com.tw/jobs/search/?keyword=python&order=1&page=" + str(page) + "&jobsource=2018indexpoc&ro=0") reqs = (grequests.get(link) for link in links) # 建立請求集合 response = grequests.imap(reqs, grequests.Pool(10)) # 平行發送請求
其中,Pool為處理池,由於有十個網頁,所以設定為10。有了十個網頁的回應結果後,就可以透過迴圈來進行職缺爬取的動作,如下範例:
from bs4 import BeautifulSoup import grequests import time start_time = time.time() #開始時間 links = list() # 請求網址清單(1-10頁的網址) for page in range(1, 11): links.append("https://www.104.com.tw/jobs/search/?keyword=python&order=1&page=" + str(page) + "&jobsource=2018indexpoc&ro=0") reqs = (grequests.get(link) for link in links) # 建立請求集合 response = grequests.imap(reqs, grequests.Pool(4)) # 發送請求 for r in response: soup = BeautifulSoup(r.content, "lxml") # 解析HTML原始碼 blocks = soup.find_all("div", {"class": "b-block__left"}) # 職缺區塊 for block in blocks: job = block.find("a", {"class": "js-job-link"}) # 職缺名稱 if job is None: continue company = block.find_all("li")[1] # 公司名稱 salary = block.find("span", {"class": "b-tag--default"}) # 待遇 print((job.getText(),) + (company.getText().strip(),) + (salary.getText(),)) print("花費:" + str(time.time() - start_time) + "秒")
執行結果
從執行結果可以看到,Python非同步網頁爬蟲所花費的時間較同步網頁爬蟲快了近3秒的時間,當所要爬取的資料量越大時,相差的執行時間就會更加明顯。
五、小結
本文利用GRequests套件,非同步的發送請求(request)及接收回應結果,來達到Python非同步網頁爬蟲的效果,進而提升執行的效率。當然,除了GRequests套件外,還有其他像asyncio等套件,提供更多的附加功能,來實作更複雜的非同步網頁爬蟲。
您也有使用Python網頁爬蟲來取得大量的網頁資料嗎?可以參考本文所分享的非同步網頁爬蟲實作技巧,來提升爬取的效率唷。
留言
張貼留言