2

Google App Engine でウィキペディア リンク クローラーを構築しようとしています。データストアにインデックスを保存したかったのです。しかし、cron ジョブとタスク キューの両方で DeadlineExceededError が発生します。

cron ジョブの場合、次のコードがあります。

def buildTree(self):

    start=time.time()
    self.log.info(" Start Time: %f" % start)
    nobranches=TreeNode.all()       

    for tree in nobranches:            
        if tree.branches==[]:
            self.addBranches(tree)
            time.sleep(1)
        if (time.time()-start) > 10 :                
            break
        self.log.info("Time Eclipsed: %f" % (time.time()-start))

    self.log.info(" End Time:%f" % time.clock())

for ループが 10 秒後に中断しない理由がわかりません。開発サーバー上で行います。サーバーの time.time() に問題があるはずです。他に使える機能はありますか?

タスク キューの場合、次のコードがあります。
def addNewBranch(self, keyword, level=0):

    self.log.debug("Add Tree")        
    self.addBranches(keyword)

    t=TreeNode.gql("WHERE name=:1", keyword).get()
    branches=t.nodes

    if level < 3:
        for branch in branches:
            if branch.branches == []:
                taskqueue.add(url="/addTree/%s" % branch.name)
                self.log.debug("url:%s" % "/addTree/%s" % branch.name)

ログは、両方が DeadlineExceededError に遭遇したことを示しています。バックグラウンド処理は、ページ リクエストの 30 秒より長くすべきではありません。例外を回避する方法はありますか?

addBranch() のコードは次のとおりです。

def addBranches(self, keyword):

    tree=TreeNode.gql("WHERE name=:1", keyword).get()
    if tree is None:
        tree=TreeNode(name=keyword)


    self.log.debug("in addBranches arguments: tree %s", tree.name)     
    t=urllib2.quote(tree.name.encode('utf8'))
    s="http://en.wikipedia.org/w/api.php?action=query&titles=%s&prop=links&pllimit=500&format=xml" % t
    self.log.debug(s)
    try:        
        usock = urllib2.urlopen(s)       
    except :        

        self.log.error( "Could not retrieve doc: %s" % tree.name)
        usock=None

    if usock is not None:

        try:
            xmldoc=minidom.parse(usock)
        except Exception , error:
            self.log.error("Parse Error: %s" % error) 
            return None   
        usock.close()            
        try:
            pyNode= xmldoc.getElementsByTagName('pl')  
            self.log.debug("Nodes to be added: %d" % pyNode.length)
        except Exception, e:
            pyNode=None
            self.log.error("Getting Nodes Error: %s" % e)
            return None
        newNodes=[]    
        if pyNode is not None:
            for child in pyNode: 
                node=None             
                node= TreeNode.gql("WHERE name=:1", child.attributes["title"].value).get()

                if node is None:
                    newNodes.append(TreeNode(name=child.attributes["title"].value))           

                else:
                    tree.branches.append(node.key())  
            db.put(newNodes)
            for node in newNodes:
                tree.branches.append(node.key())
                self.log.debug("Node Added: %s" % node.name)                    
            tree.put()
            return tree.branches 

4

4 に答える 4

2

私はGAEの日時で大成功を収めました。

from datetime import datetime, timedelta
time_start = datetime.now()
time_taken = datetime.now() - time_start

time_takenはtimedeltaになります。関心のある期間を持つ別のタイムデルタと比較できます。

ten_seconds = timedelta(seconds=10)
if time_taken > ten_seconds:
    ....do something quick.

mapreduceまたはタスクキューを使用すると、はるかに優れたサービスが提供されるようです。どちらも膨大な数のレコードを扱うのにとても楽しいです。

あなたが持っているコードのよりクリーンなパターンは、いくつかのレコードだけをフェッチすることです。

nobranches=TreeNode.all().fetch(100)

このコードは100レコードのみをプルします。100がいっぱいの場合は、完了したら、キューに別のアイテムをスローして、さらに起動することができます。

-枝のない木が必要だというコメントに基づく-

そこにあなたのモデルは表示されませんが、枝のないすべての木のリストを作成して処理しようとすると、次のようになります。100程度のブロックの木のみのキーを取得します。次に、Inクエリを使用して、それらのツリーに属するすべてのブランチをフェッチします。ツリーキーで並べ替えます。ブランチのリストをスキャンします。ツリーのキーを初めて見つけたときに、リストからキーツリーをプルします。完了すると、「ブランチレス」ツリーキーのリストが表示されます。それらのそれぞれを処理するようにスケジュールします。

より簡単なバージョンは、ツリーでMapReduceを使用することです。ツリーごとに、そのIDに一致するブランチを1つ見つけます。できない場合は、フォローアップのためにツリーにフラグを立てます。デフォルトでは、この関数は8つの同時ワーカーでツリーのバッチ(25と思います)をプルします。また、ジョブキューを内部で管理するため、タイムアウトについて心配する必要はありません。

于 2010-10-12T23:12:23.577 に答える
1

ここでの問題は、ドキュメント内のすべてのリンクに対してクエリ操作を実行していることです。ウィキペディアのページには多くのリンクが含まれている可能性があるため、これは多くのクエリを意味します。したがって、処理時間が不足します。このアプローチはまた、素晴らしいレートでクォータを消費します!

代わりに、エンティティのキ​​ー名としてWikipediaページのページ名を使用する必要があります。次に、ドキュメントからすべてのリンクをリストにまとめ、それらからキーを作成し(これは完全にローカル操作です)、すべてのリンクに対して単一のバッチdb.getを実行できます。必要に応じて更新および/または作成したら、バッチdb.putを実行して、それらすべてをデータストアに保存できます。これにより、データストア操作の合計がnumlinks*2からわずか2に削減されます。

于 2010-10-13T09:37:31.303 に答える
1

コードを適切な時間枠内で実行する以外に、期限の例外を回避する方法はありません。

于 2010-10-12T22:16:43.530 に答える
1

DeadlineExcededErrors が発生した場合、再度呼び出されたときにリクエストが最終的に成功するようにします。これには、クロールの状態が、次回スキップできるある程度の進行が保証されていることが必要になる場合があります。(ここでは扱いません)

並列化された呼び出しは非常に役立ちます。

  • Urlfetch
  • Datastore Put (db.put を使用して混合エンティティをまとめる)
  • データストア クエリ (並列クエリ - asynctools)

URL フェッチ:

  • urlfetch 呼び出しを行うときは、必ず非同期モードを使用してループを折りたたんでください。

データストア

非同期ツールの例

    if pyNode is not None:

        runner = AsyncMultiTask()
        for child in pyNode:
             title = child.attributes["title"].value
             query = db.GqlQuery("SELECT __key__ FROM TreeNode WHERE name = :1", title)
             runner.append(QueryTask(query, limit=1, client_state=title))

        # kick off the work
        runner.run()

        # peel out the results
        treeNodes = []
        for task in runner:
            task_result = task.get_result() # will raise any exception that occurred for the given query
            treeNodes.append(task_result)

        for node in treeNodes:
            if node is None:
                newNodes.append(TreeNode(name=child.attributes["title"].value))

            else:
                tree.branches.append(node.key())
        for node in newNodes:
            tree.branches.append(node.key())
            self.log.debug("Node Added: %s" % node.name)

        # put newNodes+tree at the same time
        db.put(newNodes+tree)
        return tree.branches

開示: 私は asynctools に関連付けられています。

于 2010-10-13T05:52:58.263 に答える