• scrapy_redis 相关: 多线程更新 score/request.priority


    0.背景

    使用 scrapy_redis 爬虫, 忘记或错误设置 request.priority(Rule 也可以通过参数 process_request 设置 request.priority),导致提取 item 的 request 排在有序集 xxx:requests 的队尾,持续占用内存。

    1.代码实现

    遍历 SortedSet 的所有 item 并根据预定义字典对 data 中的 url 进行正则匹配,更新 score 并复制到临时 newkey,最后执行 rename 

    # -*- coding: UTF-8 -*
    import sys
    import re
    from multiprocessing.dummy import Pool as ThreadPool
    from functools import partial
    
    try:
       input = raw_input #For py2
    except NameError:
       pass
    
    import redis
    
    
    def print_line(string):
        print('
    {symbol}{space}{string}'.format(symbol='#'*10, space=' '*5, string=string))
    
    
    def check_key_scores(key):
        try:
            total = redis_server.zcard(key)
        except redis.exceptions.ResponseError:
            print("The value of '{key}' is not a SortedSet".format(key=key))
            sys.exit()
        except Exception as err:
            print(err)
            sys.exit()
    
        if total == 0:
            print("key '{key}' does not exist or has no items".format(key=key))
            sys.exit()
    
        __, min_score = redis_server.zrange(key, 0, 0, withscores=True)[0]
        __, max_score = redis_server.zrange(key, -1, -1, withscores=True)[0]
    
        print('score  amount')
        total_ = 0
        # Asuming that score/request.priority is an integer, rather than float number like 1.1
        for score in range(int(min_score), int(max_score)+1):
            count = redis_server.zcount(key, score, score)
            print(score, count)
            total_ += count
        print("{total_}/{total} items of key '{key}' have an integer priority".format(
                total_=total_, total=total_, key=key))
    
    
    def zadd_with_new_score(startstop, total_items):
        data, ori_score = redis_server.zrange(key, startstop, startstop, withscores=True)[0]
        for pattern, score in pattern_score:
            # data eg: b'\x80\x02}q\x00(X\x03\x00\x00\x00urlq\x01X\x13\x00\x00\x00http://httpbin.org/q\x02X\x08\x00\x00\x00callbackq\x03X\x
            # See /site-packages/scrapy_redis/queue.py
                # We don't use zadd method as the order of arguments change depending on
                # whether the class is Redis or StrictRedis, and the option of using
                # kwargs only accepts strings, not bytes.
            m = pattern.search(data.decode('utf-8', 'replace'))
            if m:
                redis_server.execute_command('ZADD', newkey, score, data)
                break
        else:
            redis_server.execute_command('ZADD', newkey, ori_score, data)
        print('{startstop} / {total_items}'.format(
                startstop=startstop+1, total_items=total_items))
    
    
    if __name__ == '__main__':
    
        password = 'password'
        host = '127.0.0.1'
        port = '6379'
        database_num = 0
    
        key = 'test:requests'
        newkey = 'temp'
        # Request whose url matching any key of keyword_score would be updated with the corresponding value as its score
        # Smaller value/score means higher request.priority
        keyword_score = {'httpbin': -12, 'apps/details': 1}
        pattern_score = [(re.compile(r'url.*?%s.*?callback'%k), v)for (k, v) in keyword_score.items()]
        
        threads_amount = 10
    
        redis_server = redis.StrictRedis.from_url('redis://:{password}@{host}:{port}/{database_num}'.format(
                                                    password=password, host=host,
                                                    port=port, database_num=database_num))
    
    
        print_line('Step 0: pre check')
        check_key_scores(key)
    
    
        print_line('Step 1: copy items and update score')
        # total_items = redis_server.zlexcount(key, '-', '+')
        total_items = redis_server.zcard(key)
        input("Press Enter to copy {total_items} items of '{key}' into '{newkey}' with new score".format(
                total_items=total_items, key=key, newkey=newkey))
        p = ThreadPool(threads_amount)
        p.map(partial(zadd_with_new_score, total_items=total_items), range(total_items))
        p.close()   #Prevents any more tasks from being submitted to the pool. Once all the tasks have been completed the worker processes will exit.
        p.join()    #Wait for the worker processes to exit. One must call close() or terminate() before using join().
    
        # For py3
        # https://stackoverflow.com/questions/5442910/python-multiprocessing-pool-map-for-multiple-arguments
        # with ThreadPool(threads_amount) as pool:
            # pool.map(partial(zadd_with_new_score, total_items=total_items), range(total_items))
        # print('zadd_with_new_score done')
    
    
        print_line('Step 2: check copy result')
        check_key_scores(key)
        check_key_scores(newkey)
    
    
        print_line('Step 3: delete, rename and check key')
        input("Press Enter to DELETE '{key}' and RENAME '{newkey}' to '{key}'".format(
                key=key, newkey=newkey))
        print(redis_server.delete(key))
        print(redis_server.rename(newkey, key))
        check_key_scores(key)
        check_key_scores(newkey)

    2.运行结果

  • 相关阅读:
    LOJ6395 「THUPC2018」城市地铁规划 / City
    [题解] 好好
    [题解] CF1316F Battalion Strength
    【题解】CF1320D Reachable Strings
    【题解】夕张的改造
    【题解】期望次数
    [FJWC2020] lg
    Django 多表操作
    Django 单表操作
    Django 模板层 静态文件
  • 原文地址:https://www.cnblogs.com/my8100/p/scrapy_redis_update_score.html
Copyright © 2020-2023  润新知