簡體   English   中英

學習python和線程。 我認為我的代碼可以無限運行。 幫我找出錯誤?

[英]Learning python and threading. I think my code runs infinitely. Help me find bugs?

所以我現在就開始學習python,我絕對愛上它了。

我正在構建一個小型的Facebook數據收集器。 基本上,它將使用Graph API並刮取指定數量用戶的名字。 它在單線程(或我猜沒有線程)中可以正常工作。

我使用在線教程提出了以下多線程版本(更新的代碼)

import requests
import json
import time
import threading
import Queue

GraphURL = 'http://graph.facebook.com/'
first_names = {} # will store first names and their counts
queue = Queue.Queue()

def getOneUser(url):
    http_response = requests.get(url) # open the request URL
    if http_response.status_code == 200:
        data = http_response.text.encode('utf-8', 'ignore') # Get the text of response, and encode it
        json_obj = json.loads(data) # load it as a json object
        # name = json_obj['name']
        return json_obj['first_name']
        # last = json_obj['last_name']
    return None

class ThreadGet(threading.Thread):
    """ Threaded name scraper """
    def __init__(self, queue):
        threading.Thread.__init__(self)
        self.queue = queue

    def run(self):
        while True:
            #print 'thread started\n'
            url = GraphURL + str(self.queue.get())
            first = getOneUser(url) # get one user's first name
            if first is not None:
                if first_names.has_key(first): # if name has been encountered before
                    first_names[first] = first_names[first] + 1 # increment the count
                else:
                    first_names[first] = 1 # add the new name
            self.queue.task_done()
            #print 'thread ended\n'

def main():
    start = time.time()
    for i in range(6):
        t = ThreadGet(queue)
        t.setDaemon(True)
        t.start()

    for i in range(100):
        queue.put(i)

    queue.join()

    for name in first_names.keys():
        print name + ': ' + str(first_names[name])

    print '----------------------------------------------------------------'
    print '================================================================'
    # Print top first names
    for key in first_names.keys():
        if first_names[key] > 2:
            print key + ': ' + str(first_names[key])

    print 'It took ' + str(time.time()-start) + 's'

main()

老實說,我不了解代碼的某些部分,但我掌握了主要思想。 輸出為空。 我的意思是說外殼沒有任何東西,所以我相信它可以繼續運行。

所以我正在做的是用fb上的用戶ID整數填充queue 然后使用每個ID來構建api調用URL。 getOneUser返回一個用戶的名稱。 task (ID)被標記為“完成”,並且繼續進行。

上面的代碼有什么問題?

您的原始run功能僅處理隊列中的一項。 您總共只從隊列中刪除了5個項目。

通常run功能看起來像

run(self):
    while True:
         doUsefulWork()

也就是說,它們有一個循環,導致重復工作得以完成。

[編輯] OP編輯的代碼以包含此更改。

其他一些有用的嘗試:

  • run函數中添加一個打印語句:您會發現它僅被調用5次。
  • 刪除queue.join()調用,這就是導致模塊阻塞的原因,那么您將能夠探查隊列的狀態。
  • 將整個run變成一個函數。 驗證您可以單線程方式使用該功能以獲得所需的結果,然后
  • 僅用一個工作線程嘗試一下,然后最后
  • 多個工作線程。

您對first_names使用不是線程安全的。 您可以添加鎖來保護增量。 否則,代碼應該可以工作。 您可能會遇到一些Facebook API限制,即,您應該限制請求率。

您可以通過使用線程池並計算主線程中的名稱來簡化代碼:

#!/usr/bin/env python
import json
import urllib2
from collections import Counter
from multiprocessing.dummy import Pool # use threads

def get_name(url):
    try:
        return json.load(urllib2.urlopen(url))['first_name']
    except Exception:
        return None # error

urls = ('http://graph.facebook.com/%d' % i for i in xrange(100))
p = Pool(5) # 5 concurrent connections
first_names = Counter(p.imap_unordered(get_name, urls))
print first_names.most_common()

要查看出現什么錯誤,可以添加日志記錄:

#!/usr/bin/env python
import json
import logging
import urllib2
from collections import Counter
from multiprocessing.dummy import Pool # use threads

logging.basicConfig(level=logging.DEBUG,
                    format="%(asctime)s %(threadName)s %(message)s")

def get_name(url):
    try:
        name = json.load(urllib2.urlopen(url))['first_name']
    except Exception as e:
        logging.debug('error: %s url: %s', e, url)
        return None # error
    else:
        logging.debug('done url: %s', url)
        return name

urls = ('http://graph.facebook.com/%d' % i for i in xrange(100))
p = Pool(5) # 5 concurrent connections
first_names = Counter(p.imap_unordered(get_name, urls))
print first_names.most_common()

限制每個給定時間段內請求數量的一種簡單方法是使用信號量:

#!/usr/bin/env python
import json
import logging
import time
import urllib2
from collections import Counter
from multiprocessing.dummy import Pool # use threads
from threading import _BoundedSemaphore as BoundedSemaphore, Timer

logging.basicConfig(level=logging.DEBUG,
                    format="%(asctime)s %(threadName)s %(message)s")

class RatedSemaphore(BoundedSemaphore):
    """Limit to 1 request per `period / value` seconds (over long run)."""
    def __init__(self, value=1, period=1):
        BoundedSemaphore.__init__(self, value)
        t = Timer(period, self._add_token_loop,
                  kwargs=dict(time_delta=float(period) / value))
        t.daemon = True
        t.start()

    def _add_token_loop(self, time_delta):
        """Add token every time_delta seconds."""
        while True:
            try:
                BoundedSemaphore.release(self)
            except ValueError: # ignore if already max possible value
                pass
            time.sleep(time_delta) # ignore EINTR

    def release(self):
        pass # do nothing (only time-based release() is allowed)

def get_name(gid, rate_limit=RatedSemaphore(value=100, period=600)):
    url = 'http://graph.facebook.com/%d' % gid
    try:
        with rate_limit:
            name = json.load(urllib2.urlopen(url))['first_name']
    except Exception as e:
        logging.debug('error: %s url: %s', e, url)
        return None # error
    else:
        logging.debug('done url: %s', url)
        return name

p = Pool(5) # 5 concurrent connections
first_names = Counter(p.imap_unordered(get_name, xrange(200)))
print first_names.most_common()

初始突發之后,它應該每6秒發出一個請求。

考慮使用批處理請求

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM