Py3.x異步網絡爬蟲淺涉
異步的概念
舉個例子,A正在玩遊戲,B去叫A一起吃飯,這個時候B有兩種選擇,一是等A玩完遊戲一起去吃飯,二是去幹其他事情,并告訴A玩完通知他。是的,前一種選擇就是單線程,後一種是多線程,但是,如果使用多線程做這件事就會出現B為了得到A的通知不得不隔一段時間停下手中的活看看A是否通知,而使用while循環似乎又不太恰當,降低程式性能,這個時候就可用異步處理。
異步示例
異步是py3.4+的版本引入的,在py3.5+中正式定義其操作符async與await等,以下我将使用py3.4+的文法寫一些異步程式,這些程式在之後的版本中也是可以運作的。
import asyncio
# 擷取事件循環
loop = asyncio.get_event_loop()
@asyncio.coroutine
def a_persion():
for i in range():
yield from asyncio.sleep()
print('我是A,我在玩遊戲')
return "A遊戲玩完了!"
def a_statu(say):
print(say.result())
@asyncio.coroutine
def b_person(x):
for i in range():
yield from asyncio.sleep()
print('我是B,我在等A,順便看書', x)
a_task = asyncio.ensure_future(a_persion()) # 建立任務
a_task.add_done_callback(a_statu) # 設定回調函數
b_task = asyncio.ensure_future(b_person('無聊'))
tasks = [a_task, b_task] # 任務清單
loop.run_until_complete(asyncio.wait(tasks))
print('A和B高高興興的吃飯去了!')
執行結果:
我是A,我在玩遊戲
我是B,我在等A,順便看書 無聊
我是A,我在玩遊戲
我是B,我在等A,順便看書 無聊
我是A,我在玩遊戲
我是B,我在等A,順便看書 無聊
我是A,我在玩遊戲
我是B,我在等A,順便看書 無聊
我是A,我在玩遊戲
我是B,我在等A,順便看書 無聊
A遊戲玩完了!
我是B,我在等A,順便看書 無聊
我是B,我在等A,順便看書 無聊
A和B高高興興的吃飯去了!
很清楚的,我們看到,A與B的事情都同時在做,并且最後他們都可以同時出去吃飯,這就是異步,如果讓你用多線程方式去做是否比較麻煩?
用異步用來寫一個爬蟲架構
網絡爬蟲的決速步驟在下載下傳網頁源碼步驟上,此步耗時最大,是以,我将用異步的方式對其優化,實作并行地下載下傳頁面。我們會用到
aiohttp
這個目前還是第三方的庫,可通過
pip
安裝之。同時,我将使用Scrapy的風格寫出這個架構,也是向Scrapy團隊緻敬。我們暫且叫它py3spider吧!
引入相應的包
import asyncio
import aiohttp
import warnings
定義請求類
class Request:
def __init__(self, url, callback, meta=None):
self.url = url
self.callback = callback
self.meta = meta
用于封裝Request請求,實作對url及其解析函數的暫存
定義響應類
class Response:
def __init__(self, body, url, meta=None, callback=None):
self.body = body
self.url = url
self.meta = meta
self.callback = callback
用于對下載下傳完畢的資料進行暫存
定義中間件
class Middleware:
def __init__(self):
self.web = aiohttp.ClientSession()
def __del__(self):
self.web.close()
@asyncio.coroutine
def get(self, req):
response = yield from self.web.get(req.url)
body = yield from response.read()
response.close()
return Response(body, req.url, req.meta, req.callback)
def save(self, data):
print(data)
用于下載下傳頁面與儲存資料等
定義爬蟲接口
class Spider:
start_urls = []
def parse(self, response):
pass
所有爬蟲都需實作此接口,
start_urls
是入口清單,
parse
函數是第一個調用的解析函數
定義排程器
class Scheduler:
def __init__(self, spider, middleware=Middleware()):
self.__spider = spider
self.__midw = middleware
self.__request = [] # 請求隊列
self.__response = [] # 回應隊列
self.thread_num = # 線程數
def __start(self):
for url in self.__spider.start_urls:
self.__request.append(Request(url, self.__spider.parse))
def __putRequest(self):
loop = asyncio.get_event_loop()
def putRes(fur):
self.__response.append(fur.result())
tasks = []
while self.__request:
try:
num =
if len(self.__request) < self.thread_num:
num = len(self.__request)
else:
num = self.thread_num
for i in range(num):
task = asyncio.ensure_future(
self.__midw.get(self.__request.pop()))
task.add_done_callback(putRes)
tasks.append(task)
if tasks:
loop.run_until_complete(asyncio.wait(tasks))
self.__doResponse()
tasks.clear()
except Exception as e:
warnings.warn(e)
loop.close()
def __doResponse(self):
while self.__response:
res = self.__response.pop()
try:
for req in res.callback(res):
if type(req) == Request:
self.__request.append(req)
else:
self.__midw.save(req)
except Exception as e:
warnings.warn(e)
def start(self):
self.__start()
self.__putRequest()
print("The end ...")
執行爬蟲的排程任務,爬蟲、中間件、線程都可以自己設定。将上面的代碼封裝成
py3spider
,使用方法與Scrapy類似,隻需要修改極少代碼甚至不需要修改Scrapy的爬蟲源碼即可使用。
調用示例
以古詩文網的名句項目為例說明,想了解古詩文網的詳情可以用浏覽器打開
http://so.gushiwen.org/mingju/
這個網址檢視。
from py3spider import *
from bs4 import BeautifulSoup
import re
class MingSpider(Spider):
start_urls = ['http://so.gushiwen.org/mingju/']
def parse(self, response):
soup = BeautifulSoup(response.body.decode('utf-8', 'ignore'), 'lxml')
div = soup.find('div', attrs = {'class': 'sright'})
for a in div.find_all('a', attrs={'href': re.compile('^/mingju/Default.aspx.*')}):
yield Request('http://so.gushiwen.org/'+a.attrs['href'], callback=self.parseDetail, meta={'title': a.text})
def parseDetail(self, response):
soup = BeautifulSoup(response.body.decode('utf-8', 'ignore'), 'lxml')
# <a style=" float:left;" target="_blank" href="/mingju/ju_162.aspx" target="_blank" rel="external nofollow" >山有木兮木有枝,心悅君兮君不知。</a>
for a in soup.find_all('a', attrs={'href': re.compile('^/mingju/ju_.*')}):
if a and a.text:
yield {'title': response.meta['title'], 'text': a.text}
nt = soup.find('a', text='下一頁')
if nt and nt.attrs and 'href' in nt.attrs:
yield Request('http://so.gushiwen.org/mingju/'+nt.attrs['href'], callback=self.parseDetail, meta=response.meta)
class MyMiddleware(Middleware):
def __init__(self):
super(MyMiddleware, self).__init__()
def save(self, data):
with open(data['title']+'.txt', 'a') as f:
f.write(data['text']+'\n')
scheduler = Scheduler(MingSpider(), middleware = MyMiddleware())
scheduler.start()
執行上面的代碼即可很快的爬行名句項目。關于py3spider這個項目我已上傳
pypi
,可以通過
pip
安裝之。
相比于Scrapy,這個安裝更容易,第三方庫僅
aiohttp
一個。