import sqlite3html
Python 的一個很是大的優勢是很容易寫很容易跑起來,缺點就是不少不那麼著名的(甚至一些著名的)程序和庫都不像 C 和 C++ 那邊那樣專業、可靠(固然這也有動態類型 vs 靜態類型的緣由)。前端
python寫爬蟲,沒有使用Scrapy嗎?item屬性定義明確,pipelines來作入庫處理,怎麼會出現sql錯誤 ?python
作爬蟲難點是解析頁面和反爬,你居然還沒到這一步就掛了,還能說啥?試試scrapy吧,若是規模大須要調度就上redis。react
你用了sqllite作存儲,應該是個規模很小的項目?那不如直接存文件。上了規模建議mongodb,pymongo很靠譜!linux
sql注入爬蟲,好像很好玩啊git
抓取大多數狀況屬於get請求,即直接從對方服務器上獲取數據。github
首先,Python中自帶urllib及urllib2這兩個模塊,基本上能知足通常的頁面抓取。另外,requests也是很是有用的包,與此相似的,還有httplib2等等。web
Requests:
import requests
response = requests.get(url)
content = requests.get(url).content
print "response headers:", response.headers
print "content:", content
Urllib2:
import urllib2
response = urllib2.urlopen(url)
content = urllib2.urlopen(url).read()
print "response headers:", response.headers
print "content:", content
Httplib2:
import httplib2
http = httplib2.Http()
response_headers, content = http.request(url, 'GET')
print "response headers:", response_headers
print "content:", content
此外,對於帶有查詢字段的url,get請求通常會未來請求的數據附在url以後,以?分割url和傳輸數據,多個參數用&鏈接。ajax
data = {'data1':'XXXXX', 'data2':'XXXXX'}
Requests:data爲dict,json
import requests
response = requests.get(url=url, params=data)
Urllib2:data爲string
import urllib, urllib2
data = urllib.urlencode(data)
full_url = url+'?'+data
response = urllib2.urlopen(full_url)
import time
time.sleep(1)
很少說。redis
def multi_session(session, *arg):
while True:
retryTimes = 20
while retryTimes>0:
try:
return session.post(*arg)
except:
print '.',
retryTimes -= 1
或者
def multi_open(opener, *arg):
while True:
retryTimes = 20
while retryTimes>0:
try:
return opener.open(*arg)
except:
print '.',
retryTimes -= 1
這樣咱們就可使用multi_session或multi_open對爬蟲抓取的session或opener進行保持。
requests 處理網絡請求
logging 日誌記錄
threading 多線程
Queue 用於線程池的實現
argparse shell參數解析
sqlite3 sqlite數據庫
BeautifulSoup html頁面解析
urlparse 對連接的處理
我沒有選擇使用python的標準庫urllib2,urllib2不易於代碼維護,修改起來麻煩,並且不易擴展, 整體來講,requests就是簡單易用,如requests的介紹所說: built for human beings.
包括但不限於如下幾個緣由:
Requests will automatically decode content from the server. Most unicode charsets are seamlessly decoded.
web的編碼實在是不易處理,尤爲是顯示中文的狀況。參考[1]
由於之前不瞭解線程池,線程池的實如今一開始參照了Python Cookbook中關於線程池的例子,參考[2]。
借鑑該例子,一開始我是使用了生產者/消費者的模式,使用任務隊列和結果隊列,把html源碼下載的任務交給任務隊列,而後線程池中的線程負責下載,下載完html源碼後,放進結果隊列,主線程不斷從結果隊列拿出結果,進行下一步處理。
這樣確實能夠成功的跑起來,也實現了線程池和任務委派,但卻隱藏着一個問題:
作測試時,我指定了新浪爬深度爲4的網頁, 在爬到第3層時,內存忽然爆增,致使程序崩潰。
通過調試發現,正是以上的方法致使的:
多線程併發去下載網頁,不管主線程作的是多麼不耗時的動做,始終是沒法跟上下載的速度的,更況且主線程要負責耗時的文件IO操做,所以,結果隊列中的結果沒能被及時取出,越存越多卻處理不來,致使內存激增。
曾想過用另外的線程池來負責處理結果,可這樣該線程池的線程數很差分配,分多了分少了都會有問題,並且程序的實際線程數就多於用戶指定的那個線程數了。
所以,乾脆讓原線程在下載完網頁後,不用把結果放進結果隊列,而是繼續下一步的操做,直到把網頁存起來,才結束該線程的任務。
最後就沒用到結果隊列,一個線程的任務變成:
根據url下載網頁—->保存該網頁—->抽取該網頁的連接(爲訪問下個深度作準備)—->結束
爬蟲的BFS算法不難寫,利用隊列出棧入棧便可,有一個小難點就是對深度的控制,我一開始是這樣作的:
用一個flag來標註每一深度的最後一個連接。當訪問到最後一個連接時,深度+1。從而控制爬蟲深度。
代碼以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def getHrefsFromURL(root, depth): unvisitedHrefs.append(root) currentDepth = 1 lastURL = root flag = False while currentDepth < depth+1: url = unvisitedHrefs.popleft() if lastURL == url: flag = True #解析html源碼,獲取其中的連接。並把連接append到unvisitedHrefs去 getHrefs(url) if flag: flag = False currentDepth += 1 location = unvisitedHrefs[-1] |
可是,這個方法會帶來一些問題:
所以,在多線程中,我使用了更直接了當的方法:
先把整個深度的連接分配給線程池線程中的線程去處理(處理的內容參考上文), 等待該深度的全部連接處理完,當全部連接處理完時,則表示爬完爬了一個深度的網頁。
此時,下一個深度要訪問的連接,已經都準備好了。
1 2 3 4 5 6 7 8 9 10 |
while self.currentDepth < self.depth+1: #分配任務,線程池併發下載當前深度的全部頁面(該操做不阻塞) self._assignCurrentDepthTasks() #等待當前線程池完成全部任務 #使用self.threadPool.taskQueueJoin()可代替如下操做,可沒法Ctrl-C Interupt while self.threadPool.getTaskLeft(): time.sleep(10) #當池內的全部任務完成時,即表明爬完了一個網頁深度 #邁進下一個深度 self.currentDepth += 1 |
很顯然,一開始我這爬蟲代碼耦合性很是高,線程池,線程,爬蟲的操做,三者均粘合在一塊沒法分開了。 因而我幾乎把時間都用在了重構上面。先是把線程池在爬蟲中抽出來,再把線程從線程中抽離出來。使得如今三者均可以是相對獨立了。
一開始代碼裏有很多長函數,一個函數裏面作着幾個操做,因而我決定把操做從函數中抽離,一個函數就必須如它的命名那般清楚,只作那個操做。
因而函數雖然變多了,但每一個函數都很簡短,使得代碼可讀性加強,修改起來容易,同時也增長了代碼的可複用性。
關於這一點,重構 參考[3] 這本書幫了我很大的忙。
如何匹配keyword?
一開始使用的方法很簡單,把源碼和關鍵詞都轉爲小(大)寫,在使用find函數:
pageSource.lower().find(keyword.lower())
要把全部字符轉爲小寫,再查找,我始終以爲這樣效率不高。
因而發帖尋求幫助, 有人建議說:
使用if keyword.lower() in pageSource.lower()
確實看過文章說in比find高效,可還沒解決個人問題.
因而有人建議使用正則的re.I來查找。
我以爲這是個好方法,直覺告訴我正則查找會比較高效率。
可又有人跳出來講正則比較慢,並拿出了數據。。。
有時間我以爲要作個測試,驗證一下。
被禁止訪問的問題:
訪問未中止時,忽然某個host禁止了爬蟲訪問,這個時候unvisited列表中仍然有大量該host的地址,就會致使大量的超時。 由於每次超時,我都設置了重試,timeout=10s, * 3 = 30s 也就是一個連接要等待30s。
若不重試的話,由於開線程多,網速慢,會致使正常的網頁也timeout~
這個問題就難以權衡了。
經測試,
爬sina.com.cn 二級深度, 共訪問約1350個頁面,
開10線程與20線程都須要花費約20分鐘的時間,時間相差很少.
隨便打開了幾個頁面,均爲100k上下的大小, 假設平均頁面大小爲100k,
則總共爲135000k的數據。
ping sina.com.cn 爲聯通ip,機房測速爲聯通133k/s,
則:135000/133/60 約等於17分鐘
加上處理數據,文件IO,網頁10s超時並重試2次的時間,理論時間也比較接近20分鐘了。
所以最大的制約條件應該就是網速了。
看着代碼進行了回憶和反思,算是總結了。作以前以爲爬蟲很容易,沒想到也會遇到很多問題,也學到了不少東西,這樣的招人題目比作筆試實在多了。
此次用的是多線程,之後能夠再試試異步IO,相信也會是不錯的挑戰。
附: 爬蟲源碼
ref:
[1]網頁內容的編碼檢測
[2]simplest useful (I hope!) thread pool example
Python Cookbook
[3]重構:改善既有代碼的設計
[6]各類Documentation 以及 隨手搜的網頁,不一一列舉。
碰到驗證碼咋辦?這裏分兩種狀況處理:
如今的網頁廣泛支持gzip壓縮,這每每能夠解決大量傳輸時間,以VeryCD的主頁爲例,未壓縮版本247K,壓縮了之後45K,爲原來的1/5。這就意味着抓取速度會快5倍。
然而python的urllib/urllib2默認都不支持壓縮,要返回壓縮格式,必須在request的header裏面寫明’accept- encoding’,而後讀取response後更要檢查header查看是否有’content-encoding’一項來判斷是否須要解碼,很繁瑣瑣 碎。如何讓urllib2自動支持gzip, defalte呢?
其實能夠繼承BaseHanlder類,而後build_opener的方式來處理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
import
urllib2
from
gzip
import
GzipFile
from
StringIO
import
StringIO
class
ContentEncodingProcessor(urllib2.BaseHandler):
"""A handler to add gzip capabilities to urllib2 requests """
# add headers to requests
def
http_request(
self
, req):
req.add_header(
"Accept-Encoding"
,
"gzip, deflate"
)
return
req
# decode
def
http_response(
self
, req, resp):
old_resp
=
resp
# gzip
if
resp.headers.get(
"content-encoding"
)
=
=
"gzip"
:
gz
=
GzipFile(
fileobj
=
StringIO(resp.read()),
mode
=
"r"
)
resp
=
urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)
resp.msg
=
old_resp.msg
# deflate
if
resp.headers.get(
"content-encoding"
)
=
=
"deflate"
:
gz
=
StringIO( deflate(resp.read()) )
resp
=
urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)
# 'class to add info() and
resp.msg
=
old_resp.msg
return
resp
# deflate support
import
zlib
def
deflate(data):
# zlib only provides the zlib compress format, not the deflate format;
try
:
# so on top of all there's this workaround:
return
zlib.decompress(data,
-
zlib.MAX_WBITS)
except
zlib.error:
return
zlib.decompress(data)
|
而後就簡單了,
encoding_support = ContentEncodingProcessor
#直接用opener打開網頁,若是服務器支持gzip/defalte則自動解壓縮 content = opener.open(url).read() opener = urllib2.build_opener( encoding_support, urllib2.HTTPHandler )
總結一文的確說起了一個簡單的多線程模板,可是那個東東真正應用到程序裏面去只會讓程序變得支離破碎,不堪入目。在怎麼更方便地進行多線程方面我也動了一番腦筋。先想一想怎麼進行多線程調用最方便呢?
事實上更高效的抓取並不是必定要用多線程,也可使用異步I/O法:直接用twisted的getPage方法,而後分別加上異步I/O結束時的callback和errback方法便可。例如能夠這麼幹:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
from
twisted.web.client
import
getPage
from
twisted.internet
import
reactor
links
=
[
'http://www.verycd.com/topics/%d/'
%
i
for
i
in
range
(
5420
,
5430
) ]
def
parse_page(data,url):
print
len
(data),url
def
fetch_error(error,url):
print
error.getErrorMessage(),url
# 批量抓取連接
for
url
in
links:
getPage(url,timeout
=
5
) \
.addCallback(parse_page,url) \
#成功則調用parse_page方法
.addErrback(fetch_error,url)
#失敗則調用fetch_error方法
reactor.callLater(
5
, reactor.stop)
#5秒鐘後通知reactor結束程序
reactor.run()
|
twisted人如其名,寫的代碼實在是太扭曲了,非正常人所能接受,雖然這個簡單的例子看上去還好;每次寫twisted的程序整我的都扭曲了,累得不得了,文檔等於沒有,必須得看源碼才知道怎麼整,唉不提了。
若是要支持gzip/deflate,甚至作一些登錄的擴展,就得爲twisted寫個新的HTTPClientFactory類諸如此類,我這眉頭真是大皺,遂放棄。有毅力者請自行嘗試。
這篇講怎麼用twisted來進行批量網址處理的文章不錯,由淺入深,深刻淺出,能夠一看。
仍是以爲在urllib之類python「本土」的東東里面折騰起來更舒服。試想一下,若是有個Fetcher類,你能夠這麼調用
1
2
3
4
5
6
|
f
=
Fetcher(threads
=
10
)
#設定下載線程數爲10
for
url
in
urls:
f.push(url)
#把全部url推入下載隊列
while
f.taskleft():
#若還有未完成下載的線程
content
=
f.pop()
#從下載完成隊列中取出結果
do_with(content)
# 處理content內容
|
這 麼個多線程調用簡單明瞭,那麼就這麼設計吧,首先要有兩個隊列,用Queue搞定,多線程的基本架構也和「技巧總結」一文相似,push方法和pop方法 都比較好處理,都是直接用Queue的方法,taskleft則是若是有「正在運行的任務」或者」隊列中的任務」則爲是,也好辦,因而代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
import
urllib2
from
threading
import
Thread,Lock
from
Queue
import
Queue
import
time
class
Fetcher:
def
__init__(
self
,threads):
self
.opener
=
urllib2.build_opener(urllib2.HTTPHandler)
self
.lock
=
Lock()
#線程鎖
self
.q_req
=
Queue()
#任務隊列
self
.q_ans
=
Queue()
#完成隊列
self
.threads
=
threads
for
i
in
range
(threads):
t
=
Thread(target
=
self
.threadget)
t.setDaemon(
True
)
t.start()
self
.running
=
0
def
__del__(
self
):
#解構時需等待兩個隊列完成
time.sleep(
0.5
)
self
.q_req.join()
self
.q_ans.join()
def
taskleft(
self
):
return
self
.q_req.qsize()
+
self
.q_ans.qsize()
+
self
.running
def
push(
self
,req):
self
.q_req.put(req)
def
pop(
self
):
return
self
.q_ans.get()
def
threadget(
self
):
while
True
:
req
=
self
.q_req.get()
with
self
.lock:
#要保證該操做的原子性,進入critical area
self
.running
+
=
1
try
:
ans
=
self
.opener.
open
(req).read()
except
Exception, what:
ans
=
''
print
what
self
.q_ans.put((req,ans))
with
self
.lock:
self
.running
-
=
1
self
.q_req.task_done()
time.sleep(
0.1
)
# don't spam
if
__name__
=
=
"__main__"
:
links
=
[
'http://www.verycd.com/topics/%d/'
%
i
for
i
in
range
(
5420
,
5430
) ]
f
=
Fetcher(threads
=
10
)
for
url
in
links:
f.push(url)
while
f.taskleft():
url,content
=
f.pop()
print
url,
len
(content)
|
opener.open和urllib2.urlopen同樣,都會新建一個http請求。一般狀況下這不是什麼問題,由於線性環境下,一秒鐘可能 也就新生成一個請求;然而在多線程環境下,每秒鐘能夠是幾十上百個請求,這麼幹只要幾分鐘,正常的有理智的服務器必定會封禁你的。
然而在正常的html請求時,保持同時和服務器幾十個鏈接又是很正常的一件事,因此徹底能夠手動維護一個HttpConnection的池,而後每次抓取時從鏈接池裏面選鏈接進行鏈接便可。
這裏有一個取巧的方法,就是利用squid作代理服務器來進行抓取,則squid會自動爲你維護鏈接池,還附帶數據緩存功能,並且squid原本就是我每一個服務器上面必裝的東東,何須再自找麻煩寫鏈接池呢。
棧大小的設定將很是顯著地影響python的內存佔用,python多線程不設置這個值會致使程序佔用大量內存,這對openvz的vps來講很是致命。stack_size必須大於32768,實際上應該總要32768*2以上
from threading import stack_size stack_size(32768*16)
def get(self,req,retries=3): try: response = self.opener.open(req) data = response.read() except Exception , what: print what,req if retries>0: return self.get(req,retries-1) else: print 'GET Failed',req return '' return data
import socket socket.setdefaulttimeout(10) #設置10秒後鏈接超時
登錄更加簡化了,首先build_opener中要加入cookie支持,參考「總結」一文;如要登錄VeryCD,給Fetcher新增一個空方法login,並在init()中調用,而後繼承Fetcher類並override login方法:
1
2
3
4
5
6
7
8
9
|
def
login(
self
,username,password):
import
urllib
data
=
urllib.urlencode({
'username'
:username,
'password'
:password,
'continue'
:
'http://www.verycd.com/'
,
'login_submit'
:u
'登陸'
.encode(
'utf-8'
),
'save_cookie'
:
1
,})
url
=
'http://www.verycd.com/signin'
self
.opener.
open
(url,data).read()
|
因而在Fetcher初始化時便會自動登陸VeryCD網站。
如此,把上述全部小技巧都糅合起來就和我目前的私藏最終版的Fetcher類相差不遠了,它支持多線程,gzip/deflate壓縮,超時設置,自動重試,設置棧大小,自動登陸等功能;代碼簡單,使用方便,性能也不俗,可謂居家旅行,殺人放火,咳咳,之必備工具。
之因此說和最終版差得不遠,是由於最終版還有一個保留功能「馬甲術」:多代理自動選擇。看起來好像僅僅是一個random.choice的區別,其實包含了代理獲取,代理驗證,代理測速等諸多環節,這就是另外一個故事了。
碰到驗證碼咋辦?這裏分兩種狀況處理:
如今的網頁廣泛支持gzip壓縮,這每每能夠解決大量傳輸時間,以VeryCD的主頁爲例,未壓縮版本247K,壓縮了之後45K,爲原來的1/5。這就意味着抓取速度會快5倍。
然而python的urllib/urllib2默認都不支持壓縮,要返回壓縮格式,必須在request的header裏面寫明’accept- encoding’,而後讀取response後更要檢查header查看是否有’content-encoding’一項來判斷是否須要解碼,很繁瑣瑣 碎。如何讓urllib2自動支持gzip, defalte呢?
其實能夠繼承BaseHanlder類,而後build_opener的方式來處理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
import
urllib2
from
gzip
import
GzipFile
from
StringIO
import
StringIO
class
ContentEncodingProcessor(urllib2.BaseHandler):
"""A handler to add gzip capabilities to urllib2 requests """
# add headers to requests
def
http_request(
self
, req):
req.add_header(
"Accept-Encoding"
,
"gzip, deflate"
)
return
req
# decode
def
http_response(
self
, req, resp):
old_resp
=
resp
# gzip
if
resp.headers.get(
"content-encoding"
)
=
=
"gzip"
:
gz
=
GzipFile(
fileobj
=
StringIO(resp.read()),
mode
=
"r"
)
resp
=
urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)
resp.msg
=
old_resp.msg
# deflate
if
resp.headers.get(
"content-encoding"
)
=
=
"deflate"
:
gz
=
StringIO( deflate(resp.read()) )
resp
=
urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)
# 'class to add info() and
resp.msg
=
old_resp.msg
return
resp
# deflate support
import
zlib
def
deflate(data):
# zlib only provides the zlib compress format, not the deflate format;
try
:
# so on top of all there's this workaround:
return
zlib.decompress(data,
-
zlib.MAX_WBITS)
except
zlib.error:
return
zlib.decompress(data)
|
而後就簡單了,
encoding_support = ContentEncodingProcessor
#直接用opener打開網頁,若是服務器支持gzip/defalte則自動解壓縮 content = opener.open(url).read() opener = urllib2.build_opener( encoding_support, urllib2.HTTPHandler )
總結一文的確說起了一個簡單的多線程模板,可是那個東東真正應用到程序裏面去只會讓程序變得支離破碎,不堪入目。在怎麼更方便地進行多線程方面我也動了一番腦筋。先想一想怎麼進行多線程調用最方便呢?
事實上更高效的抓取並不是必定要用多線程,也可使用異步I/O法:直接用twisted的getPage方法,而後分別加上異步I/O結束時的callback和errback方法便可。例如能夠這麼幹:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
from
twisted.web.client
import
getPage
from
twisted.internet
import
reactor
links
=
[
'http://www.verycd.com/topics/%d/'
%
i
for
i
in
range
(
5420
,
5430
) ]
def
parse_page(data,url):
print
len
(data),url
def
fetch_error(error,url):
print
error.getErrorMessage(),url
# 批量抓取連接
for
url
in
links:
getPage(url,timeout
=
5
) \
.addCallback(parse_page,url) \
#成功則調用parse_page方法
.addErrback(fetch_error,url)
#失敗則調用fetch_error方法
reactor.callLater(
5
, reactor.stop)
#5秒鐘後通知reactor結束程序
reactor.run()
|
twisted人如其名,寫的代碼實在是太扭曲了,非正常人所能接受,雖然這個簡單的例子看上去還好;每次寫twisted的程序整我的都扭曲了,累得不得了,文檔等於沒有,必須得看源碼才知道怎麼整,唉不提了。
若是要支持gzip/deflate,甚至作一些登錄的擴展,就得爲twisted寫個新的HTTPClientFactory類諸如此類,我這眉頭真是大皺,遂放棄。有毅力者請自行嘗試。
這篇講怎麼用twisted來進行批量網址處理的文章不錯,由淺入深,深刻淺出,能夠一看。
仍是以爲在urllib之類python「本土」的東東里面折騰起來更舒服。試想一下,若是有個Fetcher類,你能夠這麼調用
1
2
3
4
5
6
|
f
=
Fetcher(threads
=
10
)
#設定下載線程數爲10
for
url
in
urls:
f.push(url)
#把全部url推入下載隊列
while
f.taskleft():
#若還有未完成下載的線程
content
=
f.pop()
#從下載完成隊列中取出結果
do_with(content)
# 處理content內容
|
這 麼個多線程調用簡單明瞭,那麼就這麼設計吧,首先要有兩個隊列,用Queue搞定,多線程的基本架構也和「技巧總結」一文相似,push方法和pop方法 都比較好處理,都是直接用Queue的方法,taskleft則是若是有「正在運行的任務」或者」隊列中的任務」則爲是,也好辦,因而代碼以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
import
urllib2
from
threading
import
Thread,Lock
from
Queue
import
Queue
import
time
class
Fetcher:
def
__init__(
self
,threads):
self
.opener
=
urllib2.build_opener(urllib2.HTTPHandler)
self
.lock
=
Lock()
#線程鎖
self
.q_req
=
Queue()
#任務隊列
self
.q_ans
=
Queue()
#完成隊列
self
.threads
=
threads
for
i
in
range
(threads):
t
=
Thread(target
=
self
.threadget)
t.setDaemon(
True
)
t.start()
self
.running
=
0
def
__del__(
self
):
#解構時需等待兩個隊列完成
time.sleep(
0.5
)
self
.q_req.join()
self
.q_ans.join()
def
taskleft(
self
):
return
self
.q_req.qsize()
+
self
.q_ans.qsize()
+
self
.running
def
push(
self
,req):
self
.q_req.put(req)
def
pop(
self
):
return
self
|